I am trying to connect to Firebase Cloud Messaging by using smack library.
I don't have much knowledge of the Smack API. After reading the Firebase docs, I see that the connection must be authenticated and there are a series of responses back and forth between "app server" and Cloud Connection Servers. According to the docs, I must create a Sasl Plain authentication. I don't know how to implement this. But after reading some posts by other StackOverFlow users I see that I must create an authentication class. Specifically, I was reviewing the answers and comments on "Gtalk XMPP SASL authentication failed using mechanism X-OAUTH2?" These responses that are between CCS and the "app server" are enclosed in and tags. I dont know how to use Smack to get or build these responses. I do have my connection set up with XMPP, and I've tried setting a addAsyncStanzListener to my XMPP connection with the hopes of getting some of these responses from CCS. But, nothing comes in. The responses between "app server" and CCS could be found in this link https://firebase.google.com/docs/cloud-messaging/server#connecting
Does anyone here know how to proceed from here. I think that Smack is not well documented and having little knowledge of XMPP makes it even worse. There are all these classes, Packets, extensions, IQ class, XML pull parsers etc.
Any rough structure on the set up would be ideal.
Thanks
Here is a snippet of my server code that I use with Firebase. If you want to understand in more detail try this blog post (it is how I figured out how to get everything to work in the end): http://www.grokkingandroid.com/xmpp-server-google-cloud-messaging/
In order to get the code below to work you will need to go to the Firebase console and go to project settings
From here you will need to take note of your Server Key and Sender ID and replace it in the code snippet below (it is near the bottom of the code) and save it in your path as SmackCcsClient.class
After doing that you can compile and run your server through the command promt:
// Set up java file (replace PATH_TO_WHERE_YOUR_CLASS_IS with your own path and make sure to put the json and smack JARs there as well
javac -d PATH_TO_WHERE_YOUR_CLASS_IS -sourcepath src -cp PATH_TO_WHERE_YOUR_CLASS_IS\json-simple-1.1.1.jar;PATH_TO_WHERE_YOUR_CLASS_IS\smack-3.4.1-0cec571.jar PATH_TO_WHERE_YOUR_CLASS_IS\SmackCcsClient.java
// Run
java -cp PATH_TO_WHERE_YOUR_CLASS_IS;PATH_TO_WHERE_YOUR_CLASS_IS\json-simple-1.1.1.jar;PATH_TO_WHERE_YOUR_CLASS_IS\smack-3.4.1-0cec571.jar SmackCcsClient
/*
SmackCcsClient:
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.PacketInterceptor;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.DefaultPacketExtension;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.provider.PacketExtensionProvider;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.util.StringUtils;
import org.json.simple.JSONValue;
import org.json.simple.parser.ParseException;
import org.xmlpull.v1.XmlPullParser;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.io.*;
import javax.net.ssl.SSLSocketFactory;
/**
* Sample Smack implementation of a client for GCM Cloud Connection Server.
*
* For illustration purposes only.
*/
public class SmackCcsClient {
static final String REG_ID_STORE = "gcmchat.txt";
static final String MESSAGE_KEY = "SM";
Logger logger = Logger.getLogger("SmackCcsClient");
public static final String GCM_SERVER = "fcm-xmpp.googleapis.com";
public static final int GCM_PORT = 5235;
public static final String GCM_ELEMENT_NAME = "gcm";
public static final String GCM_NAMESPACE = "google:mobile:data";
static Random random = new Random();
XMPPConnection connection;
ConnectionConfiguration config;
/**
* XMPP Packet Extension for GCM Cloud Connection Server.
*/
class GcmPacketExtension extends DefaultPacketExtension {
String json;
public GcmPacketExtension(String json) {
super(GCM_ELEMENT_NAME, GCM_NAMESPACE);
this.json = json;
}
public String getJson() {
return json;
}
#Override
public String toXML() {
return String.format("<%s xmlns=\"%s\">%s</%s>", GCM_ELEMENT_NAME,
GCM_NAMESPACE, json, GCM_ELEMENT_NAME);
}
#SuppressWarnings("unused")
public Packet toPacket() {
return new Message() {
// Must override toXML() because it includes a <body>
#Override
public String toXML() {
StringBuilder buf = new StringBuilder();
buf.append("<message");
if (getXmlns() != null) {
buf.append(" xmlns=\"").append(getXmlns()).append("\"");
}
if (getLanguage() != null) {
buf.append(" xml:lang=\"").append(getLanguage())
.append("\"");
}
if (getPacketID() != null) {
buf.append(" id=\"").append(getPacketID()).append("\"");
}
if (getTo() != null) {
buf.append(" to=\"")
.append(StringUtils.escapeForXML(getTo()))
.append("\"");
}
if (getFrom() != null) {
buf.append(" from=\"")
.append(StringUtils.escapeForXML(getFrom()))
.append("\"");
}
buf.append(">");
buf.append(GcmPacketExtension.this.toXML());
buf.append("</message>");
return buf.toString();
}
};
}
}
public SmackCcsClient() {
// Add GcmPacketExtension
ProviderManager.getInstance().addExtensionProvider(GCM_ELEMENT_NAME,
GCM_NAMESPACE, new PacketExtensionProvider() {
#Override
public PacketExtension parseExtension(XmlPullParser parser)
throws Exception {
String json = parser.nextText();
GcmPacketExtension packet = new GcmPacketExtension(json);
return packet;
}
});
}
/**
* Returns a random message id to uniquely identify a message.
*
* <p>
* Note: This is generated by a pseudo random number generator for
* illustration purpose, and is not guaranteed to be unique.
*
*/
public String getRandomMessageId() {
return "m-" + Long.toString(random.nextLong());
}
/**
* Sends a downstream GCM message.
*/
public void send(String jsonRequest) {
Packet request = new GcmPacketExtension(jsonRequest).toPacket();
connection.sendPacket(request);
}
/**
* Handles an upstream data message from a device application.
*
* <p>
* This sample echo server sends an echo message back to the device.
* Subclasses should override this method to process an upstream message.
*/
public void handleIncomingDataMessage(Map<String, Object> jsonObject) {
String from = jsonObject.get("from").toString();
// PackageName of the application that sent this message.
String category = jsonObject.get("category").toString();
// Use the packageName as the collapseKey in the echo packet
String collapseKey = "echo:CollapseKey";
#SuppressWarnings("unchecked")
Map<String, String> payload = (Map<String, String>) jsonObject
.get("data");
String messageText = payload.get("message_text");
String messageFrom = payload.get("message_userfrom");
String messageTime = payload.get("message_timestamp");
String toUser = payload.get("message_recipient");
payload.put("message_text", messageText);
payload.put("message_userfrom", messageFrom);
payload.put("message_timestamp", messageTime);
String message = createJsonMessage(toUser, getRandomMessageId(),
payload, collapseKey, null, false);
send(message);
}
/**
* Handles an ACK.
*
* <p>
* By default, it only logs a INFO message, but subclasses could override it
* to properly handle ACKS.
*/
public void handleAckReceipt(Map<String, Object> jsonObject) {
String messageId = jsonObject.get("message_id").toString();
String from = jsonObject.get("from").toString();
logger.log(Level.INFO, "handleAckReceipt() from: " + from
+ ", messageId: " + messageId);
}
/**
* Handles a NACK.
*
* <p>
* By default, it only logs a INFO message, but subclasses could override it
* to properly handle NACKS.
*/
public void handleNackReceipt(Map<String, Object> jsonObject) {
String messageId = jsonObject.get("message_id").toString();
String from = jsonObject.get("from").toString();
logger.log(Level.INFO, "handleNackReceipt() from: " + from
+ ", messageId: " + messageId);
}
/**
* Creates a JSON encoded GCM message.
*
* #param to
* RegistrationId of the target device (Required).
* #param messageId
* Unique messageId for which CCS will send an "ack/nack"
* (Required).
* #param payload
* Message content intended for the application. (Optional).
* #param collapseKey
* GCM collapse_key parameter (Optional).
* #param timeToLive
* GCM time_to_live parameter (Optional).
* #param delayWhileIdle
* GCM delay_while_idle parameter (Optional).
* #return JSON encoded GCM message.
*/
public static String createJsonMessage(String to, String messageId,
Map<String, String> payload, String collapseKey, Long timeToLive,
Boolean delayWhileIdle) {
Map<String, Object> message = new HashMap<String, Object>();
message.put("to", to);
if (collapseKey != null) {
message.put("collapse_key", collapseKey);
}
if (timeToLive != null) {
message.put("time_to_live", timeToLive);
}
if (delayWhileIdle != null && delayWhileIdle) {
message.put("delay_while_idle", true);
}
message.put("message_id", messageId);
message.put("data", payload);
return JSONValue.toJSONString(message);
}
/**
* Creates a JSON encoded ACK message for an upstream message received from
* an application.
*
* #param to
* RegistrationId of the device who sent the upstream message.
* #param messageId
* messageId of the upstream message to be acknowledged to CCS.
* #return JSON encoded ack.
*/
public static String createJsonAck(String to, String messageId) {
Map<String, Object> message = new HashMap<String, Object>();
message.put("message_type", "ack");
message.put("to", to);
message.put("message_id", messageId);
return JSONValue.toJSONString(message);
}
/**
* Connects to GCM Cloud Connection Server using the supplied credentials.
*
* #param username
* GCM_SENDER_ID#gcm.googleapis.com
* #param password
* API Key
* #throws XMPPException
*/
public void connect(String username, String password) throws XMPPException {
config = new ConnectionConfiguration(GCM_SERVER, GCM_PORT);
config.setSecurityMode(SecurityMode.enabled);
config.setReconnectionAllowed(true);
config.setRosterLoadedAtLogin(false);
config.setSendPresence(false);
config.setSocketFactory(SSLSocketFactory.getDefault());
// NOTE: Set to true to launch a window with information about packets
// sent and received
config.setDebuggerEnabled(true);
// -Dsmack.debugEnabled=true
XMPPConnection.DEBUG_ENABLED = true;
connection = new XMPPConnection(config);
connection.connect();
connection.addConnectionListener(new ConnectionListener() {
#Override
public void reconnectionSuccessful() {
logger.info("Reconnecting..");
}
#Override
public void reconnectionFailed(Exception e) {
logger.log(Level.INFO, "Reconnection failed.. ", e);
}
#Override
public void reconnectingIn(int seconds) {
logger.log(Level.INFO, "Reconnecting in %d secs", seconds);
}
#Override
public void connectionClosedOnError(Exception e) {
logger.log(Level.INFO, "Connection closed on error.");
}
#Override
public void connectionClosed() {
logger.info("Connection closed.");
}
});
// Handle incoming packets
connection.addPacketListener(new PacketListener() {
#Override
public void processPacket(Packet packet) {
logger.log(Level.INFO, "Received: " + packet.toXML());
Message incomingMessage = (Message) packet;
GcmPacketExtension gcmPacket = (GcmPacketExtension) incomingMessage
.getExtension(GCM_NAMESPACE);
String json = gcmPacket.getJson();
try {
#SuppressWarnings("unchecked")
Map<String, Object> jsonObject = (Map<String, Object>) JSONValue
.parseWithException(json);
// present for "ack"/"nack", null otherwise
Object messageType = jsonObject.get("message_type");
if (messageType == null) {
// Normal upstream data message
handleIncomingDataMessage(jsonObject);
// Send ACK to CCS
String messageId = jsonObject.get("message_id")
.toString();
String from = jsonObject.get("from").toString();
String ack = createJsonAck(from, messageId);
send(ack);
} else if ("ack".equals(messageType.toString())) {
// Process Ack
handleAckReceipt(jsonObject);
} else if ("nack".equals(messageType.toString())) {
// Process Nack
handleNackReceipt(jsonObject);
} else {
logger.log(Level.WARNING,
"Unrecognized message type (%s)",
messageType.toString());
}
} catch (ParseException e) {
logger.log(Level.SEVERE, "Error parsing JSON " + json, e);
} catch (Exception e) {
logger.log(Level.SEVERE, "Couldn't send echo.", e);
}
}
}, new PacketTypeFilter(Message.class));
// Log all outgoing packets
connection.addPacketInterceptor(new PacketInterceptor() {
#Override
public void interceptPacket(Packet packet) {
logger.log(Level.INFO, "Sent: {0}", packet.toXML());
}
}, new PacketTypeFilter(Message.class));
connection.login(username, password);
}
public void writeToFile(String name, String regId) throws IOException {
Map<String, String> regIdMap = readFromFile();
regIdMap.put(name, regId);
PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(
REG_ID_STORE, false)));
for (Map.Entry<String, String> entry : regIdMap.entrySet()) {
out.println(entry.getKey() + "," + entry.getValue());
}
out.println(name + "," + regId);
out.close();
}
public Map<String, String> readFromFile() {
Map<String, String> regIdMap = null;
try {
BufferedReader br = new BufferedReader(new FileReader(REG_ID_STORE));
String regIdLine = "";
regIdMap = new HashMap<String, String>();
while ((regIdLine = br.readLine()) != null) {
String[] regArr = regIdLine.split(",");
regIdMap.put(regArr[0], regArr[1]);
}
br.close();
} catch(IOException ioe) {
}
return regIdMap;
}
public static void main(String [] args) {
final String userName = "Sender ID" + "#gcm.googleapis.com";
final String password = "Server key";
SmackCcsClient ccsClient = new SmackCcsClient();
try {
ccsClient.connect(userName, password);
} catch (XMPPException e) {
e.printStackTrace();
}
}
}
Related
Please tell me that how can I write my own created logs to text file in device ?
I found this code in stackoverflow itself but this code it prints whole logcat, How can I filter the same?
public static void write() {
try {
Process process = Runtime.getRuntime().exec("logcat -d");
// Log.e("","******************---1");
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
log = new StringBuilder();
String line;
// Log.e("","******************---2");
while ((line = bufferedReader.readLine()) != null) {
log.append(line);
}
} catch (IOException exception) {
}
//convert log to string
final String logString = new String(log.toString());
//create text file in SDCard
File sdCard = Environment.getExternalStorageDirectory();
// Log.e("","******************---3");
File dir = new File(sdCard.getAbsolutePath() + "/myLogcat");
dir.mkdirs();
File file = new File(dir, "logcat.txt");
try {
//to write logcat in text file
FileOutputStream fOut = new FileOutputStream(file);
OutputStreamWriter osw = new OutputStreamWriter(fOut);
// Write the string to the file
osw.write(logString);
// Log.e("", "******************---4");
osw.flush();
osw.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
Try this :
try {
Process process = Runtime.getRuntime().exec("logcat -d");
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
StringBuilder log=new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
log.append(line);
}
appendLog(log.toString());
} catch (IOException e) {
}
Method to write log to file:
public void appendLog(String text) {
File logFile = new File("sdcard/Log.txt");
if (!logFile.exists()) {
try {
logFile.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
// BufferedWriter for performance, true to set append to file flag
BufferedWriter buf = new BufferedWriter(new FileWriter(logFile,
true));
buf.append(text);
buf.newLine();
buf.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Add the following permission in manifest file also :
<uses-permission android:name="android.permission.READ_LOGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
First check out the log locations here: https://android.stackexchange.com/questions/14430/how-can-i-view-and-examine-the-android-log
If that's not good enough, you can always make your own Log.java class which would log directly to the file you want (though you should really reconsider using LogCat as it is working as expected).
As a alternate way you can create your custom logger.
Here is one sample util class that I have created for my project.
import android.os.Environment;
import android.util.Log;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
/**
* TODO: Add a class header comment!
*
* #author Dhaval Patel
* #version 1.0, May 24, 2015
* #since 1.0
*/
public final class Logger {
public static final int MAX_FILE_SIZE = 1024*1024*5;// max file size 5MB, if file size increase, Logger will create new file.
private static final String LOG_PREFIX = "prefix_";
private static final int LOG_PREFIX_LENGTH = LOG_PREFIX.length();
private static final int MAX_LOG_TAG_LENGTH = 23;
private static final Boolean ENABLE_CONSOLE_LOG = true; //Flag to enable or disable console log
private static final Boolean ENABLE_FILE_LOG = true; //Flag to enable or disable file log
private static final LogLevel GLOBAL_LOG_LEVEL = LogLevel.VERBOSE; //Flag indicate log level
private static final String LOG_DIRECTORY = Environment.getExternalStorageDirectory()+"log"+File.separator; //Log directory
public static String makeLogTag(String str) {
if (str.length() > MAX_LOG_TAG_LENGTH - LOG_PREFIX_LENGTH) {
return LOG_PREFIX + str.substring(0, MAX_LOG_TAG_LENGTH - LOG_PREFIX_LENGTH - 1);
}
return LOG_PREFIX + str;
}
private enum LogLevel{
VERBOSE(Log.VERBOSE),
DEBUG(Log.DEBUG),
INFO(Log.INFO),
WARNING(Log.WARN),
ERROR(Log.ERROR),
ASSERT(Log.ASSERT);
private final int logLevel;
LogLevel(int logLevel) {
this.logLevel = logLevel;
}
public int getLogLevel() {
return logLevel;
}
}
/**
*
* #param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* #param msg The message you would like logged.
*
*/
public static void v(String tag, String msg) {
write(LogLevel.VERBOSE, tag, msg);
}
/**
*
* #param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* #param msg The message you would like logged.
* #param tr An exception to log
*
*/
public static void v(String tag, String msg, Throwable tr) {
write(LogLevel.VERBOSE, tag, msg, tr);
}
/**
*
* #param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* #param msg The message you would like logged.
*
*/
public static void d(String tag, String msg) {
write(LogLevel.DEBUG, tag, msg);
}
/**
*
* #param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* #param msg The message you would like logged.
* #param tr An exception to log
*
*/
public static void d(String tag, String msg, Throwable tr) {
write(LogLevel.DEBUG, tag, msg, tr);
}
/**
*
* #param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* #param msg The message you would like logged.
*
*/
public static void i(String tag, String msg) {
write(LogLevel.INFO, tag, msg);
}
/**
*
* #param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* #param msg The message you would like logged.
* #param tr An exception to log
*
*/
public static void i(String tag, String msg, Throwable tr) {
write(LogLevel.INFO, tag, msg, tr);
}
/**
*
* #param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* #param msg The message you would like logged.
*
*/
public static void w(String tag, String msg) {
write(LogLevel.WARNING, tag, msg);
}
/**
*
* #param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* #param msg The message you would like logged.
* #param tr An exception to log
*
*/
public static void w(String tag, String msg, Throwable tr) {
write(LogLevel.WARNING, tag, msg, tr);
}
/**
*
* #param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* #param msg The message you would like logged.
*
*/
public static void e(String tag, String msg) {
write(LogLevel.ERROR, tag, msg);
}
/**
*
* #param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* #param msg The message you would like logged.
* #param tr An exception to log
*
*/
public static void e(String tag, String msg, Throwable tr) {
write(LogLevel.ERROR, tag, msg, tr);
}
private static boolean isLogEnable(LogLevel logLevel){
return GLOBAL_LOG_LEVEL.getLogLevel() <= logLevel.getLogLevel();
}
private static void write(LogLevel logLevel, String tag, String log) {
if (isLogEnable(logLevel) && ENABLE_FILE_LOG){
StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[4];
String logPoint = stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + ":" + stackTraceElement.getLineNumber();
String msg = "["+getCurrentDateTime()+"] "+ logLevel.name() +" "+ logPoint +" "+tag+"//:"+log;
write(msg);
}
if (isLogEnable(logLevel) && ENABLE_CONSOLE_LOG){
Log.println(logLevel.getLogLevel(), makeLogTag(tag), log);
}
}
private static void write(LogLevel logLevel, String tag, String log, Throwable tr){
if (isLogEnable(logLevel) && ENABLE_FILE_LOG){
StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[4];
String logPoint = stackTraceElement.getClassName() + "::" + stackTraceElement.getMethodName() + ":" + stackTraceElement.getLineNumber();
String msg = "["+getCurrentDateTime()+"] "+ logLevel.name() +" "+ logPoint+" "+tag+"//:"+log+"\n"+Log.getStackTraceString(tr);
write(msg);
}
if (isLogEnable(logLevel) && ENABLE_CONSOLE_LOG){
Log.println(logLevel.getLogLevel(), makeLogTag(tag), log + "\n" + Log.getStackTraceString(tr));
}
}
private static void write(String text){
BufferedWriter out = null;
String filePath=LOG_DIRECTORY;
try {
SimpleDateFormat df = new SimpleDateFormat("dd_MMM_yyyy", Locale.ENGLISH);
String formattedDate = df.format(System.currentTimeMillis());
if(!new File(LOG_DIRECTORY).exists()) {
new File(LOG_DIRECTORY).mkdirs();
}
filePath = LOG_DIRECTORY +formattedDate+".log";
while (new File(filePath).exists() && new File(filePath).length() > MAX_FILE_SIZE) {
String[] txt1 = filePath.split("\\.log");
int fileNum = 1;
if (txt1.length == 2) {
fileNum = Integer.parseInt(txt1[1].substring(1));
fileNum++;
}
filePath = LOG_DIRECTORY + formattedDate + ".log" + "." + fileNum;
}
if(!new File(filePath).exists()){
new File(filePath).createNewFile();
}
if(new File(filePath).exists()){
FileWriter fStream = new FileWriter(filePath, true);
out = new BufferedWriter(fStream);
out.write(text + "\n" + new File(filePath).length());
out.flush();
}
} catch (IOException e) {
Log.e("Log", "Path:"+filePath);
e.printStackTrace();
} catch (Exception e) {
Log.e("Log", e.getMessage());
e.printStackTrace();
} finally {
try {
if(out!=null)
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static String getCurrentDateTime(){
return new SimpleDateFormat("dd MMM yyyy HH:mm:ss:SSS" , Locale.getDefault()).format(Calendar.getInstance().getTime());
}
}
Use it same like your android Log.
Logger.i("Info", "my sample log");
Here log store in file date-wise, you can modify write method as per your needs.
I am new to Android Studio and intelliJ.
I am trying to work with AmazonSNS - Push. I am unable to figure out how to add AwsCredentials.properties file to classpath of the module. I get a NPE at line 57 in the image below(at method getResourceAsStream()). I added the required keys in AwsCredentials.properties file.
Error:
In the questions that i have come across on StackOverflow regarding similar issues, some suggested that the file should be in the root folder, where, src is. I placed it in the same folder as that of src, but still getting the NPE. I also tried placing the file in com/test/ but with no use.
How do i solve this? Are there any other steps involved?
EDIT after starting a bounty - Adding java files
Here is what i did till now..
Create an Android Application called MyApplication. Imported all classes(AndroidMobilePushApp.java, ExternalReceiver.java, MessageReceivingService.java) from the demo application. Added required libs, and ran it and got the registationId as response from Amazon.
In the same application, i created a new module called snspush and imported SNSMobilePush.java file into it. Also imported the AwsCredentials.properties file to the same path as that of SNSMobilePush.java. Added the keys in AwsCredentials.properties file.
Followed the steps in documentation to uncomment necessary funtions.
Project Structure:
Java files:
AndroidMobilePushApp.java:
public class AndroidMobilePushApp extends AppCompatActivity {
private TextView tView;
private SharedPreferences savedValues;
private String numOfMissedMessages;
// Since this activity is SingleTop, there can only ever be one instance. This variable corresponds to this instance.
public static Boolean inBackground = true;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
numOfMissedMessages = getString(R.string.num_of_missed_messages);
setContentView(R.layout.activity_main);
tView = (TextView) findViewById(R.id.tViewId);
tView.setMovementMethod(new ScrollingMovementMethod());
startService(new Intent(this, MessageReceivingService.class));
}
public void onStop(){
super.onStop();
inBackground = true;
}
public void onRestart(){
super.onRestart();
tView.setText("");;
}
public void onResume(){
super.onResume();
inBackground = false;
savedValues = MessageReceivingService.savedValues;
int numOfMissedMessages = 0;
if(savedValues != null){
numOfMissedMessages = savedValues.getInt(this.numOfMissedMessages, 0);
}
String newMessage = getMessage(numOfMissedMessages);
if(newMessage!=""){
Log.i("displaying message", newMessage);
tView.append(newMessage);
}
}
public void onNewIntent(Intent intent){
super.onNewIntent(intent);
setIntent(intent);
}
// If messages have been missed, check the backlog. Otherwise check the current intent for a new message.
private String getMessage(int numOfMissedMessages) {
String message = "";
String linesOfMessageCount = getString(R.string.lines_of_message_count);
if(numOfMissedMessages > 0){
String plural = numOfMissedMessages > 1 ? "s" : "";
Log.i("onResume","missed " + numOfMissedMessages + " message" + plural);
tView.append("You missed " + numOfMissedMessages +" message" + plural + ". Your most recent was:\n");
for(int i = 0; i < savedValues.getInt(linesOfMessageCount, 0); i++){
String line = savedValues.getString("MessageLine"+i, "");
message+= (line + "\n");
}
NotificationManager mNotification = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mNotification.cancel(R.string.notification_number);
SharedPreferences.Editor editor=savedValues.edit();
editor.putInt(this.numOfMissedMessages, 0);
editor.putInt(linesOfMessageCount, 0);
editor.commit();
}
else{
Log.i("onResume","no missed messages");
Intent intent = getIntent();
if(intent!=null){
Bundle extras = intent.getExtras();
if(extras!=null){
for(String key: extras.keySet()){
message+= key + "=" + extras.getString(key) + "\n";
}
}
}
}
message+="\n";
return message;
}
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
public boolean onOptionsItemSelected(MenuItem item) {
if(item.getItemId() == R.id.menu_clear){
tView.setText("");
return true;
}
else{
return super.onOptionsItemSelected(item);
}
}
}
ExternalReceiver.java
package com.test.awstestapp;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
public class ExternalReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
if(intent!=null){
Bundle extras = intent.getExtras();
if(!AndroidMobilePushApp.inBackground){
MessageReceivingService.sendToApp(extras, context);
}
else{
MessageReceivingService.saveToLog(extras, context);
}
}
}
}
MessageReceivingService.java
public class MessageReceivingService extends Service{
private GoogleCloudMessaging gcm;
public static SharedPreferences savedValues;
public static void sendToApp(Bundle extras, Context context){
Intent newIntent = new Intent();
newIntent.setClass(context, AndroidMobilePushApp.class);
newIntent.putExtras(extras);
newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(newIntent);
}
public void onCreate(){
super.onCreate();
final String preferences = getString(R.string.preferences);
savedValues = getSharedPreferences(preferences, Context.MODE_PRIVATE);
// In later versions multi_process is no longer the default
if(VERSION.SDK_INT > 9){
savedValues = getSharedPreferences(preferences, Context.MODE_MULTI_PROCESS);
}
gcm = GoogleCloudMessaging.getInstance(getBaseContext());
SharedPreferences savedValues = PreferenceManager.getDefaultSharedPreferences(this);
if(savedValues.getBoolean(getString(R.string.first_launch), true)){
register();
SharedPreferences.Editor editor = savedValues.edit();
editor.putBoolean(getString(R.string.first_launch), false);
editor.commit();
}
// Let AndroidMobilePushApp know we have just initialized and there may be stored messages
sendToApp(new Bundle(), this);
}
protected static void saveToLog(Bundle extras, Context context){
SharedPreferences.Editor editor=savedValues.edit();
String numOfMissedMessages = context.getString(R.string.num_of_missed_messages);
int linesOfMessageCount = 0;
for(String key : extras.keySet()){
String line = String.format("%s=%s", key, extras.getString(key));
editor.putString("MessageLine" + linesOfMessageCount, line);
linesOfMessageCount++;
}
editor.putInt(context.getString(R.string.lines_of_message_count), linesOfMessageCount);
editor.putInt(context.getString(R.string.lines_of_message_count), linesOfMessageCount);
editor.putInt(numOfMissedMessages, savedValues.getInt(numOfMissedMessages, 0) + 1);
editor.commit();
postNotification(new Intent(context, AndroidMobilePushApp.class), context);
}
protected static void postNotification(Intent intentAction, Context context){
final NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intentAction, Notification.DEFAULT_LIGHTS | Notification.FLAG_AUTO_CANCEL);
final Notification notification = new NotificationCompat.Builder(context).setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("Message Received!")
.setContentText("")
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.getNotification();
mNotificationManager.notify(R.string.notification_number, notification);
}
private void register() {
new AsyncTask(){
protected Object doInBackground(final Object... params) {
String token;
try {
token = gcm.register(getString(R.string.project_number));
Log.i("registrationId", token);
}
catch (IOException e) {
Log.i("Registration Error", e.getMessage());
}
return true;
}
}.execute(null, null, null);
}
public IBinder onBind(Intent arg0) {
return null;
}
}
SNSMobilePush.java
package com.test;
public class SNSMobilePush {
private AmazonSNSClientWrapper snsClientWrapper;
public SNSMobilePush(AmazonSNS snsClient) {
this.snsClientWrapper = new AmazonSNSClientWrapper(snsClient);
}
public static final Map<Platform, Map<String, MessageAttributeValue>> attributesMap = new HashMap<Platform, Map<String, MessageAttributeValue>>();
static {
attributesMap.put(Platform.ADM, null);
attributesMap.put(Platform.GCM, null);
attributesMap.put(Platform.APNS, null);
attributesMap.put(Platform.APNS_SANDBOX, null);
attributesMap.put(Platform.BAIDU, addBaiduNotificationAttributes());
attributesMap.put(Platform.WNS, addWNSNotificationAttributes());
attributesMap.put(Platform.MPNS, addMPNSNotificationAttributes());
}
public static void main(String[] args) throws IOException {
/*
* TODO: Be sure to fill in your AWS access credentials in the
* AwsCredentials.properties file before you try to run this sample.
* http://aws.amazon.com/security-credentials
*/
AmazonSNS sns = new AmazonSNSClient(new PropertiesCredentials(
SNSMobilePush.class
.getResourceAsStream("AwsCredentials.properties")));
sns.setEndpoint("https://sns.us-west-2.amazonaws.com");
System.out.println("===========================================\n");
System.out.println("Getting Started with Amazon SNS");
System.out.println("===========================================\n");
try {
SNSMobilePush sample = new SNSMobilePush(sns);
/* TODO: Uncomment the services you wish to use. */
sample.demoAndroidAppNotification();
// sample.demoKindleAppNotification();
// sample.demoAppleAppNotification();
// sample.demoAppleSandboxAppNotification();
// sample.demoBaiduAppNotification();
// sample.demoWNSAppNotification();
// sample.demoMPNSAppNotification();
} catch (AmazonServiceException ase) {
System.out
.println("Caught an AmazonServiceException, which means your request made it "
+ "to Amazon SNS, but was rejected with an error response for some reason.");
System.out.println("Error Message: " + ase.getMessage());
System.out.println("HTTP Status Code: " + ase.getStatusCode());
System.out.println("AWS Error Code: " + ase.getErrorCode());
System.out.println("Error Type: " + ase.getErrorType());
System.out.println("Request ID: " + ase.getRequestId());
} catch (AmazonClientException ace) {
System.out
.println("Caught an AmazonClientException, which means the client encountered "
+ "a serious internal problem while trying to communicate with SNS, such as not "
+ "being able to access the network.");
System.out.println("Error Message: " + ace.getMessage());
}
}
public void demoAndroidAppNotification() {
// TODO: Please fill in following values for your application. You can
// also change the notification payload as per your preferences using
// the method
// com.amazonaws.sns.samples.tools.SampleMessageGenerator.getSampleAndroidMessage()
String serverAPIKey = "REPLACED_WITH_SERVER_API_KEY";
String applicationName = "snspushtest";
String registrationId = "REPLACED_WITH_REG_ID_FROM_AMAZON";
snsClientWrapper.demoNotification(Platform.GCM, "", serverAPIKey,
registrationId, applicationName, attributesMap);
}
public void demoKindleAppNotification() {
// TODO: Please fill in following values for your application. You can
// also change the notification payload as per your preferences using
// the method
// com.amazonaws.sns.samples.tools.SampleMessageGenerator.getSampleKindleMessage()
String clientId = "";
String clientSecret = "";
String applicationName = "";
String registrationId = "";
snsClientWrapper.demoNotification(Platform.ADM, clientId, clientSecret,
registrationId, applicationName, attributesMap);
}
public void demoAppleAppNotification() {
// TODO: Please fill in following values for your application. You can
// also change the notification payload as per your preferences using
// the method
// com.amazonaws.sns.samples.tools.SampleMessageGenerator.getSampleAppleMessage()
String certificate = ""; // This should be in pem format with \n at the
// end of each line.
String privateKey = ""; // This should be in pem format with \n at the
// end of each line.
String applicationName = "";
String deviceToken = ""; // This is 64 hex characters.
snsClientWrapper.demoNotification(Platform.APNS, certificate,
privateKey, deviceToken, applicationName, attributesMap);
}
public void demoAppleSandboxAppNotification() {
// TODO: Please fill in following values for your application. You can
// also change the notification payload as per your preferences using
// the method
// com.amazonaws.sns.samples.tools.SampleMessageGenerator.getSampleAppleMessage()
String certificate = ""; // This should be in pem format with \n at the
// end of each line.
String privateKey = ""; // This should be in pem format with \n at the
// end of each line.
String applicationName = "";
String deviceToken = ""; // This is 64 hex characters.
snsClientWrapper.demoNotification(Platform.APNS_SANDBOX, certificate,
privateKey, deviceToken, applicationName, attributesMap);
}
public void demoBaiduAppNotification() {
/*
* TODO: Please fill in the following values for your application. If
* you wish to change the properties of your Baidu notification, you can
* do so by modifying the attribute values in the method
* addBaiduNotificationAttributes() . You can also change the
* notification payload as per your preferences using the method
* com.amazonaws
* .sns.samples.tools.SampleMessageGenerator.getSampleBaiduMessage()
*/
String userId = "";
String channelId = "";
String apiKey = "";
String secretKey = "";
String applicationName = "";
snsClientWrapper.demoNotification(Platform.BAIDU, apiKey, secretKey,
channelId + "|" + userId, applicationName, attributesMap);
}
public void demoWNSAppNotification() {
/*
* TODO: Please fill in the following values for your application. If
* you wish to change the properties of your WNS notification, you can
* do so by modifying the attribute values in the method
* addWNSNotificationAttributes() . You can also change the notification
* payload as per your preferences using the method
* com.amazonaws.sns.samples
* .tools.SampleMessageGenerator.getSampleWNSMessage()
*/
String notificationChannelURI = "";
String packageSecurityIdentifier = "";
String secretKey = "";
String applicationName = "";
snsClientWrapper.demoNotification(Platform.WNS,
packageSecurityIdentifier, secretKey, notificationChannelURI,
applicationName, attributesMap);
}
public void demoMPNSAppNotification() {
/*
* TODO: Please fill in the following values for your application. If
* you wish to change the properties of your MPNS notification, you can
* do so by modifying the attribute values in the method
* addMPNSNotificationAttributes() . You can also change the
* notification payload as per your preferences using the method
* com.amazonaws
* .sns.samples.tools.SampleMessageGenerator.getSampleMPNSMessage ()
*/
String notificationChannelURI = "";
String applicationName = "";
snsClientWrapper.demoNotification(Platform.MPNS, "", "",
notificationChannelURI, applicationName, attributesMap);
}
private static Map<String, MessageAttributeValue> addBaiduNotificationAttributes() {
Map<String, MessageAttributeValue> notificationAttributes = new HashMap<String, MessageAttributeValue>();
notificationAttributes.put("AWS.SNS.MOBILE.BAIDU.DeployStatus",
new MessageAttributeValue().withDataType("String")
.withStringValue("1"));
notificationAttributes.put("AWS.SNS.MOBILE.BAIDU.MessageKey",
new MessageAttributeValue().withDataType("String")
.withStringValue("default-channel-msg-key"));
notificationAttributes.put("AWS.SNS.MOBILE.BAIDU.MessageType",
new MessageAttributeValue().withDataType("String")
.withStringValue("0"));
return notificationAttributes;
}
private static Map<String, MessageAttributeValue> addWNSNotificationAttributes() {
Map<String, MessageAttributeValue> notificationAttributes = new HashMap<String, MessageAttributeValue>();
notificationAttributes.put("AWS.SNS.MOBILE.WNS.CachePolicy",
new MessageAttributeValue().withDataType("String")
.withStringValue("cache"));
notificationAttributes.put("AWS.SNS.MOBILE.WNS.Type",
new MessageAttributeValue().withDataType("String")
.withStringValue("wns/badge"));
return notificationAttributes;
}
private static Map<String, MessageAttributeValue> addMPNSNotificationAttributes() {
Map<String, MessageAttributeValue> notificationAttributes = new HashMap<String, MessageAttributeValue>();
notificationAttributes.put("AWS.SNS.MOBILE.MPNS.Type",
new MessageAttributeValue().withDataType("String")
.withStringValue("token")); // This attribute is required.
notificationAttributes.put("AWS.SNS.MOBILE.MPNS.NotificationClass",
new MessageAttributeValue().withDataType("String")
.withStringValue("realtime")); // This attribute is required.
return notificationAttributes;
}
}
EDIT 2:
EDIT 3:
I changed the following code to:
AmazonSNS sns = new AmazonSNSClient(new PropertiesCredentials(
SNSMobilePush.class
.getResourceAsStream("AwsCredentials.properties")));
to
AmazonSNS sns = new AmazonSNSClient(new BasicAWSCredentials("ACCESS_KEY_REPLACED",
"SECRET_KEY_REPLACED"));
Now, there is a different error: Logcat
===========================================
Getting Started with Amazon SNS
===========================================
Exception in thread "main" java.lang.NoClassDefFoundError: org/xmlpull/v1/XmlPullParserException
at com.amazonaws.services.sns.AmazonSNSClient.invoke(AmazonSNSClient.java:2263)
at com.amazonaws.services.sns.AmazonSNSClient.createPlatformApplication(AmazonSNSClient.java:358)
at com.test.tools.AmazonSNSClientWrapper.createPlatformApplication(AmazonSNSClientWrapper.java:49)
at com.test.tools.AmazonSNSClientWrapper.demoNotification(AmazonSNSClientWrapper.java:119)
at com.test.SNSMobilePush.demoAndroidAppNotification(SNSMobilePush.java:104)
at com.test.SNSMobilePush.main(SNSMobilePush.java:71)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
Caused by: java.lang.ClassNotFoundException: org.xmlpull.v1.XmlPullParserException
at java.net.URLClassLoader$1.run(URLClassLoader.java:372)
at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:360)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 11 more
Process finished with exit code 1
I'm using sns v2.2.5 :
build.gradle > compile 'com.amazonaws:aws-android-sdk-sns:2.2.5'
Here is my solution for subscribe/unsubscribe:
public class AmazonPushClient {
private static final String TAG = "AmazonPushClient";
private static final String PLATFORM_APPLICATION_ARN = "*****";
private static final String IDENTITY_POOL_ID = "******";
private AmazonSNSClient mClient;
private boolean isUnregistering;
private Application mApp;
private NotifPreferencesHelper mNotifPreferencesHelper;
public AmazonPushClient(Application application) {
try {
mApp = application;
mClient = createPushClient(application);
mNotifPreferencesHelper = new NotifPreferencesHelper(application);
} catch (Exception e) {
LOGE(TAG, "AmazonPushClient", e);
}
}
#Nullable
private String token() {
try {
return InstanceID.getInstance(mApp).getToken(mApp.getString(R.string.gcm_defaultSenderId),
GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
} catch (Exception e) {
LOGW(TAG, "token", e);
return null;
}
}
private CognitoCachingCredentialsProvider cognitoCachingCredentialsProvider(Application application) {
return new CognitoCachingCredentialsProvider(
application,
IDENTITY_POOL_ID,
Regions.EU_WEST_1 // if your identity_pool_id start with : eu-west-1
);
}
private AmazonSNSClient createPushClient(Application application) {
AmazonSNSClient client = new AmazonSNSClient(cognitoCachingCredentialsProvider(application));
client.setRegion(Region.getRegion(Regions.EU_WEST_1));
client.addRequestHandler(mHandler);
return client;
}
public void pRegister() {
synchronized (TAG) {
LOGD(TAG, "registering");
isUnregistering = true;
String token = token();
if(TextUtils.isEmpty(token)) {
return;
}
mNotifPreferencesHelper.saveNotificationPreferences(true);
CreatePlatformEndpointRequest platformEndpointRequest = new CreatePlatformEndpointRequest();
platformEndpointRequest.setToken(token());
platformEndpointRequest.setPlatformApplicationArn(PLATFORM_APPLICATION_ARN);
CreatePlatformEndpointResult result = mClient.createPlatformEndpoint(platformEndpointRequest);
mNotifPreferencesHelper.storeEndpointArn(result.getEndpointArn());
}
}
public void pUnregister() {
synchronized (TAG) {
LOGD(TAG, "unregistering");
isUnregistering = false;
mNotifPreferencesHelper.saveNotificationPreferences(false);
DeleteEndpointRequest deletePlatformApplicationRequest = new DeleteEndpointRequest();
deletePlatformApplicationRequest.setEndpointArn(mNotifPreferencesHelper.getEndpointArn());
mClient.deleteEndpoint(deletePlatformApplicationRequest);
}
}
private RequestHandler2 mHandler = new RequestHandler2() {
#Override
public void beforeRequest(Request<?> request) {
}
#Override
public void afterResponse(Request<?> request, Response<?> response) {
if (isUnregistering) {
mNotifPreferencesHelper.storeEndpointArn(null);
}
}
#Override
public void afterError(Request<?> request, Response<?> response, Exception e) {
}
};
}
NotifPreferencesHelper is just something to store the EndpointARN. You have to use this class in a background thread
In association you have to implements GcmListenerService etc.. SNS is just for subscribe, not receive.
https://developers.google.com/cloud-messaging/
With respect to the Properties Credentials
A. Are you sure you are exporting the file in the build? Have you made sure you can access the file using regular file I/O outside of the credentials provider? Are you sure the file is formatted correctly (see https://github.com/aws/aws-sdk-android/blob/master/aws-android-sdk-core/src/main/java/com/amazonaws/auth/PropertiesCredentials.java)
accessKey=KEY
secretKey=SECRET
Also looking at the source you should be able to load this file yourself using http://developer.android.com/reference/java/util/Properties.html.
However, I highly recommend not using this credentials provider. It is extremely unsafe in a mobile applications. Anyone could decompile your app and steal your credentials. A much safer approach is to use Amazon Cognito, which there is a plethora of examples. (Bottom of https://docs.aws.amazon.com/mobile/sdkforandroid/developerguide/setup.html , any of the examples here: https://docs.aws.amazon.com/mobile/sdkforandroid/developerguide/getting-started-android.html as well as samples in the GitHub repo https://github.com/awslabs/aws-sdk-android-samples
Cognito requires a little bit of set-up but the guides are tested, and it really doesn't take much more than a few minutes to be much more secure.
Don't think you should import 'SNSMobilePush' to a module of your android application
In the same application, i created a new module called snspush and
imported SNSMobilePush.java file into it. Also imported the
AwsCredentials.properties file to the same path as that of
SNSMobilePush.java. Added the keys in AwsCredentials.properties file.
SNSMobilePush is just a Java app provided by AWS to do tasks like
upload (bulkupload package) several tokens (device tokens or registration IDs) to Amazon SNS or
send a push notification.
You need to register your mobile app with AWS (using AndroidMobilePushApp android app). You should obtain below information (refer this link)
Client ID and client secret
API key
Device token or Registration ID (per device)
Then you can use SNSMobilePush java app or even a AWS SNS console as described here to send push notification to your registered device.
I would suggest you to try out sending push notification from the console instead of java app. You can register tokens from devices that will install your app in the future as described in the one of the options (preferably the last option) as described here
I've been having problems implementing Google Play Services login on my android app and passing the authorisation code to my backend server, so the server will exchange the code for access token and refresh token.
First let me write a few lines what has already been tried/read:
on code.google.com/apis/console I've created a new project, with two clients (WEB client and Android installed client)
read articles onhttps://developers.google.com/+/mobile/android/sign-in#cross-platform_single_sign_on and http://android-developers.blogspot.com/2013/01/verifying-back-end-calls-from-android.html
Here is my code for client side that retrieves the authorization code and IdToken:
package com.google.drive.samples.crossclientoauth2;
import java.util.Arrays;
import java.util.List;
import android.accounts.AccountManager;
import android.app.Activity;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.widget.EditText;
import com.google.android.gms.auth.GoogleAuthUtil;
import com.google.android.gms.auth.UserRecoverableAuthException;
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException;
public class MainActivity extends Activity {
final private String CLIENT_ID = MY WEB SERVER'S CLIENT ID;
final private List<String> SCOPES = Arrays.asList(new String[]{
"https://www.googleapis.com/auth/plus.login",
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/youtube",
"https://www.googleapis.com/auth/youtube.readonly"
});
// I have modified the above line of code.
private GoogleAccountCredential mCredential;
private EditText mExchangeCodeEditText;
private EditText mIdTokenEditText;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mExchangeCodeEditText = (EditText) findViewById(R.id.editTextExchangeCode);
mIdTokenEditText = (EditText) findViewById(R.id.editTextIdToken);
// initiate a credential object with drive and plus.login scopes
// cross identity is only available for tokens retrieved with plus.login
mCredential = GoogleAccountCredential.usingOAuth2(this, null);
// user needs to select an account, start account picker
startActivityForResult(
mCredential.newChooseAccountIntent(), REQUEST_ACCOUNT_PICKER);
}
/**
* Handles the callbacks from result returning
* account picker and permission requester activities.
*/
#Override
protected void onActivityResult(
final int requestCode, final int resultCode, final Intent data) {
switch (requestCode) {
// user has returned back from the account picker,
// initiate the rest of the flow with the account he/she has chosen.
case REQUEST_ACCOUNT_PICKER:
String accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
if (accountName != null) {
mCredential.setSelectedAccountName(accountName);
new RetrieveExchangeCodeAsyncTask().execute();
new RetrieveJwtAsyncTask().execute();
}
break;
// user has returned back from the permissions screen,
// if he/she has given enough permissions, retry the the request.
case REQUEST_AUTHORIZATION:
if (resultCode == Activity.RESULT_OK) {
// replay the same operations
new RetrieveExchangeCodeAsyncTask().execute();
new RetrieveJwtAsyncTask().execute();
}
break;
}
}
/**
* Retrieves the exchange code to be sent to the
* server-side component of the app.
*/
public class RetrieveExchangeCodeAsyncTask
extends AsyncTask<Void, Boolean, String> {
#Override
protected String doInBackground(Void... params) {
String scope = String.format("oauth2:server:client_id:%s:api_scope:%s",
CLIENT_ID, TextUtils.join(" ", SCOPES));
try {
return GoogleAuthUtil.getToken(
MainActivity.this, mCredential.getSelectedAccountName(), scope);
} catch (UserRecoverableAuthException e) {
startActivityForResult(e.getIntent(), REQUEST_AUTHORIZATION);
} catch (Exception e) {
e.printStackTrace(); // TODO: handle the exception
}
return null;
}
#Override
protected void onPostExecute(String code) {
// exchange code with server-side to retrieve an additional
// access token on the server-side.
Log.v("first One ","code 1 is: "+ code);
mExchangeCodeEditText.setText(code);
}
}
/**
* Retrieves a JWT to identify the user without the
* regular client-side authorization flow. The jwt payload needs to be
* sent to the server-side component.
*/
public class RetrieveJwtAsyncTask
extends AsyncTask<Void, Boolean, String> {
#Override
protected String doInBackground(Void... params) {
String scope = "audience:server:client_id:" + CLIENT_ID;
try {
return GoogleAuthUtil.getToken(
MainActivity.this, mCredential.getSelectedAccountName(), scope);
} catch(UserRecoverableAuthIOException e) {
startActivityForResult(e.getIntent(), REQUEST_AUTHORIZATION);
} catch (Exception e) {
e.printStackTrace(); // TODO: handle the exception
}
return null;
}
#Override
protected void onPostExecute(String idToken) {
// exchange encrypted idToken with server-side to identify the user
Log.v("Second One","2222"+ idToken);
mIdTokenEditText.setText(idToken);
}
}
private static final int REQUEST_ACCOUNT_PICKER = 100;
private static final int REQUEST_AUTHORIZATION = 200;
}
The above code gives me two codes:
1.One returned by RetrieveExchangeCodeAsyncTask - named code.
2.Second returned by RetrieveJwtAsyncTask class- named IdToken.
Now in the first place i am confused as to which one from the above do i need to send to my web server where it will be exchanged.
I tried using the first one(the one that starts as "4/....") for exchange at my server side but got A null pointer exception.
Also please specify what redirect URI i need to use.
Here is my server-side code for exchange:
package com.myAuthSample.tial;
import java.io.IOException;
import com.myAuthSample.tial.MyClass.CodeExchangeException;
import com.myAuthSample.tial.MyClass.NoRefreshTokenException;
public class MyMainDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
MyClass.getCredentials("4/...something...", "state"); //passed the retrieved authorization code
} catch (CodeExchangeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoRefreshTokenException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class MyClass {
// Path to client_secrets.json which should contain a JSON document such as:
// {
// "web": {
// "client_id": "[[YOUR_CLIENT_ID]]",
// "client_secret": "[[YOUR_CLIENT_SECRET]]",
// "auth_uri": "https://accounts.google.com/o/oauth2/auth",
// "token_uri": "https://accounts.google.com/o/oauth2/token"
// }
// }
private static final String CLIENTSECRETS_LOCATION = "/client_secrets_2.json";// client secrets of my android client
// private static final String REDIRECT_URI = "<YOUR_REGISTERED_REDIRECT_URI>";
private static String REDIRECT_URI ="";
private static final List<String> SCOPES = Arrays.asList(
"https://www.googleapis.com/auth/plus.login",
"https://www.googleapis.com/auth/youtube");
private static GoogleAuthorizationCodeFlow flow = null;
/**
* Exception thrown when an error occurred while retrieving credentials.
*/
public static class GetCredentialsException extends Exception {
protected String authorizationUrl;
/**
* Construct a GetCredentialsException.
*
* #param authorizationUrl The authorization URL to redirect the user to.
*/
public GetCredentialsException(String authorizationUrl) {
this.authorizationUrl = authorizationUrl;
}
/**
* Set the authorization URL.
*/
public void setAuthorizationUrl(String authorizationUrl) {
this.authorizationUrl = authorizationUrl;
}
/**
* #return the authorizationUrl
*/
public String getAuthorizationUrl() {
return authorizationUrl;
}
}
/**
* Exception thrown when a code exchange has failed.
*/
public static class CodeExchangeException extends GetCredentialsException {
/**
* Construct a CodeExchangeException.
*
* #param authorizationUrl The authorization URL to redirect the user to.
*/
public CodeExchangeException(String authorizationUrl) {
super(authorizationUrl);
}
}
/**
* Exception thrown when no refresh token has been found.
*/
public static class NoRefreshTokenException extends GetCredentialsException {
/**
* Construct a NoRefreshTokenException.
*
* #param authorizationUrl The authorization URL to redirect the user to.
*/
public NoRefreshTokenException(String authorizationUrl) {
super(authorizationUrl);
}
}
/**
* Exception thrown when no user ID could be retrieved.
*/
private static class NoUserIdException extends Exception {
}
/**
* Retrieved stored credentials for the provided user ID.
*
* #param userId User's ID.
* #return Stored Credential if found, {#code null} otherwise.
*/
static Credential getStoredCredentials(String userId) {
// TODO: Implement this method to work with your database. Instantiate a new
// Credential instance with stored accessToken and refreshToken.
throw new UnsupportedOperationException();
}
/**
* Store OAuth 2.0 credentials in the application's database.
*
* #param userId User's ID.
* #param credentials The OAuth 2.0 credentials to store.
*/
static void storeCredentials(String userId, Credential credentials) {
// TODO: Implement this method to work with your database.
// Store the credentials.getAccessToken() and credentials.getRefreshToken()
// string values in your database.
System.out.println("credentials are : " + credentials.toString());
throw new UnsupportedOperationException();
}
/**
* Build an authorization flow and store it as a static class attribute.
*
* #return GoogleAuthorizationCodeFlow instance.
* #throws IOException Unable to load client_secrets.json.
*/
static GoogleAuthorizationCodeFlow getFlow() throws IOException {
if (flow == null) {
HttpTransport httpTransport = new NetHttpTransport();
JacksonFactory jsonFactory = new JacksonFactory(); //...this was the original line....
// JsonFactory jsonFactory = new JacksonFactory();
//my code....
Reader clientSecretReader = new InputStreamReader(MyClass.class.getResourceAsStream(CLIENTSECRETS_LOCATION));
GoogleClientSecrets clientSecrets =
GoogleClientSecrets.load(jsonFactory,clientSecretReader);
REDIRECT_URI =clientSecrets.getDetails().getRedirectUris().get(0);
// my code ends...
/* GoogleClientSecrets clientSecrets =
GoogleClientSecrets.load(jsonFactory,
MyClass.class.getResourceAsStream(CLIENTSECRETS_LOCATION));
*/
flow =
new GoogleAuthorizationCodeFlow.Builder(httpTransport, jsonFactory, clientSecrets, SCOPES)
.setAccessType("offline").setApprovalPrompt("force").build();
}
return flow;
}
/**
* Exchange an authorization code for OAuth 2.0 credentials.
*
* #param authorizationCode Authorization code to exchange for OAuth 2.0
* credentials.
* #return OAuth 2.0 credentials.
* #throws CodeExchangeException An error occurred.
*/
static Credential exchangeCode(String authorizationCode)
throws CodeExchangeException {
try {
GoogleAuthorizationCodeFlow flow = getFlow();
GoogleTokenResponse response =
flow.newTokenRequest(authorizationCode).setRedirectUri(REDIRECT_URI).execute();
return flow.createAndStoreCredential(response, null);
} catch (IOException e) {
System.err.println("An error occurred: " + e);
throw new CodeExchangeException(null);
}
}
/**
* Send a request to the UserInfo API to retrieve the user's information.
*
* #param credentials OAuth 2.0 credentials to authorize the request.
* #return User's information.
* #throws NoUserIdException An error occurred.
*/
static Userinfo getUserInfo(Credential credentials)
throws NoUserIdException {
Oauth2 userInfoService =
new Oauth2.Builder(new NetHttpTransport(), new JacksonFactory(), credentials).build();
Userinfo userInfo = null;
try {
userInfo = userInfoService.userinfo().get().execute();
} catch (IOException e) {
System.err.println("An error occurred: " + e);
}
if (userInfo != null && userInfo.getId() != null) {
return userInfo;
} else {
throw new NoUserIdException();
}
}
/**
* Retrieve the authorization URL.
*
* #param emailAddress User's e-mail address.
* #param state State for the authorization URL.
* #return Authorization URL to redirect the user to.
* #throws IOException Unable to load client_secrets.json.
*/
public static String getAuthorizationUrl(String emailAddress, String state) throws IOException {
GoogleAuthorizationCodeRequestUrl urlBuilder =
getFlow().newAuthorizationUrl().setRedirectUri(REDIRECT_URI).setState(state);
urlBuilder.set("user_id", emailAddress);
return urlBuilder.build();
}
/**
* Retrieve credentials using the provided authorization code.
*
* This function exchanges the authorization code for an access token and
* queries the UserInfo API to retrieve the user's e-mail address. If a
* refresh token has been retrieved along with an access token, it is stored
* in the application database using the user's e-mail address as key. If no
* refresh token has been retrieved, the function checks in the application
* database for one and returns it if found or throws a NoRefreshTokenException
* with the authorization URL to redirect the user to.
*
* #param authorizationCode Authorization code to use to retrieve an access
* token.
* #param state State to set to the authorization URL in case of error.
* #return OAuth 2.0 credentials instance containing an access and refresh
* token.
* #throws NoRefreshTokenException No refresh token could be retrieved from
* the available sources.
* #throws IOException Unable to load client_secrets.json.
*/
public static Credential getCredentials(String authorizationCode, String state)
throws CodeExchangeException, NoRefreshTokenException, IOException {
String emailAddress = "";
try {
Credential credentials = exchangeCode(authorizationCode);
Userinfo userInfo = getUserInfo(credentials);
String userId = userInfo.getId();
emailAddress = userInfo.getEmail();
if (credentials.getRefreshToken() != null) {
storeCredentials(userId, credentials);
return credentials;
} else {
credentials = getStoredCredentials(userId);
if (credentials != null && credentials.getRefreshToken() != null) {
return credentials;
}
}
} catch (CodeExchangeException e) {
e.printStackTrace();
// Drive apps should try to retrieve the user and credentials for the current
// session.
// If none is available, redirect the user to the authorization URL.
e.setAuthorizationUrl(getAuthorizationUrl(emailAddress, state));
throw e;
} catch (NoUserIdException e) {
e.printStackTrace();
}
// No refresh token has been retrieved.
String authorizationUrl = getAuthorizationUrl(emailAddress, state);
throw new NoRefreshTokenException(authorizationUrl);
}
}
Also,am i passing the correct client_secret.json file in my server side code(in MyClass)--which is of android client.
Please help!!!
Thanx in advance.
Use from this:
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeTokenRequest;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.jackson.JacksonFactory;
private final val TRANSPORT: HttpTransport = new NetHttpTransport()
private final val JSON_FACTORY: JacksonFactory = new JacksonFactory()
GoogleTokenResponse tokenResponse = new GoogleAuthorizationCodeTokenRequest(TRANSPORT, JSON_FACTORY,
CLIENT_ID, CLIENT_SECRET, code, "postmessage").execute();
GoogleIdToken idToken = tokenResponse.parseIdToken();
String gplusId = idToken.getPayload().getSubject();
you must replace your values of the client_id,client_secret and code with above related variables.
more information is in the flowing link:
https://github.com/googleplus/gplus-quickstart-java/blob/master/src/com/google/plus/samples/quickstart/Signin.java
Also you can get the Google API libraries from this link:
http://repo1.maven.org/maven2/com/google/
You need to exchange the code for an access token indeed. The id_token is meant for your client only and self-contained so it does not need to be exchanged for anything else. The redirect_uri that you need to send along with the code in the exchange request to the token endpoint is the exact same redirect_uri value that you sent originally in the authorization request that went to the authorization endpoint (and that is registered for your client in the Google API Console), so looking at your code it is the one retrieved with clientSecrets.getDetails().getRedirectUris().get(0);
Currently I have some kind of contact form in my Android application.
I am using an intent similar to this question to launch the Android email client.
Intent i = new Intent(Intent.ACTION_SEND);
i.setType("message/rfc822");
i.putExtra(Intent.EXTRA_EMAIL, new String[] { "contact#mycompany.com" });
i.putExtra(android.content.Intent.EXTRA_SUBJECT, "[Contact us]");
The problem is that in the Android email app, the recipient email address (contact#mycompany.com) is editable, which is inappropriate in this case. So I need some way to prevent the user from changing or adding another email address.
Is it possible to put some extras in my intent in order to give clues to the email client that the recipient email address should not be editable (fixed)?
Not sure it's possible. Instead you can try sending an email directly from your app. Have a look at these implementations: 1
// Copyright (C) 2009 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.mail;
import com.google.gerrit.common.Version;
import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.util.TimeUtil;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.apache.commons.net.smtp.AuthSMTPClient;
import org.apache.commons.net.smtp.SMTPClient;
import org.apache.commons.net.smtp.SMTPReply;
import org.eclipse.jgit.lib.Config;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
/** Sends email via a nearby SMTP server. */
#Singleton
public class SmtpEmailSender implements EmailSender {
public static class Module extends AbstractModule {
#Override
protected void configure() {
bind(EmailSender.class).to(SmtpEmailSender.class);
}
}
public static enum Encryption {
NONE, SSL, TLS
}
private final boolean enabled;
private String smtpHost;
private int smtpPort;
private String smtpUser;
private String smtpPass;
private Encryption smtpEncryption;
private boolean sslVerify;
private Set<String> allowrcpt;
private String importance;
private int expiryDays;
#Inject
SmtpEmailSender(#GerritServerConfig final Config cfg) {
enabled = cfg.getBoolean("sendemail", null, "enable", true);
smtpHost = cfg.getString("sendemail", null, "smtpserver");
if (smtpHost == null) {
smtpHost = "127.0.0.1";
}
smtpEncryption =
ConfigUtil.getEnum(cfg, "sendemail", null, "smtpencryption",
Encryption.NONE);
sslVerify = cfg.getBoolean("sendemail", null, "sslverify", true);
final int defaultPort;
switch (smtpEncryption) {
case SSL:
defaultPort = 465;
break;
case NONE:
case TLS:
default:
defaultPort = 25;
break;
}
smtpPort = cfg.getInt("sendemail", null, "smtpserverport", defaultPort);
smtpUser = cfg.getString("sendemail", null, "smtpuser");
smtpPass = cfg.getString("sendemail", null, "smtppass");
Set<String> rcpt = new HashSet<String>();
for (String addr : cfg.getStringList("sendemail", null, "allowrcpt")) {
rcpt.add(addr);
}
allowrcpt = Collections.unmodifiableSet(rcpt);
importance = cfg.getString("sendemail", null, "importance");
expiryDays = cfg.getInt("sendemail", null, "expiryDays", 0);
}
#Override
public boolean isEnabled() {
return enabled;
}
#Override
public boolean canEmail(String address) {
if (!isEnabled()) {
return false;
}
if (allowrcpt.isEmpty()) {
return true;
}
if (allowrcpt.contains(address)) {
return true;
}
String domain = address.substring(address.lastIndexOf('#') + 1);
if (allowrcpt.contains(domain) || allowrcpt.contains("#" + domain)) {
return true;
}
return false;
}
#Override
public void send(final Address from, Collection<Address> rcpt,
final Map<String, EmailHeader> callerHeaders, final String body)
throws EmailException {
if (!isEnabled()) {
throw new EmailException("Sending email is disabled");
}
final Map<String, EmailHeader> hdrs =
new LinkedHashMap<String, EmailHeader>(callerHeaders);
setMissingHeader(hdrs, "MIME-Version", "1.0");
setMissingHeader(hdrs, "Content-Type", "text/plain; charset=UTF-8");
setMissingHeader(hdrs, "Content-Transfer-Encoding", "8bit");
setMissingHeader(hdrs, "Content-Disposition", "inline");
setMissingHeader(hdrs, "User-Agent", "Gerrit/" + Version.getVersion());
if(importance != null) {
setMissingHeader(hdrs, "Importance", importance);
}
if(expiryDays > 0) {
Date expiry = new Date(TimeUtil.nowMs() +
expiryDays * 24 * 60 * 60 * 1000L );
setMissingHeader(hdrs, "Expiry-Date",
new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z").format(expiry));
}
StringBuffer rejected = new StringBuffer();
try {
final SMTPClient client = open();
try {
if (!client.setSender(from.email)) {
throw new EmailException("Server " + smtpHost
+ " rejected from address " + from.email);
}
/* Do not prevent the email from being sent to "good" users simply
* because some users get rejected. If not, a single rejected
* project watcher could prevent email for most actions on a project
* from being sent to any user! Instead, queue up the errors, and
* throw an exception after sending the email to get the rejected
* error(s) logged.
*/
for (Address addr : rcpt) {
if (!client.addRecipient(addr.email)) {
String error = client.getReplyString();
rejected.append("Server ").append(smtpHost)
.append(" rejected recipient ").append(addr)
.append(": ").append(error);
}
}
Writer w = client.sendMessageData();
if (w == null) {
/* Include rejected recipient error messages here to not lose that
* information. That piece of the puzzle is vital if zero recipients
* are accepted and the server consequently rejects the DATA command.
*/
throw new EmailException(rejected + "Server " + smtpHost
+ " rejected DATA command: " + client.getReplyString());
}
w = new BufferedWriter(w);
for (Map.Entry<String, EmailHeader> h : hdrs.entrySet()) {
if (!h.getValue().isEmpty()) {
w.write(h.getKey());
w.write(": ");
h.getValue().write(w);
w.write("\r\n");
}
}
w.write("\r\n");
w.write(body);
w.flush();
w.close();
if (!client.completePendingCommand()) {
throw new EmailException("Server " + smtpHost
+ " rejected message body: " + client.getReplyString());
}
client.logout();
if (rejected.length() > 0) {
throw new EmailException(rejected.toString());
}
} finally {
client.disconnect();
}
} catch (IOException e) {
throw new EmailException("Cannot send outgoing email", e);
}
}
private void setMissingHeader(final Map<String, EmailHeader> hdrs,
final String name, final String value) {
if (!hdrs.containsKey(name) || hdrs.get(name).isEmpty()) {
hdrs.put(name, new EmailHeader.String(value));
}
}
private SMTPClient open() throws EmailException {
final AuthSMTPClient client = new AuthSMTPClient("UTF-8");
if (smtpEncryption == Encryption.SSL) {
client.enableSSL(sslVerify);
}
try {
client.connect(smtpHost, smtpPort);
if (!SMTPReply.isPositiveCompletion(client.getReplyCode())) {
throw new EmailException("SMTP server rejected connection");
}
if (!client.login()) {
String e = client.getReplyString();
throw new EmailException(
"SMTP server rejected HELO/EHLO greeting: " + e);
}
if (smtpEncryption == Encryption.TLS) {
if (!client.startTLS(smtpHost, smtpPort, sslVerify)) {
throw new EmailException("SMTP server does not support TLS");
}
if (!client.login()) {
String e = client.getReplyString();
throw new EmailException("SMTP server rejected login: " + e);
}
}
if (smtpUser != null && !client.auth(smtpUser, smtpPass)) {
String e = client.getReplyString();
throw new EmailException("SMTP server rejected auth: " + e);
}
} catch (IOException e) {
if (client.isConnected()) {
try {
client.disconnect();
} catch (IOException e2) {
}
}
throw new EmailException(e.getMessage(), e);
} catch (EmailException e) {
if (client.isConnected()) {
try {
client.disconnect();
} catch (IOException e2) {
}
}
throw e;
}
return client;
}
}
I'm not sure about your question but you can use intent like this....
Intent intent = new Intent(this,BirdsActivity.class);
intent.putExtra("Status","fixed");
startActivity(intent);
Bundle b = getIntent().getExtras();
int status = b.getString("Status");
//do whatever you want with status...
I think this will help you. you have tom write this proprty in your EditText
EditText.getFreezesText()
To authenticate a request, I use Authenticator.setDefault
which is VM wide...
What If I want to separate different webservices
and each one are aware of their authentication credentials.
Do I need to Authenticator.setDefault for each request ?
This may not work if there are concurrent connection with mixed webservices...
Building on Mike's response above I have the following solution, because while I much appreciate the general idea (that's why I've copied it ;-) , I see a few problems with it:
Mike's solution will throw a NullPointerException if the JDK requests the authentication via one of the two static request methods in java.net.Authenticator that do not pass the URL (then getRequestingURL() will return null).
It requires you to pass in an external regex pattern that deconstructs the URL. This is (very) easy to get wrong, and the URL class in the JDK implements this parsing, so I prefer to use that.
It requires that some external class builds the map of PasswordAuthentication objects, and then sets it. It does not implement a registration mechanism that other components in your system can use. I've also turned it into a singleton.
More of a style thing: I don't recommend duplicating class names (Authenticator), so I've renamed it DefaultAuthenticator.
Below solution I think solves these issues.
/**
* Authenticator which keeps credentials to be passed to the requestor based on authority of the requesting URL. The
* authority is <pre>user:password#host:port</pre>, where all parts are optional except the host.
* <p>
* If the configured credentials are not found, the Authenticator will use the credentials embedded in the URL, if
* present. Embedded credentials are in the form of <pre>user:password#host:port</pre>
*
* #author Michael Fortin 2011-09-23
*/
public final class DefaultAuthenticator extends Authenticator {
private static final Logger LOG = Logger.getLogger(DefaultAuthenticator.class.getName());
private static DefaultAuthenticator instance;
private Map<String, PasswordAuthentication> authInfo = new HashMap<String, PasswordAuthentication>();
private DefaultAuthenticator() {
}
public static synchronized DefaultAuthenticator getInstance() {
if (instance == null) {
instance = new DefaultAuthenticator();
Authenticator.setDefault(instance);
}
return instance;
}
// unit testing
static void reset() {
instance = null;
Authenticator.setDefault(null);
}
#Override
protected PasswordAuthentication getPasswordAuthentication() {
String requestorInfo = getRequestorInfo();
LOG.info(getRequestorType() + " at \"" + getRequestingPrompt() + "\" is requesting " + getRequestingScheme()
+ " password authentication for \"" + requestorInfo + "\"");
if (authInfo.containsKey(requestorInfo)) {
return authInfo.get(requestorInfo);
} else {
PasswordAuthentication pa = getEmbeddedCredentials(getRequestingURL());
if (pa == null) {
LOG.warning("No authentication information");
}
return pa;
}
}
/**
* Register the authentication information for a given URL.
*
* #param url - the URL that will request authorization
* #param auth - the {#link PasswordAuthentication} for this URL providing the credentials
*/
public void register(URL url, PasswordAuthentication auth) {
String requestorInfo = getRequestorInfo(url.getHost(), url.getPort());
authInfo.put(requestorInfo, auth);
}
/**
* Get the requestor info based on info provided.
*
* #param host - hostname of requestor
* #param port - TCP/IP port
* #return requestor info string
*/
private String getRequestorInfo(String host, int port) {
String fullHostname;
try {
InetAddress addr = InetAddress.getByName(host);
fullHostname = addr.getCanonicalHostName();
} catch (UnknownHostException e) {
fullHostname = host;
}
if (port == -1) {
return fullHostname;
} else {
return fullHostname + ":" + port;
}
}
/**
* Get the requestor info for the request currently being processed by this Authenticator.
*
* #return requestor info string for current request
*/
private String getRequestorInfo() {
String host;
InetAddress addr = getRequestingSite();
if (addr == null) {
host = getRequestingHost();
} else {
host = addr.getCanonicalHostName();
}
return getRequestorInfo(host, getRequestingPort());
}
/**
* Get the credentials from the requesting URL.
*
* #param url - URL to get the credentials from (can be null, method will return null)
* #return PasswordAuthentication with credentials from URL or null if URL contains no credentials or if URL is
* null itself
*/
PasswordAuthentication getEmbeddedCredentials(URL url) {
if (url == null) {
return null;
}
String userInfo = url.getUserInfo();
int colon = userInfo == null ? -1 : userInfo.indexOf(":");
if (colon == -1) {
return null;
} else {
String userName = userInfo.substring(0, colon);
String pass = userInfo.substring(colon + 1);
return new PasswordAuthentication(userName, pass.toCharArray());
}
}
}
While I'm at it, let me give you my unit tests (JUnit 4).
/**
* #author Paul Balm - May 10 2012
*/
public class DefaultAuthenticatorTest {
private static final Logger LOG = Logger.getLogger(DefaultAuthenticatorTest.class.getName());
#Before
public void setUp() throws Exception {
DefaultAuthenticator.reset();
DefaultAuthenticator.getInstance();
}
#After
public void tearDown() {
DefaultAuthenticator.reset();
}
#Test
public void testRequestAuthenticationFromURL() throws MalformedURLException, UnknownHostException {
Map<String, String[]> urls = generateURLs();
for (String urlStr : urls.keySet()) {
String[] userInfo = urls.get(urlStr);
LOG.info("Testing: " + urlStr);
URL url = new URL(urlStr);
request(userInfo[1], userInfo[2], url, true);
}
}
#Test
public void testRequestAuthenticationRegistered() throws UnknownHostException, MalformedURLException {
Map<String, String[]> urls = generateURLs();
for (String urlStr : urls.keySet()) {
String[] userInfo = urls.get(urlStr);
LOG.info("Testing: " + urlStr);
URL url = new URL(urlStr);
DefaultAuthenticator.reset();
DefaultAuthenticator auth = DefaultAuthenticator.getInstance();
String userName = userInfo[1];
String password = userInfo[2];
if (password != null) {
// You can't register a null password
auth.register(url, new PasswordAuthentication(userName, password.toCharArray()));
}
request(userName, password, url, false);
}
}
/**
* Generate a bunch of URLs mapped to String array. The String array has the following elements:
* - user info part of URL,
* - expected user,
* - expected password
*
* Note that the keys of the maps must be strings and not URL objects, because of the way URL.equals is
* implemented. This method does not consider the credentials.
*
* #throws MalformedURLException
*/
Map<String, String[]> generateURLs() {
String[] hosts = new String[]{ "127.0.0.1", "localhost.localdomain"};
List<String[]> userData = new ArrayList<String[]>();
// normal cases
userData.add(new String[] { "user:pass#", "user", "pass" }); // results in: http://user:pass#[host]
userData.add(new String[] { "", null, null });
// unexpected cases
userData.add(new String[] { "#", null, null });
userData.add(new String[] { ":#", "", "" });
userData.add(new String[] { "user:#", "user", "" });
userData.add(new String[] { ":pass#", "", "pass" });
Map<String, String[]> urls = new HashMap<String, String[]>();
for (String[] userInfo : userData) {
for (String host : hosts) {
String s = "http://" + userInfo[0] + host;
urls.put(s, userInfo);
}
}
LOG.info("" + urls.size() + " URLs prepared");
return urls;
}
private void request(String expectedUser, String expectedPass, URL url, boolean inURL)
throws UnknownHostException {
String host = url.getHost();
InetAddress addr = InetAddress.getAllByName(host)[0];
int port = url.getPort();
String protocol = url.getProtocol();
String prompt = ""; // prompt for the user when asking for the credentials
String scheme = "basic"; // or digest
RequestorType reqType = RequestorType.SERVER;
PasswordAuthentication credentials =
Authenticator.requestPasswordAuthentication(addr, port, protocol, prompt, scheme);
// If the credentials are in the URL, you can't find them using this method because we're not passing the URL
checkCredentials(url, inURL ? null : expectedUser, inURL ? null : expectedPass, credentials);
credentials = Authenticator.requestPasswordAuthentication(host, addr, port, protocol, prompt, scheme);
// If the credentials are in the URL, you can't find them using this method because we're not passing the URL
checkCredentials(url, inURL ? null : expectedUser, inURL ? null : expectedPass, credentials);
credentials = Authenticator.requestPasswordAuthentication(host, addr, port, protocol, prompt, scheme, url, reqType);
checkCredentials(url, expectedUser, expectedPass, credentials);
}
private void checkCredentials(URL url, String expectedUser, String expectedPass, PasswordAuthentication credentials) {
if (expectedUser == null) {
Assert.assertNull(url.toString(), credentials);
} else {
Assert.assertNotNull(url.toString(), credentials);
Assert.assertEquals(url.toString(), expectedUser, credentials.getUserName());
if (expectedPass == null) {
Assert.assertNull(url.toString(), credentials.getPassword());
} else {
Assert.assertArrayEquals(url.toString(), expectedPass.toCharArray(), credentials.getPassword());
}
}
}
}
Here's the solution I've implemented and it works like a charm!
import java.net.*;
import java.util.*;
import java.util.logging.*;
import java.util.regex.*;
/**
* Authenticator which keeps credentials to be passed to the requester
* based on the concatenation of the authority and the URL that requires
* authentication.
*
* If the configured credentials are not found, the Authenticator will
* use the embedded credentials if present.
*
* Embedded credentials are in the form of <pre><b>user</b>:<b>password</b><i>#host:port/<url-path></i></pre>
*
* #author Michael Fortin 2011-09-23
*/
public class Authenticator extends java.net.Authenticator {
private Logger log = Logger.getLogger(this.getClass().getName());
private Map<String, PasswordAuthentication> authInfos;
private Pattern embeddedAuthInfoPattern;
#Override
protected PasswordAuthentication getPasswordAuthentication() {
String requesterInfo = String.format("%s%s", getRequestingURL().getAuthority(), getRequestingURL().getPath());
log.fine(String.format("%s at \"%s\" is requesting %s password authentication for \"%s\"", getRequestorType(), getRequestingPrompt(), getRequestingScheme(), requesterInfo));
PasswordAuthentication pa = null;
if ((pa = authInfos.get(requesterInfo)) == null && (pa = getEmbeddedPA(getRequestingURL().getAuthority())) == null) {
log.warning(String.format("No authentication information for \"%s\"", requesterInfo));
}
return pa;
}
public void setAuthInfos(Map<String, PasswordAuthentication> authInfos) {
this.authInfos = authInfos;
}
public void setEmbeddedAuthInfoPattern(String pattern) {
this.embeddedAuthInfoPattern = Pattern.compile(pattern);
}
private PasswordAuthentication getEmbeddedPA(String authInfo) {
if (authInfo != null) {
Matcher matcher = embeddedAuthInfoPattern.matcher(authInfo);
if (matcher.find()) {
return new PasswordAuthentication(matcher.group(1), matcher.group(2).toCharArray());
}
}
return null;
}
}
Lack of answers tends to mean that nobody knows, which tells me there isn't an answer.
I've been wondering the same thing, and I think the answer is that it can't be done through java.net. I think you either need to limit your http accesses to one server at a time, or look into other packages such as org.apache.http.client.