mirror of
https://github.com/fergalmoran/chrometophone.git
synced 2025-12-30 13:39:57 +00:00
Handle selection
This commit is contained in:
@@ -45,7 +45,7 @@ import com.google.appengine.api.labs.taskqueue.TaskOptions;
|
|||||||
public class C2DMessaging {
|
public class C2DMessaging {
|
||||||
private static final String UPDATE_CLIENT_AUTH = "Update-Client-Auth";
|
private static final String UPDATE_CLIENT_AUTH = "Update-Client-Auth";
|
||||||
|
|
||||||
public static final String DATAMESSAGING_SEND_ENDPOINT =
|
public static final String DATAMESSAGING_SEND_ENDPOINT =
|
||||||
"https://android.clients.google.com/c2dm/send";
|
"https://android.clients.google.com/c2dm/send";
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(C2DMessaging.class.getName());
|
private static final Logger log = Logger.getLogger(C2DMessaging.class.getName());
|
||||||
@@ -53,11 +53,11 @@ public class C2DMessaging {
|
|||||||
public static final String PARAM_REGISTRATION_ID = "registration_id";
|
public static final String PARAM_REGISTRATION_ID = "registration_id";
|
||||||
|
|
||||||
public static final String PARAM_DELAY_WHILE_IDLE = "delay_while_idle";
|
public static final String PARAM_DELAY_WHILE_IDLE = "delay_while_idle";
|
||||||
|
|
||||||
public static final String PARAM_COLLAPSE_KEY = "collapse_key";
|
public static final String PARAM_COLLAPSE_KEY = "collapse_key";
|
||||||
|
|
||||||
private static final String UTF8 = "UTF-8";
|
private static final String UTF8 = "UTF-8";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Jitter - random interval to wait before retry.
|
* Jitter - random interval to wait before retry.
|
||||||
*/
|
*/
|
||||||
@@ -66,11 +66,11 @@ public class C2DMessaging {
|
|||||||
static C2DMessaging singleton;
|
static C2DMessaging singleton;
|
||||||
|
|
||||||
final C2DMConfigLoader serverConfig;
|
final C2DMConfigLoader serverConfig;
|
||||||
|
|
||||||
private C2DMessaging(C2DMConfigLoader serverConfig) {
|
private C2DMessaging(C2DMConfigLoader serverConfig) {
|
||||||
this.serverConfig = serverConfig;
|
this.serverConfig = serverConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized static C2DMessaging get(ServletContext servletContext) {
|
public synchronized static C2DMessaging get(ServletContext servletContext) {
|
||||||
if (singleton == null) {
|
if (singleton == null) {
|
||||||
C2DMConfigLoader serverConfig = new C2DMConfigLoader(getPMF(servletContext));
|
C2DMConfigLoader serverConfig = new C2DMConfigLoader(getPMF(servletContext));
|
||||||
@@ -78,7 +78,7 @@ public class C2DMessaging {
|
|||||||
}
|
}
|
||||||
return singleton;
|
return singleton;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized static C2DMessaging get(PersistenceManagerFactory pmf) {
|
public synchronized static C2DMessaging get(PersistenceManagerFactory pmf) {
|
||||||
if (singleton == null) {
|
if (singleton == null) {
|
||||||
C2DMConfigLoader serverConfig = new C2DMConfigLoader(pmf);
|
C2DMConfigLoader serverConfig = new C2DMConfigLoader(pmf);
|
||||||
@@ -86,18 +86,18 @@ public class C2DMessaging {
|
|||||||
}
|
}
|
||||||
return singleton;
|
return singleton;
|
||||||
}
|
}
|
||||||
|
|
||||||
C2DMConfigLoader getServerConfig() {
|
C2DMConfigLoader getServerConfig() {
|
||||||
return serverConfig;
|
return serverConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize PMF - we use a context attribute, so other servlets can
|
* Initialize PMF - we use a context attribute, so other servlets can
|
||||||
* be share the same instance. This is similar with a shared static
|
* be share the same instance. This is similar with a shared static
|
||||||
* field, but avoids dependencies.
|
* field, but avoids dependencies.
|
||||||
*/
|
*/
|
||||||
public static PersistenceManagerFactory getPMF(ServletContext ctx) {
|
public static PersistenceManagerFactory getPMF(ServletContext ctx) {
|
||||||
PersistenceManagerFactory pmfFactory =
|
PersistenceManagerFactory pmfFactory =
|
||||||
(PersistenceManagerFactory) ctx.getAttribute(
|
(PersistenceManagerFactory) ctx.getAttribute(
|
||||||
PersistenceManagerFactory.class.getName());
|
PersistenceManagerFactory.class.getName());
|
||||||
if (pmfFactory == null) {
|
if (pmfFactory == null) {
|
||||||
@@ -110,13 +110,13 @@ public class C2DMessaging {
|
|||||||
return pmfFactory;
|
return pmfFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean sendNoRetry(String registrationId,
|
public boolean sendNoRetry(String registrationId,
|
||||||
String collapse,
|
String collapse,
|
||||||
Map<String, String[]> params,
|
Map<String, String[]> params,
|
||||||
boolean delayWhileIdle)
|
boolean delayWhileIdle)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
// Send a sync message to this Android device.
|
// Send a sync message to this Android device.
|
||||||
StringBuilder postDataBuilder = new StringBuilder();
|
StringBuilder postDataBuilder = new StringBuilder();
|
||||||
postDataBuilder.append(PARAM_REGISTRATION_ID).
|
postDataBuilder.append(PARAM_REGISTRATION_ID).
|
||||||
@@ -126,13 +126,13 @@ public class C2DMessaging {
|
|||||||
postDataBuilder.append("&")
|
postDataBuilder.append("&")
|
||||||
.append(PARAM_DELAY_WHILE_IDLE).append("=1");
|
.append(PARAM_DELAY_WHILE_IDLE).append("=1");
|
||||||
}
|
}
|
||||||
postDataBuilder.append("&").append(PARAM_COLLAPSE_KEY).append("=").
|
postDataBuilder.append("&").append(PARAM_COLLAPSE_KEY).append("=").
|
||||||
append(collapse);
|
append(collapse);
|
||||||
|
|
||||||
for (Object keyObj: params.keySet()) {
|
for (Object keyObj: params.keySet()) {
|
||||||
String key = (String) keyObj;
|
String key = (String) keyObj;
|
||||||
if (key.startsWith("data.")) {
|
if (key.startsWith("data.")) {
|
||||||
String[] values = (String[]) params.get(key);
|
String[] values = params.get(key);
|
||||||
postDataBuilder.append("&").append(key).append("=").
|
postDataBuilder.append("&").append(key).append("=").
|
||||||
append(URLEncoder.encode(values[0], UTF8));
|
append(URLEncoder.encode(values[0], UTF8));
|
||||||
}
|
}
|
||||||
@@ -142,7 +142,7 @@ public class C2DMessaging {
|
|||||||
|
|
||||||
// Hit the dm URL.
|
// Hit the dm URL.
|
||||||
URL url = new URL(DATAMESSAGING_SEND_ENDPOINT);
|
URL url = new URL(DATAMESSAGING_SEND_ENDPOINT);
|
||||||
|
|
||||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||||
conn.setDoOutput(true);
|
conn.setDoOutput(true);
|
||||||
conn.setUseCaches(false);
|
conn.setUseCaches(false);
|
||||||
@@ -155,20 +155,20 @@ public class C2DMessaging {
|
|||||||
OutputStream out = conn.getOutputStream();
|
OutputStream out = conn.getOutputStream();
|
||||||
out.write(postData);
|
out.write(postData);
|
||||||
out.close();
|
out.close();
|
||||||
|
|
||||||
int responseCode = conn.getResponseCode();
|
int responseCode = conn.getResponseCode();
|
||||||
|
|
||||||
if (responseCode == HttpServletResponse.SC_UNAUTHORIZED ||
|
if (responseCode == HttpServletResponse.SC_UNAUTHORIZED ||
|
||||||
responseCode == HttpServletResponse.SC_FORBIDDEN) {
|
responseCode == HttpServletResponse.SC_FORBIDDEN) {
|
||||||
// The token is too old - return false to retry later, will fetch the token
|
// The token is too old - return false to retry later, will fetch the token
|
||||||
// from DB. This happens if the password is changed or token expires. Either admin
|
// from DB. This happens if the password is changed or token expires. Either admin
|
||||||
// is updating the token, or Update-Client-Auth was received by another server,
|
// is updating the token, or Update-Client-Auth was received by another server,
|
||||||
// and next retry will get the good one from database.
|
// and next retry will get the good one from database.
|
||||||
log.warning("Unauthorized - need token");
|
log.warning("Unauthorized - need token");
|
||||||
serverConfig.invalidateCachedToken();
|
serverConfig.invalidateCachedToken();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for updated token header
|
// Check for updated token header
|
||||||
String updatedAuthToken = conn.getHeaderField(UPDATE_CLIENT_AUTH);
|
String updatedAuthToken = conn.getHeaderField(UPDATE_CLIENT_AUTH);
|
||||||
if (updatedAuthToken != null && !authToken.equals(updatedAuthToken)) {
|
if (updatedAuthToken != null && !authToken.equals(updatedAuthToken)) {
|
||||||
@@ -176,25 +176,25 @@ public class C2DMessaging {
|
|||||||
updatedAuthToken);
|
updatedAuthToken);
|
||||||
serverConfig.updateToken(updatedAuthToken);
|
serverConfig.updateToken(updatedAuthToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
String responseLine = new BufferedReader(new InputStreamReader(conn.getInputStream()))
|
String responseLine = new BufferedReader(new InputStreamReader(conn.getInputStream()))
|
||||||
.readLine();
|
.readLine();
|
||||||
|
|
||||||
// NOTE: You *MUST* use exponential backoff if you receive a 503 response code.
|
// NOTE: You *MUST* use exponential backoff if you receive a 503 response code.
|
||||||
// Since App Engine's task queue mechanism automatically does this for tasks that
|
// Since App Engine's task queue mechanism automatically does this for tasks that
|
||||||
// return non-success error codes, this is not explicitly implemented here.
|
// return non-success error codes, this is not explicitly implemented here.
|
||||||
// If we weren't using App Engine, we'd need to manually implement this.
|
// If we weren't using App Engine, we'd need to manually implement this.
|
||||||
log.info("Got " + responseCode + " response from Google datamessaging endpoint.");
|
log.info("Got " + responseCode + " response from Google datamessaging endpoint.");
|
||||||
|
|
||||||
if (responseLine == null || responseLine.equals("")) {
|
if (responseLine == null || responseLine.equals("")) {
|
||||||
throw new IOException("Got empty response from Google datamessaging endpoint.");
|
throw new IOException("Got empty response from Google datamessaging endpoint.");
|
||||||
}
|
}
|
||||||
|
|
||||||
String[] responseParts = responseLine.split("=", 2);
|
String[] responseParts = responseLine.split("=", 2);
|
||||||
if (responseParts.length != 2) {
|
if (responseParts.length != 2) {
|
||||||
log.warning("Invalid message from google: " +
|
log.warning("Invalid message from google: " +
|
||||||
responseCode + " " + responseLine);
|
responseCode + " " + responseLine);
|
||||||
throw new IOException("Invalid response from Google " +
|
throw new IOException("Invalid response from Google " +
|
||||||
responseCode + " " + responseLine);
|
responseCode + " " + responseLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,16 +202,16 @@ public class C2DMessaging {
|
|||||||
log.info("Successfully sent data message to device: " + responseLine);
|
log.info("Successfully sent data message to device: " + responseLine);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (responseParts[0].equals("Error")) {
|
if (responseParts[0].equals("Error")) {
|
||||||
String err = responseParts[1];
|
String err = responseParts[1];
|
||||||
log.warning("Got error response from Google datamessaging endpoint: " + err);
|
log.warning("Got error response from Google datamessaging endpoint: " + err);
|
||||||
// No retry.
|
// No retry.
|
||||||
// TODO(costin): show a nicer error to the user.
|
// TODO(costin): show a nicer error to the user.
|
||||||
throw new IOException("Server error: " + err);
|
throw new IOException("Server error: " + err);
|
||||||
} else {
|
} else {
|
||||||
// 500 or unparseable response - server error, needs to retry
|
// 500 or unparseable response - server error, needs to retry
|
||||||
log.warning("Invalid response from google " + responseLine + " " +
|
log.warning("Invalid response from google " + responseLine + " " +
|
||||||
responseCode);
|
responseCode);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -219,44 +219,48 @@ public class C2DMessaging {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper method to send a message, with 2 parameters.
|
* Helper method to send a message, with 2 parameters.
|
||||||
*
|
*
|
||||||
* Permanent errors will result in IOException.
|
* Permanent errors will result in IOException.
|
||||||
* Retriable errors will cause the message to be scheduled for retry.
|
* Retriable errors will cause the message to be scheduled for retry.
|
||||||
*/
|
*/
|
||||||
public void sendWithRetry(String token, String collapseKey,
|
public void sendWithRetry(String token, String collapseKey,
|
||||||
String name1, String value1, String name2, String value2)
|
String name1, String value1, String name2, String value2,
|
||||||
|
String name3, String value3)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
Map<String, String[]> params = new HashMap<String, String[]>();
|
Map<String, String[]> params = new HashMap<String, String[]>();
|
||||||
params.put("data." + name1, new String[] {value1});
|
if (value1 != null) params.put("data." + name1, new String[] {value1});
|
||||||
params.put("data." + name2, new String[] {value2});
|
if (value2 != null) params.put("data." + name2, new String[] {value2});
|
||||||
|
if (value3 != null) params.put("data." + name3, new String[] {value3});
|
||||||
|
|
||||||
boolean sentOk = sendNoRetry(token, collapseKey, params, true);
|
boolean sentOk = sendNoRetry(token, collapseKey, params, true);
|
||||||
if (!sentOk) {
|
if (!sentOk) {
|
||||||
retry(token, collapseKey, params, true);
|
retry(token, collapseKey, params, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean sendNoRetry(String token, String collapseKey,
|
public boolean sendNoRetry(String token, String collapseKey,
|
||||||
String name1, String value1, String name2, String value2)
|
String name1, String value1, String name2, String value2,
|
||||||
|
String name3, String value3)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
Map<String, String[]> params = new HashMap<String, String[]>();
|
Map<String, String[]> params = new HashMap<String, String[]>();
|
||||||
params.put("data." + name1, new String[] {value1});
|
if (value1 != null) params.put("data." + name1, new String[] {value1});
|
||||||
params.put("data." + name2, new String[] {value2});
|
if (value2 != null) params.put("data." + name2, new String[] {value2});
|
||||||
|
if (value3 != null) params.put("data." + name3, new String[] {value3});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return sendNoRetry(token, collapseKey, params, true);
|
return sendNoRetry(token, collapseKey, params, true);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void retry(String token, String collapseKey,
|
private void retry(String token, String collapseKey,
|
||||||
Map<String, String[]> params, boolean delayWhileIdle) {
|
Map<String, String[]> params, boolean delayWhileIdle) {
|
||||||
Queue dmQueue = QueueFactory.getQueue("c2dm");
|
Queue dmQueue = QueueFactory.getQueue("c2dm");
|
||||||
try {
|
try {
|
||||||
TaskOptions url =
|
TaskOptions url =
|
||||||
TaskOptions.Builder.url(C2DMRetryServlet.URI)
|
TaskOptions.Builder.url(C2DMRetryServlet.URI)
|
||||||
.param(C2DMessaging.PARAM_REGISTRATION_ID, token)
|
.param(C2DMessaging.PARAM_REGISTRATION_ID, token)
|
||||||
.param(C2DMessaging.PARAM_COLLAPSE_KEY, collapseKey);
|
.param(C2DMessaging.PARAM_COLLAPSE_KEY, collapseKey);
|
||||||
@@ -264,20 +268,20 @@ public class C2DMessaging {
|
|||||||
url.param(PARAM_DELAY_WHILE_IDLE, "1");
|
url.param(PARAM_DELAY_WHILE_IDLE, "1");
|
||||||
}
|
}
|
||||||
for (String key: params.keySet()) {
|
for (String key: params.keySet()) {
|
||||||
String[] values = (String[]) params.get(key);
|
String[] values = params.get(key);
|
||||||
url.param(key, URLEncoder.encode(values[0], UTF8));
|
url.param(key, URLEncoder.encode(values[0], UTF8));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Task queue implements the exponential backoff
|
// Task queue implements the exponential backoff
|
||||||
long jitter = (int) Math.random() * DATAMESSAGING_MAX_JITTER_MSEC;
|
long jitter = (int) Math.random() * DATAMESSAGING_MAX_JITTER_MSEC;
|
||||||
url.countdownMillis(jitter);
|
url.countdownMillis(jitter);
|
||||||
|
|
||||||
TaskHandle add = dmQueue.add(url);
|
TaskHandle add = dmQueue.add(url);
|
||||||
} catch (UnsupportedEncodingException e) {
|
} catch (UnsupportedEncodingException e) {
|
||||||
// Ignore - UTF8 should be supported
|
// Ignore - UTF8 should be supported
|
||||||
log.log(Level.SEVERE, "Unexpected error", e);
|
log.log(Level.SEVERE, "Unexpected error", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,15 +40,17 @@ public class SendServlet extends HttpServlet {
|
|||||||
private static final String OK_STATUS = "OK";
|
private static final String OK_STATUS = "OK";
|
||||||
private static final String ERROR_STATUS = "ERROR";
|
private static final String ERROR_STATUS = "ERROR";
|
||||||
|
|
||||||
|
@Override
|
||||||
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||||
doGet(req, resp);
|
doGet(req, resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||||
resp.setContentType("text/plain");
|
resp.setContentType("text/plain");
|
||||||
String url = req.getParameter("url");
|
String url = req.getParameter("url");
|
||||||
String title = req.getParameter("title");
|
String title = req.getParameter("title");
|
||||||
|
String sel = req.getParameter("sel"); // optional
|
||||||
if (url == null && title == null) {
|
if (url == null && title == null) {
|
||||||
resp.setStatus(400);
|
resp.setStatus(400);
|
||||||
resp.getWriter().println(ERROR_STATUS + " (Must specify url and title parameters)");
|
resp.getWriter().println(ERROR_STATUS + " (Must specify url and title parameters)");
|
||||||
@@ -58,17 +60,18 @@ public class SendServlet extends HttpServlet {
|
|||||||
UserService userService = UserServiceFactory.getUserService();
|
UserService userService = UserServiceFactory.getUserService();
|
||||||
User user = userService.getCurrentUser();
|
User user = userService.getCurrentUser();
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
doSendToPhone(url, title, user.getEmail(), resp);
|
doSendToPhone(url, title, sel, user.getEmail(), resp);
|
||||||
} else {
|
} else {
|
||||||
String followOnURL = req.getRequestURI() + "?title=" +
|
String followOnURL = req.getRequestURI() + "?title=" +
|
||||||
URLEncoder.encode(req.getParameter("title"), "UTF-8") +
|
URLEncoder.encode(req.getParameter("title"), "UTF-8") +
|
||||||
"&url=" + URLEncoder.encode(req.getParameter("url"), "UTF-8");
|
"&url=" + URLEncoder.encode(req.getParameter("url"), "UTF-8") +
|
||||||
|
"&sel=" + URLEncoder.encode(req.getParameter("sel"), "UTF-8");
|
||||||
resp.sendRedirect(userService.createLoginURL(followOnURL));
|
resp.sendRedirect(userService.createLoginURL(followOnURL));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean doSendToPhone(String url, String title, String userAccount,
|
private boolean doSendToPhone(String url, String title, String sel,
|
||||||
HttpServletResponse resp) throws IOException {
|
String userAccount, HttpServletResponse resp) throws IOException {
|
||||||
// Get device info
|
// Get device info
|
||||||
DeviceInfo deviceInfo = null;
|
DeviceInfo deviceInfo = null;
|
||||||
// Shared PMF
|
// Shared PMF
|
||||||
@@ -91,7 +94,8 @@ public class SendServlet extends HttpServlet {
|
|||||||
// Send push message to phone
|
// Send push message to phone
|
||||||
C2DMessaging push = C2DMessaging.get(getServletContext());
|
C2DMessaging push = C2DMessaging.get(getServletContext());
|
||||||
if (push.sendNoRetry(deviceInfo.getDeviceRegistrationID(),
|
if (push.sendNoRetry(deviceInfo.getDeviceRegistrationID(),
|
||||||
"" + url.hashCode(), "url", url, "title", title)) {
|
"" + url.hashCode(), "url", url, "title", title,
|
||||||
|
"sel", sel)) {
|
||||||
log.info("Link sent to phone!");
|
log.info("Link sent to phone!");
|
||||||
resp.getWriter().println(OK_STATUS);
|
resp.getWriter().println(OK_STATUS);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
Reference in New Issue
Block a user