mirror of https://github.com/tasks/tasks
Remove update message service
parent
d1d684d348
commit
69829e3f68
@ -1,18 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2012 Todoroo Inc
|
||||
*
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.andlib.service;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class HttpErrorException extends IOException {
|
||||
|
||||
private static final long serialVersionUID = 5373340422464657279L;
|
||||
|
||||
public HttpErrorException(int code, String message) {
|
||||
super(String.format("%d %s", code, message)); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
}
|
@ -1,246 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2012 Todoroo Inc
|
||||
*
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.andlib.service;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.todoroo.andlib.utility.AndroidUtilities;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HeaderElement;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpException;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.HttpRequestInterceptor;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpResponseInterceptor;
|
||||
import org.apache.http.HttpVersion;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.conn.params.ConnManagerPNames;
|
||||
import org.apache.http.conn.params.ConnPerRouteBean;
|
||||
import org.apache.http.conn.scheme.PlainSocketFactory;
|
||||
import org.apache.http.conn.scheme.Scheme;
|
||||
import org.apache.http.conn.scheme.SchemeRegistry;
|
||||
import org.apache.http.conn.ssl.SSLSocketFactory;
|
||||
import org.apache.http.entity.HttpEntityWrapper;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
|
||||
import org.apache.http.params.BasicHttpParams;
|
||||
import org.apache.http.params.HttpConnectionParams;
|
||||
import org.apache.http.params.HttpParams;
|
||||
import org.apache.http.params.HttpProtocolParams;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* RestClient allows Android to consume web requests.
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class HttpRestClient implements RestClient {
|
||||
|
||||
private static final int HTTP_UNAVAILABLE_END = 599;
|
||||
private static final int HTTP_UNAVAILABLE_START = 500;
|
||||
private static final int HTTP_OK = 200;
|
||||
|
||||
private static final int TIMEOUT_MILLIS = 60000;
|
||||
|
||||
private WeakReference<HttpClient> httpClient = null;
|
||||
|
||||
protected boolean debug = false;
|
||||
|
||||
private int timeout = TIMEOUT_MILLIS;
|
||||
|
||||
public HttpRestClient() {
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
|
||||
SchemeRegistry schemeRegistry = new SchemeRegistry();
|
||||
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
|
||||
schemeRegistry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
|
||||
|
||||
params = new BasicHttpParams();
|
||||
HttpConnectionParams.setConnectionTimeout(params, timeout);
|
||||
HttpConnectionParams.setSoTimeout(params, timeout);
|
||||
params.setParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 30);
|
||||
params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new ConnPerRouteBean(30));
|
||||
params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false);
|
||||
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
|
||||
|
||||
cm = new ThreadSafeClientConnManager(params, schemeRegistry);
|
||||
}
|
||||
|
||||
public HttpRestClient(int timeout) {
|
||||
super();
|
||||
this.timeout = timeout;
|
||||
|
||||
HttpConnectionParams.setConnectionTimeout(params, timeout);
|
||||
HttpConnectionParams.setSoTimeout(params, timeout);
|
||||
}
|
||||
|
||||
private HttpParams params;
|
||||
private ThreadSafeClientConnManager cm;
|
||||
|
||||
private synchronized HttpClient getClient() {
|
||||
if (httpClient == null || httpClient.get() == null) {
|
||||
DefaultHttpClient client = new DefaultHttpClient(cm, params);
|
||||
httpClient = new WeakReference<HttpClient>(client);
|
||||
actsAsGzippable(client);
|
||||
return client;
|
||||
}
|
||||
return httpClient.get();
|
||||
}
|
||||
|
||||
protected void actsAsGzippable(DefaultHttpClient client) {
|
||||
client.addRequestInterceptor(new HttpRequestInterceptor() {
|
||||
@Override
|
||||
public void process(
|
||||
final HttpRequest request,
|
||||
final HttpContext context) throws HttpException, IOException {
|
||||
if (!request.containsHeader("Accept-Encoding")) {
|
||||
request.addHeader("Accept-Encoding", "gzip");
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
client.addResponseInterceptor(new HttpResponseInterceptor() {
|
||||
@Override
|
||||
public void process(
|
||||
final HttpResponse response,
|
||||
final HttpContext context) throws HttpException, IOException {
|
||||
HttpEntity entity = response.getEntity();
|
||||
Header ceheader = entity.getContentEncoding();
|
||||
if (ceheader != null) {
|
||||
HeaderElement[] codecs = ceheader.getElements();
|
||||
for (int i = 0; i < codecs.length; i++) {
|
||||
if (codecs[i].getName().equalsIgnoreCase("gzip")) {
|
||||
response.setEntity(
|
||||
new GzipDecompressingEntity(response.getEntity()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static class GzipDecompressingEntity extends HttpEntityWrapper {
|
||||
|
||||
public GzipDecompressingEntity(final HttpEntity entity) {
|
||||
super(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getContent()
|
||||
throws IOException, IllegalStateException {
|
||||
|
||||
// the wrapped entity's getContent() decides about repeatability
|
||||
InputStream wrappedin = wrappedEntity.getContent();
|
||||
|
||||
return new GZIPInputStream(wrappedin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContentLength() {
|
||||
// length of ungzipped content is not known
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private String processHttpResponse(HttpResponse response) throws IOException {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
if(statusCode >= HTTP_UNAVAILABLE_START && statusCode <= HTTP_UNAVAILABLE_END) {
|
||||
throw new HttpUnavailableException();
|
||||
}
|
||||
|
||||
HttpEntity entity = response.getEntity();
|
||||
|
||||
String body = null;
|
||||
if (entity != null) {
|
||||
InputStream contentStream = entity.getContent();
|
||||
try {
|
||||
body = AndroidUtilities.readInputStream(contentStream);
|
||||
} finally {
|
||||
contentStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
if(statusCode != HTTP_OK) {
|
||||
System.out.println(body);
|
||||
throw new HttpErrorException(response.getStatusLine().getStatusCode(),
|
||||
response.getStatusLine().getReasonPhrase());
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Issue an HTTP GET for the given URL, return the response
|
||||
*
|
||||
* @param url url with url-encoded params
|
||||
* @return response, or null if there was no response
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
public synchronized String get(String url) throws IOException {
|
||||
if(debug) {
|
||||
Log.d("http-rest-client-get", url); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
try {
|
||||
HttpGet httpGet = new HttpGet(url);
|
||||
HttpResponse response = getClient().execute(httpGet);
|
||||
|
||||
return processHttpResponse(response);
|
||||
} catch (IOException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
IOException ioException = new IOException(e.getMessage());
|
||||
ioException.initCause(e);
|
||||
throw ioException;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Issue an HTTP POST for the given URL, return the response
|
||||
*
|
||||
* @param data
|
||||
* url-encoded data
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
public synchronized String post(String url, HttpEntity data, Header... headers) throws IOException {
|
||||
if(debug) {
|
||||
Log.d("http-rest-client-post", url + " | " + data); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
}
|
||||
|
||||
try {
|
||||
HttpPost httpPost = new HttpPost(url);
|
||||
httpPost.setEntity(data);
|
||||
for(Header header : headers) {
|
||||
httpPost.addHeader(header);
|
||||
}
|
||||
HttpResponse response = getClient().execute(httpPost);
|
||||
|
||||
return processHttpResponse(response);
|
||||
} catch (IOException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
IOException ioException = new IOException(e.getMessage());
|
||||
ioException.initCause(e);
|
||||
throw ioException;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2012 Todoroo Inc
|
||||
*
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.andlib.service;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Exception displayed when a 500 error is received on an HTTP invocation
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class HttpUnavailableException extends IOException {
|
||||
|
||||
private static final long serialVersionUID = 5373340422464657279L;
|
||||
|
||||
public HttpUnavailableException() {
|
||||
super();
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "Sorry, our servers are experiencing some issues. Please try again later!"; //$NON-NLS-1$ // FIXME
|
||||
}
|
||||
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2012 Todoroo Inc
|
||||
*
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.andlib.service;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpEntity;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* RestClient stub invokes the HTML requests as desired
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public interface RestClient {
|
||||
public String get(String url) throws IOException;
|
||||
public String post(String url, HttpEntity data, Header... headers) throws IOException;
|
||||
}
|
@ -1,330 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2012 Todoroo Inc
|
||||
*
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.service;
|
||||
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.andlib.service.RestClient;
|
||||
import com.todoroo.astrid.dao.StoreObjectDao;
|
||||
import com.todoroo.astrid.dao.StoreObjectDao.StoreObjectCriteria;
|
||||
import com.todoroo.astrid.gtasks.GtasksPreferenceService;
|
||||
import com.todoroo.astrid.test.DatabaseTestCase;
|
||||
import com.todoroo.astrid.utility.Constants;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.json.JSONArray;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class UpdateMessageServiceTest extends DatabaseTestCase {
|
||||
|
||||
@Autowired private StoreObjectDao storeObjectDao;
|
||||
@Autowired private GtasksPreferenceService gtasksPreferenceService;
|
||||
|
||||
public void testNoUpdates() {
|
||||
clearLatestUpdates();
|
||||
|
||||
new TestUpdateMessageService() {
|
||||
|
||||
@Override
|
||||
void verifyMessage(MessageTuple message) {
|
||||
fail("should not have displayed updates");
|
||||
}
|
||||
|
||||
@Override
|
||||
String getUpdates(String url) throws IOException {
|
||||
assertTrue(url, url.contains("language=eng"));
|
||||
assertTrue(url.contains("version="));
|
||||
return "";
|
||||
}
|
||||
}.processUpdates();
|
||||
}
|
||||
|
||||
public void testIOException() {
|
||||
clearLatestUpdates();
|
||||
|
||||
new TestUpdateMessageService() {
|
||||
|
||||
@Override
|
||||
void verifyMessage(MessageTuple message) {
|
||||
fail("should not have displayed updates");
|
||||
}
|
||||
|
||||
@Override
|
||||
String getUpdates(String url) throws IOException {
|
||||
throw new IOException("yayaya");
|
||||
}
|
||||
}.processUpdates();
|
||||
}
|
||||
|
||||
public void testNewUpdate() {
|
||||
clearLatestUpdates();
|
||||
|
||||
new TestUpdateMessageService() {
|
||||
|
||||
@Override
|
||||
void verifyMessage(MessageTuple message) {
|
||||
assertTrue(message.message.toString().contains("yo"));
|
||||
}
|
||||
|
||||
@Override
|
||||
String getUpdates(String url) throws IOException {
|
||||
return "[{message:'yo'}]";
|
||||
}
|
||||
}.processUpdates();
|
||||
}
|
||||
|
||||
public void testMultipleUpdates() {
|
||||
clearLatestUpdates();
|
||||
|
||||
new TestUpdateMessageService() {
|
||||
|
||||
@Override
|
||||
void verifyMessage(MessageTuple message) {
|
||||
assertTrue(message.message.toString().contains("yo"));
|
||||
assertFalse(message.message.toString().contains("cat")); // We only process the first update now
|
||||
}
|
||||
|
||||
@Override
|
||||
String getUpdates(String url) throws IOException {
|
||||
return "[{message:'yo'},{message:'cat'}]";
|
||||
}
|
||||
}.processUpdates();
|
||||
}
|
||||
|
||||
public void testExistingUpdate() {
|
||||
clearLatestUpdates();
|
||||
|
||||
new TestUpdateMessageService() {
|
||||
|
||||
@Override
|
||||
void verifyMessage(MessageTuple message) {
|
||||
assertTrue(message.message.toString().contains("yo"));
|
||||
}
|
||||
|
||||
@Override
|
||||
String getUpdates(String url) throws IOException {
|
||||
return "[{message:'yo'}]";
|
||||
}
|
||||
}.processUpdates();
|
||||
|
||||
new TestUpdateMessageService() {
|
||||
|
||||
@Override
|
||||
void verifyMessage(MessageTuple message) {
|
||||
fail("should have not displayed again");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEmptyMessage() {
|
||||
// expected
|
||||
}
|
||||
|
||||
@Override
|
||||
String getUpdates(String url) throws IOException {
|
||||
return "[{message:'yo'}]";
|
||||
}
|
||||
}.processUpdates();
|
||||
}
|
||||
|
||||
public void testUpdateWithDate() {
|
||||
clearLatestUpdates();
|
||||
|
||||
new TestUpdateMessageService() {
|
||||
|
||||
@Override
|
||||
void verifyMessage(MessageTuple message) {
|
||||
assertTrue(message.message.toString().contains("yo"));
|
||||
}
|
||||
|
||||
@Override
|
||||
String getUpdates(String url) throws IOException {
|
||||
return "[{message:'yo',date:'date'}]";
|
||||
}
|
||||
}.processUpdates();
|
||||
}
|
||||
|
||||
public void testUpdateWithInternalPluginOn() {
|
||||
clearLatestUpdates();
|
||||
gtasksPreferenceService.setToken("gtasks");
|
||||
|
||||
new TestUpdateMessageService() {
|
||||
|
||||
@Override
|
||||
void verifyMessage(MessageTuple message) {
|
||||
assertTrue(message.message.toString().contains("gtasks man"));
|
||||
}
|
||||
|
||||
@Override
|
||||
String getUpdates(String url) throws IOException {
|
||||
return "[{message:'gtasks man',plugin:'gtasks'}]";
|
||||
}
|
||||
}.processUpdates();
|
||||
gtasksPreferenceService.setToken(null);
|
||||
}
|
||||
|
||||
public void testUpdateWithInternalPluginOff() {
|
||||
clearLatestUpdates();
|
||||
gtasksPreferenceService.setToken(null);
|
||||
|
||||
new TestUpdateMessageService() {
|
||||
|
||||
@Override
|
||||
void verifyMessage(MessageTuple message) {
|
||||
fail("displayed update");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEmptyMessage() {
|
||||
// expected
|
||||
}
|
||||
|
||||
@Override
|
||||
String getUpdates(String url) throws IOException {
|
||||
return "[{message:'gtasks man',plugin:'gtasks'}]";
|
||||
}
|
||||
}.processUpdates();
|
||||
}
|
||||
|
||||
public void testUpdateWithExternalPluginOn() {
|
||||
clearLatestUpdates();
|
||||
|
||||
new TestUpdateMessageService() {
|
||||
|
||||
@Override
|
||||
void verifyMessage(MessageTuple message) {
|
||||
assertTrue(message.message.toString().contains("astrid man"));
|
||||
}
|
||||
|
||||
@Override
|
||||
String getUpdates(String url) throws IOException {
|
||||
return "[{message:'astrid man',plugin:'" + Constants.PACKAGE + "'}]";
|
||||
}
|
||||
}.processUpdates();
|
||||
}
|
||||
|
||||
public void testUpdateWithExternalPluginOff() {
|
||||
clearLatestUpdates();
|
||||
|
||||
new TestUpdateMessageService() {
|
||||
|
||||
@Override
|
||||
void verifyMessage(MessageTuple message) {
|
||||
fail("displayed update");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEmptyMessage() {
|
||||
// expected
|
||||
}
|
||||
|
||||
@Override
|
||||
String getUpdates(String url) throws IOException {
|
||||
return "[{message:'astrid man',plugin:'com.bogus.package'}]";
|
||||
}
|
||||
}.processUpdates();
|
||||
}
|
||||
|
||||
public void testUpdateWithScreenFlow() {
|
||||
clearLatestUpdates();
|
||||
|
||||
new TestUpdateMessageService() {
|
||||
|
||||
@Override
|
||||
void verifyMessage(MessageTuple message) {
|
||||
assertTrue(message.linkText.size() > 0);
|
||||
assertTrue(message.click.size() > 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
String getUpdates(String url) throws IOException {
|
||||
return "[{type:'screen',screens:['com.todoroo.astrid.activity.TaskListActivity'],message:'Screens'}]";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void testUpdateWithPrefs() {
|
||||
clearLatestUpdates();
|
||||
|
||||
new TestUpdateMessageService() {
|
||||
|
||||
@Override
|
||||
void verifyMessage(MessageTuple message) {
|
||||
assertTrue(message.linkText.size() > 0);
|
||||
assertTrue(message.click.size() > 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
String getUpdates(String url) throws IOException {
|
||||
return "[{type:'pref',prefs:[{key:'key', type:'bool', title:'my pref'}],message:'Prefs'}]";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void testUpdateWithLinks() {
|
||||
clearLatestUpdates();
|
||||
|
||||
new TestUpdateMessageService() {
|
||||
@Override
|
||||
void verifyMessage(MessageTuple message) {
|
||||
assertEquals("Message", message.message);
|
||||
assertEquals("link", message.linkText.get(0));
|
||||
assertNotNull(message.click.get(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
String getUpdates(String url) throws IOException {
|
||||
return "[{message:'Message', links:[{title:'link',url:'http://astrid.com'}]]";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
private void clearLatestUpdates() {
|
||||
storeObjectDao.deleteWhere(StoreObjectCriteria.byType(UpdateMessageService.UpdateMessage.TYPE));
|
||||
}
|
||||
|
||||
/** helper test class */
|
||||
abstract public class TestUpdateMessageService extends UpdateMessageService {
|
||||
|
||||
public TestUpdateMessageService() {
|
||||
super(null);
|
||||
restClient = new RestClient() {
|
||||
|
||||
public String post(String url, HttpEntity data, Header... headers) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String get(String url) throws IOException {
|
||||
return getUpdates(url);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
abstract void verifyMessage(MessageTuple message);
|
||||
|
||||
abstract String getUpdates(String url) throws IOException;
|
||||
|
||||
protected void onEmptyMessage() {
|
||||
fail("empty update message");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MessageTuple buildUpdateMessage(JSONArray updates) {
|
||||
MessageTuple message = super.buildUpdateMessage(updates);
|
||||
if(message == null || message.message.length() == 0)
|
||||
onEmptyMessage();
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void displayUpdateDialog(MessageTuple tuple) {
|
||||
verifyMessage(tuple);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,383 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2012 Todoroo Inc
|
||||
*
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.service;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.WindowManager.BadTokenException;
|
||||
import android.webkit.WebView;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.LinearLayout.LayoutParams;
|
||||
|
||||
import com.todoroo.andlib.data.Property.StringProperty;
|
||||
import com.todoroo.andlib.data.TodorooCursor;
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.andlib.service.DependencyInjectionService;
|
||||
import com.todoroo.andlib.service.RestClient;
|
||||
import com.todoroo.andlib.sql.Query;
|
||||
import com.todoroo.andlib.utility.AndroidUtilities;
|
||||
import com.todoroo.astrid.dao.StoreObjectDao;
|
||||
import com.todoroo.astrid.dao.StoreObjectDao.StoreObjectCriteria;
|
||||
import com.todoroo.astrid.data.StoreObject;
|
||||
import com.todoroo.astrid.gtasks.GtasksPreferenceService;
|
||||
import com.todoroo.astrid.utility.Constants;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.tasks.R;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Notifies users when there are server updates
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class UpdateMessageService {
|
||||
|
||||
private static final String URL = "http://blog.astrid.com/updates";
|
||||
|
||||
private static final String PLUGIN_GTASKS = "gtasks";
|
||||
|
||||
@Autowired protected RestClient restClient;
|
||||
@Autowired private GtasksPreferenceService gtasksPreferenceService;
|
||||
@Autowired private AddOnService addOnService;
|
||||
@Autowired private StoreObjectDao storeObjectDao;
|
||||
|
||||
private final Activity activity;
|
||||
|
||||
public UpdateMessageService(Activity activity) {
|
||||
this.activity = activity;
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
}
|
||||
|
||||
public void processUpdates() {
|
||||
JSONArray updates = checkForUpdates();
|
||||
|
||||
if(updates == null || updates.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
MessageTuple message = buildUpdateMessage(updates);
|
||||
if(message == null || message.message.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
displayUpdateDialog(message);
|
||||
}
|
||||
|
||||
public static class MessageTuple {
|
||||
public String message = null;
|
||||
public List<String> linkText = new ArrayList<String>();
|
||||
public List<OnClickListener> click = new ArrayList<OnClickListener>();
|
||||
}
|
||||
|
||||
private static interface DialogShower {
|
||||
void showDialog(Activity activity);
|
||||
}
|
||||
|
||||
private void tryShowDialog(DialogShower ds) {
|
||||
try {
|
||||
ds.showDialog(activity);
|
||||
} catch (BadTokenException bt) {
|
||||
try {
|
||||
Activity current = (Activity) ContextManager.getContext();
|
||||
ds.showDialog(current);
|
||||
} catch (ClassCastException c) {
|
||||
// Oh well, context wasn't an activity
|
||||
} catch (BadTokenException bt2) {
|
||||
// Oh well, activity isn't running
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void displayUpdateDialog(final MessageTuple message) {
|
||||
if(activity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.linkText.size() > 0) {
|
||||
final DialogShower ds = new DialogShower() {
|
||||
@Override
|
||||
public void showDialog(Activity a) {
|
||||
try {
|
||||
final Dialog d = new Dialog(activity, R.style.ReminderDialog);
|
||||
d.setContentView(R.layout.update_message_view);
|
||||
|
||||
// TODO: Make HTML message
|
||||
WebView messageView = (WebView) d.findViewById(R.id.reminder_message);
|
||||
String html = "<html><body>" + message.message + "</body></html>";
|
||||
messageView.loadDataWithBaseURL("file:///android_asset/", html, "text/html", "utf-8", null);
|
||||
messageView.setBackgroundColor(0);
|
||||
d.findViewById(R.id.dismiss).setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
d.dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
LinearLayout root = (LinearLayout) d.findViewById(R.id.reminder_root);
|
||||
DisplayMetrics metrics = activity.getResources().getDisplayMetrics();
|
||||
TypedValue themeColor = new TypedValue();
|
||||
activity.getTheme().resolveAttribute(R.attr.asThemeTextColor, themeColor, false);
|
||||
int color = activity.getResources().getColor(themeColor.data);
|
||||
for (int i = 0; i < message.linkText.size(); i++) {
|
||||
Button linkButton = new Button(activity);
|
||||
linkButton.setText(message.linkText.get(i));
|
||||
final OnClickListener click = message.click.get(i);
|
||||
linkButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
click.onClick(v);
|
||||
d.dismiss();
|
||||
}
|
||||
});
|
||||
LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, (int) (metrics.density * 35));
|
||||
params.leftMargin = params.rightMargin = (int) (metrics.density * 5);
|
||||
params.bottomMargin = (int) (metrics.density * 10);
|
||||
linkButton.setTextColor(Color.WHITE);
|
||||
linkButton.setTextSize(20);
|
||||
linkButton.setBackgroundColor(color);
|
||||
linkButton.setLayoutParams(params);
|
||||
linkButton.setPadding(0, 0, 0, 0);
|
||||
|
||||
root.addView(linkButton);
|
||||
}
|
||||
|
||||
d.show();
|
||||
} catch (Exception e) {
|
||||
// This should never ever crash
|
||||
}
|
||||
}
|
||||
};
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
tryShowDialog(ds);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected MessageTuple buildUpdateMessage(JSONArray updates) {
|
||||
for(int i = 0; i < updates.length(); i++) {
|
||||
JSONObject update;
|
||||
try {
|
||||
update = updates.getJSONObject(i);
|
||||
} catch (JSONException e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String date = update.optString("date", null);
|
||||
String message = update.optString("message", null);
|
||||
String plugin = update.optString("plugin", null);
|
||||
String notPlugin = update.optString("notplugin", null);
|
||||
|
||||
if(message == null) {
|
||||
continue;
|
||||
}
|
||||
if(plugin != null) {
|
||||
if(!pluginConditionMatches(plugin)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if(notPlugin != null) {
|
||||
if(pluginConditionMatches(notPlugin)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
MessageTuple toReturn = new MessageTuple();
|
||||
toReturn.message = message;
|
||||
String type = update.optString("type", null);
|
||||
if ("screen".equals(type) || "pref".equals(type)) {
|
||||
String linkText = update.optString("link");
|
||||
OnClickListener click = getClickListenerForUpdate(update, type);
|
||||
if (click == null) {
|
||||
continue;
|
||||
}
|
||||
toReturn.linkText.add(linkText);
|
||||
toReturn.click.add(click);
|
||||
} else {
|
||||
JSONArray links = update.optJSONArray("links");
|
||||
if (links != null) {
|
||||
for (int j = 0; j < links.length(); j++) {
|
||||
JSONObject link = links.optJSONObject(j);
|
||||
if (link == null) {
|
||||
continue;
|
||||
}
|
||||
String linkText = link.optString("title");
|
||||
if (TextUtils.isEmpty(linkText)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final String url = link.optString("url");
|
||||
OnClickListener click = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
};
|
||||
|
||||
toReturn.linkText.add(linkText);
|
||||
toReturn.click.add(click);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(messageAlreadySeen(date, message)) {
|
||||
continue;
|
||||
}
|
||||
return toReturn;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private OnClickListener getClickListenerForUpdate(JSONObject update, String type) {
|
||||
if ("pref".equals(type)) {
|
||||
try {
|
||||
if (!update.has("action_list")) {
|
||||
return null;
|
||||
}
|
||||
JSONArray prefSpec = update.getJSONArray("action_list");
|
||||
if (prefSpec.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
final String prefArray = prefSpec.toString();
|
||||
return new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View b) {
|
||||
Intent prefScreen = new Intent(activity, UpdateMessagePreference.class);
|
||||
prefScreen.putExtra(UpdateMessagePreference.TOKEN_PREFS_ARRAY, prefArray);
|
||||
activity.startActivityForResult(prefScreen, 0);
|
||||
}
|
||||
};
|
||||
} catch (JSONException e) {
|
||||
return null;
|
||||
}
|
||||
} else if ("screen".equals(type)) {
|
||||
try {
|
||||
if (!update.has("action_list")) {
|
||||
return null;
|
||||
}
|
||||
JSONArray screens = update.getJSONArray("action_list");
|
||||
if (screens.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
final ArrayList<String> screenList = new ArrayList<String>();
|
||||
for (int i = 0; i < screens.length(); i++) {
|
||||
String screen = screens.getString(i).trim();
|
||||
if (!TextUtils.isEmpty(screen)) {
|
||||
screenList.add(screen);
|
||||
}
|
||||
}
|
||||
return new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent screenFlow = new Intent(activity, UpdateScreenFlow.class);
|
||||
screenFlow.putStringArrayListExtra(UpdateScreenFlow.TOKEN_SCREENS, screenList);
|
||||
activity.startActivity(screenFlow);
|
||||
}
|
||||
};
|
||||
} catch (JSONException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean pluginConditionMatches(String plugin) {
|
||||
// handle internal plugin specially
|
||||
if(PLUGIN_GTASKS.equals(plugin)) {
|
||||
return gtasksPreferenceService.isLoggedIn();
|
||||
} else {
|
||||
return addOnService.isInstalled(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean messageAlreadySeen(String date, String message) {
|
||||
if(date != null) {
|
||||
message = date + message;
|
||||
}
|
||||
String hash = AndroidUtilities.md5(message);
|
||||
|
||||
TodorooCursor<StoreObject> cursor = storeObjectDao.query(Query.select(StoreObject.ID).
|
||||
where(StoreObjectCriteria.byTypeAndItem(UpdateMessage.TYPE, hash)));
|
||||
try {
|
||||
if(cursor.getCount() > 0) {
|
||||
return true;
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
StoreObject newUpdateMessage = new StoreObject();
|
||||
newUpdateMessage.setValue(StoreObject.TYPE, UpdateMessage.TYPE);
|
||||
newUpdateMessage.setValue(UpdateMessage.HASH, hash);
|
||||
storeObjectDao.persist(newUpdateMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
private JSONArray checkForUpdates() {
|
||||
PackageManager pm = ContextManager.getContext().getPackageManager();
|
||||
try {
|
||||
PackageInfo pi = pm.getPackageInfo(Constants.PACKAGE, PackageManager.GET_META_DATA);
|
||||
int versionCode = pi.versionCode;
|
||||
String url = URL + "?version=" + versionCode + "&" +
|
||||
"language=" + Locale.getDefault().getISO3Language() + "&" +
|
||||
"market=" + Constants.MARKET_STRATEGY.strategyId() + "&" +
|
||||
"actfm=0" + "&" +
|
||||
"premium=1";
|
||||
String result = restClient.get(url); //$NON-NLS-1$
|
||||
if(TextUtils.isEmpty(result)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new JSONArray(result);
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
} catch (NameNotFoundException e) {
|
||||
return null;
|
||||
} catch (JSONException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** store object for messages a user has seen */
|
||||
static class UpdateMessage {
|
||||
|
||||
/** type*/
|
||||
public static final String TYPE = "update-message"; //$NON-NLS-1$
|
||||
|
||||
/** message contents */
|
||||
public static final StringProperty HASH = new StringProperty(StoreObject.TABLE,
|
||||
StoreObject.ITEM.name);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
** Copyright (c) 2012 Todoroo Inc
|
||||
**
|
||||
** See the file "LICENSE" for the full license governing this code.
|
||||
-->
|
||||
<merge>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/speech_bubble_container"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginLeft="2dip"
|
||||
android:layout_marginRight="5dip"
|
||||
android:layout_marginBottom="10dip"
|
||||
android:gravity="bottom">
|
||||
<ImageView
|
||||
android:id="@+id/astridIcon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="5dip"
|
||||
android:src="@drawable/icon"
|
||||
android:scaleType="fitCenter"
|
||||
android:layout_alignParentLeft="true"/>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/speech_bubble_content"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="5dip"
|
||||
android:layout_marginBottom="5dip"
|
||||
android:minHeight="60dip"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:gravity="center_vertical"
|
||||
android:background="@drawable/speech_bubble_reminder"
|
||||
android:layout_toRightOf="@id/astridIcon">
|
||||
<WebView
|
||||
android:id="@+id/reminder_message"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/TextAppearance.TLA_Reminder"
|
||||
android:background="@android:color/transparent"
|
||||
android:gravity="center_vertical"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</merge>
|
@ -1,43 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
** Copyright (c) 2012 Todoroo Inc
|
||||
**
|
||||
** See the file "LICENSE" for the full license governing this code.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/reminder_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="10dip"
|
||||
android:paddingRight="10dip"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="5dip"
|
||||
android:layout_marginRight="5dip"
|
||||
android:layout_marginBottom="20dip"
|
||||
android:layout_marginLeft="5dip">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/update_title"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="20sp"
|
||||
android:textColor="@android:color/white"
|
||||
android:text="@string/UpS_updates_title"
|
||||
android:layout_weight="1"/>
|
||||
<ImageView
|
||||
android:id="@+id/dismiss"
|
||||
android:layout_width="25dip"
|
||||
android:layout_height="25dip"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_menu_close"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<include layout="@layout/update_message_speech_bubble"/>
|
||||
|
||||
</LinearLayout>
|
Loading…
Reference in New Issue