/*******************************************************************************
* Copyright 2011 The Regents of the University of California
*
* 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 org.ohmage;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.app.Application;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.http.AndroidHttpClient;
import android.os.Build;
import android.os.Handler;
import com.commonsware.cwac.wakeful.WakefulIntentService;
import com.google.android.imageloader.BitmapContentHandler;
import com.google.android.imageloader.ImageLoader;
import org.ohmage.authenticator.Authenticator;
import org.ohmage.db.DbContract.Responses;
import org.ohmage.db.DbHelper;
import org.ohmage.db.Models.Response;
import org.ohmage.logprobe.Analytics;
import org.ohmage.logprobe.Log;
import org.ohmage.logprobe.LogProbe;
import org.ohmage.logprobe.LogProbe.Status;
import org.ohmage.prompt.multichoicecustom.MultiChoiceCustomDbAdapter;
import org.ohmage.prompt.singlechoicecustom.SingleChoiceCustomDbAdapter;
import org.ohmage.responsesync.ResponseSyncService;
import org.ohmage.service.SurveyGeotagService;
import org.ohmage.service.UploadService;
import org.ohmage.triggers.glue.TriggerFramework;
import java.io.IOException;
import java.net.ContentHandler;
import java.net.URLStreamHandlerFactory;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class OhmageApplication extends Application {
private static final String TAG = "OhmageApplication";
/**
* Account type string.
*/
public static final String ACCOUNT_TYPE = "org.ohmage";
/**
* Authtoken type string.
*/
public static final String AUTHTOKEN_TYPE = "org.ohmage";
public static final String VIEW_MAP = "ohmage.intent.action.VIEW_MAP";
public static final String ACTION_VIEW_REMOTE_IMAGE = "org.ohmage.action.VIEW_REMOTE_IMAGE";
private static final int IMAGE_TASK_LIMIT = 3;
// 50% of available memory, up to a maximum of 32MB
private static final long IMAGE_CACHE_SIZE = Math.min(Runtime.getRuntime().maxMemory() / 2,
32 * 1024 * 1024);
/**
* 10MB max for cached thumbnails
*/
public static final int MAX_DISK_CACHE_SIZE = 10 * 1024 * 1024;
private ImageLoader mImageLoader;
private static OhmageApplication self;
private static ContentResolver mFakeContentResolver;
private static AndroidHttpClient mHttpClient;
private static AccountManager mAccountManager;
@Override
public void onCreate() {
super.onCreate();
Analytics.activity(this, Status.ON);
self = this;
ConfigHelper config = new ConfigHelper(this);
LogProbe.setLevel(config.getLogAnalytics(), config.getLogLevel());
LogProbe.get(this);
mImageLoader = createImageLoader(this);
int currentVersionCode = 0;
try {
currentVersionCode = getPackageManager().getPackageInfo("org.ohmage", 0).versionCode;
} catch (NameNotFoundException e) {
Log.e(TAG, "unable to retrieve current version code", e);
}
ConfigHelper prefs = new ConfigHelper(this);
int lastVersionCode = prefs.getLastVersionCode();
boolean isFirstRun = prefs.isFirstRun();
if (currentVersionCode != lastVersionCode && !isFirstRun) {
BackgroundManager.initComponents(this);
prefs.setLastVersionCode(currentVersionCode);
}
verifyState();
// If they can't set a custom server, verify the server that is set is
// the first in the list of servers
if (!getResources().getBoolean(R.bool.allow_custom_server)) {
List<String> servers = Arrays.asList(getResources().getStringArray(R.array.servers));
if (servers.isEmpty())
throw new RuntimeException("At least one server must be specified in config.xml");
ConfigHelper.setServerUrl(servers.get(0));
}
}
public void updateLogLevel() {
ConfigHelper config = new ConfigHelper(this);
LogProbe.setLevel(config.getLogAnalytics(), config.getLogLevel());
}
/**
* This method verifies that the state of ohmage is correct when it starts
* up. fixes response state for crashes while waiting for:
* <ul>
* <li>location from the {@link SurveyGeotagService}, waiting for location
* status</li>
* <li>{@link UploadService}, uploading or queued status</li>
* <ul>
* It also deletes any responses which have no uuid
*/
private void verifyState() {
ContentValues values = new ContentValues();
values.put(Responses.RESPONSE_STATUS, Response.STATUS_STANDBY);
getContentResolver().update(
Responses.CONTENT_URI,
values,
Responses.RESPONSE_STATUS + "=" + Response.STATUS_QUEUED + " OR "
+ Responses.RESPONSE_STATUS + "=" + Response.STATUS_UPLOADING + " OR "
+ Responses.RESPONSE_STATUS + "=" + Response.STATUS_WAITING_FOR_LOCATION,
null);
if (getContentResolver().delete(Responses.CONTENT_URI,
Responses.RESPONSE_UUID + " is null", null) != 0) {
// If there were some responses with no uuid, start the feedback
// service
Intent fbIntent = new Intent(getContext(), ResponseSyncService.class);
WakefulIntentService.sendWakefulWork(getContext(), fbIntent);
}
}
public void resetAll() {
// clear everything?
Log.v(TAG, "Reseting all data");
// clear the user account
AccountManager accountManager = AccountManager.get(self);
Account[] accounts = accountManager.getAccountsByType(ACCOUNT_TYPE);
final CountDownLatch latch = new CountDownLatch(accounts.length);
Authenticator.setAllowRemovingAccounts(true);
for (Account account : accounts) {
accountManager.removeAccount(account, new AccountManagerCallback<Boolean>() {
@Override
public void run(AccountManagerFuture<Boolean> future) {
latch.countDown();
}
}, null);
}
// clear user prefs
new UserPreferencesHelper(this).clearAll();
// clear all deployment settings
new ConfigHelper(this).clearDeploymentSettings();
// clear triggers
TriggerFramework.resetAllTriggerSettings(this);
// delete campaign specific settings
CampaignPreferencesHelper.clearAll(this);
// clear db
new DbHelper(this).clearAll();
// clear custom type dbs
SingleChoiceCustomDbAdapter singleChoiceDbAdapter = new SingleChoiceCustomDbAdapter(this);
if (singleChoiceDbAdapter.open()) {
singleChoiceDbAdapter.clearAll();
singleChoiceDbAdapter.close();
}
MultiChoiceCustomDbAdapter multiChoiceDbAdapter = new MultiChoiceCustomDbAdapter(this);
if (multiChoiceDbAdapter.open()) {
multiChoiceDbAdapter.clearAll();
multiChoiceDbAdapter.close();
}
// clear images
try {
Utilities.delete(getExternalCacheDir());
} catch (IOException e) {
Log.e(TAG, "Error deleting external cache directory", e);
}
try {
Utilities.delete(getCacheDir());
} catch (IOException e) {
Log.e(TAG, "Error deleting cache directory", e);
}
}
private static ImageLoader createImageLoader(Context context) {
// Install the file cache (if it is not already installed)
OhmageCache.install(context);
checkCacheUsage();
// Just use the default URLStreamHandlerFactory because
// it supports all of the required URI schemes (http).
URLStreamHandlerFactory streamFactory = null;
// Load images using a BitmapContentHandler
// and cache the image data in the file cache.
ContentHandler bitmapHandler = OhmageCache.capture(new BitmapContentHandler(), null);
// For pre-fetching, use a "sink" content handler so that the
// the binary image data is captured by the cache without actually
// parsing and loading the image data into memory. After pre-fetching,
// the image data can be loaded quickly on-demand from the local cache.
ContentHandler prefetchHandler = OhmageCache.capture(OhmageCache.sink(), null);
// Perform callbacks on the main thread
Handler handler = null;
return new ImageLoader(IMAGE_TASK_LIMIT, streamFactory, bitmapHandler, prefetchHandler,
IMAGE_CACHE_SIZE, handler);
}
/**
* check the cache usage
*/
public static void checkCacheUsage() {
OhmageCache.checkCacheUsage(self, MAX_DISK_CACHE_SIZE);
}
@Override
public void onTerminate() {
if (mHttpClient != null) {
mHttpClient.close();
mHttpClient = null;
}
LogProbe.close(this);
mImageLoader = null;
Analytics.activity(this, Status.OFF);
super.onTerminate();
}
@Override
public Object getSystemService(String name) {
if (ImageLoader.IMAGE_LOADER_SERVICE.equals(name)) {
return mImageLoader;
} else {
return super.getSystemService(name);
}
}
public static void setFakeContentResolver(ContentResolver resolver) {
mFakeContentResolver = resolver;
}
public static ContentResolver getFakeContentResolver() {
return mFakeContentResolver;
}
@Override
public ContentResolver getContentResolver() {
if (mFakeContentResolver != null)
return mFakeContentResolver;
return super.getContentResolver();
}
/**
* Static reference from the Application to return the context
*
* @return the application context
*/
public static Application getContext() {
return self;
}
/**
* Determines if we are running on release or debug
*
* @return true if we are running Debug
* @throws Exception
*/
public static boolean isDebugBuild() {
PackageManager pm = getContext().getPackageManager();
PackageInfo pi;
try {
pi = pm.getPackageInfo(getContext().getPackageName(), 0);
return ((pi.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0);
} catch (NameNotFoundException e) {
e.printStackTrace();
return false;
}
}
public static AndroidHttpClient getHttpClient() {
if (mHttpClient == null)
mHttpClient = AndroidHttpClient.newInstance(Build.MANUFACTURER + " " + Build.MODEL
+ " (" + Build.VERSION.RELEASE + ")");
return mHttpClient;
}
public static AccountManager getAccountManager() {
if(mAccountManager == null)
mAccountManager = AccountManager.get(self);
return mAccountManager;
}
}