/* * Copyright 2012 The Stanford MobiSocial Laboratory * * 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 mobisocial.musubi; import mobisocial.metrics.MusubiExceptionHandler; import mobisocial.metrics.UsageMetrics; import mobisocial.metrics.UsageMetrics.ReportingLevel; import mobisocial.musubi.model.helpers.DatabaseFile; import mobisocial.musubi.model.helpers.DatabaseManager; import mobisocial.musubi.model.helpers.FeedManager; import mobisocial.musubi.objects.ProfileObj; import mobisocial.musubi.provider.DBProvider; import mobisocial.musubi.provider.MusubiContentProvider; import mobisocial.musubi.provider.MusubiContentProvider.Provided; import mobisocial.musubi.provider.MusubiProvider; import mobisocial.musubi.provider.TestSettingsProvider; import mobisocial.musubi.provider.UICacheProvider; import mobisocial.musubi.service.MusubiService; import mobisocial.musubi.ui.MusubiBaseActivity; import mobisocial.musubi.ui.SettingsActivity; import mobisocial.musubi.util.IdentityCache; import mobisocial.socialkit.Obj; import mobisocial.socialkit.User; import mobisocial.socialkit.musubi.Musubi; import android.app.Application; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.database.sqlite.SQLiteOpenHelper; import android.net.Uri; import android.os.AsyncTask; import android.util.Log; public class App extends Application implements DBProvider, MusubiProvider, UICacheProvider { /** * The protocol version we speak, affecting things like wire protocol * format and physical network support, available features, app api, etc. */ public static final String PREF_POSI_VERSION = "posi_version"; public static final int POSI_VERSION = 5; public final static String TAG = "musubi"; private IdentityCache mContactCache; private ScreenState mScreenState; private static Uri sCurrentFeedUri; private Musubi mMusubi; private SQLiteOpenHelper mDatabaseSource; private DatabaseManager mDatabaseManager; private Object mDatabaseCreationLog; public static Obj clipboardObject; @Override public synchronized SQLiteOpenHelper getDatabaseSource() { if (mDatabaseSource == null) { synchronized (this) { if (mDatabaseSource == null) { mDatabaseSource = new DatabaseFile(getApplicationContext()); mDatabaseCreationLog = new Throwable(); } } } return mDatabaseSource; } DatabaseManager getDatabaseManager() { if (mDatabaseManager == null) { mDatabaseManager = new DatabaseManager(getDatabaseSource()); } return mDatabaseManager; } public static SQLiteOpenHelper getDatabaseSource(Context c) { Context app_as_context = c.getApplicationContext(); if(app_as_context instanceof DBProvider) { return ((DBProvider)app_as_context).getDatabaseSource(); } else { throw new RuntimeException("application or mock missing database source"); } } public static UsageMetrics getUsageMetrics(Context c) { UsageMetrics m; if (MusubiBaseActivity.isDeveloperModeEnabled(c)) { User u = getMusubi(c).userForLocalDevice(null); m = new UsageMetrics(c, u); } else { m = new UsageMetrics(c); } SharedPreferences p = c.getSharedPreferences(SettingsActivity.PREFS_NAME, 0); if (!p.getBoolean(SettingsActivity.PREF_ANONYMOUS_STATS, true)) { m.setReportingLevel(ReportingLevel.DISABLED); } return m; } public static Musubi getMusubi(Context c) { Context app_as_context = c.getApplicationContext(); if(app_as_context instanceof MusubiProvider) { return ((MusubiProvider)app_as_context).getMusubi(); } else { throw new RuntimeException("application or mock missing musubi source"); } } public static Uri getCurrentFeed() { return sCurrentFeedUri; } public static void setCurrentFeed(Context context, Uri feedUri) { sCurrentFeedUri = feedUri; if (MusubiBaseActivity.DBG) { Log.d(TAG, "Setting current feed " + feedUri); } if (feedUri != null) { new UnreadMessageUpdateTask(context).execute(feedUri); } } static class UnreadMessageUpdateTask extends AsyncTask<Uri, Void, Void> { final DatabaseManager mDbManager; final ContentResolver mResolver; public UnreadMessageUpdateTask(Context context) { Context appContext = context.getApplicationContext(); if (appContext instanceof App) { mDbManager = ((App)appContext).getDatabaseManager(); } else { throw new RuntimeException("UnreadMessageUpdateTask from non-App context"); } mResolver = context.getContentResolver(); } @Override protected Void doInBackground(Uri... params) { // Strongly correlated with UI activity. Delay a bit with hopes of // letting UI-driven db queries go first. /*try { Thread.sleep(2000); } catch (InterruptedException e) {}*/ FeedManager fm = mDbManager.getFeedManager(); for (Uri uri : params) { long feedId; try { feedId = Long.parseLong(uri.getLastPathSegment()); } catch (NumberFormatException e) { Log.w(TAG, "bad feed url " + uri); continue; } if (fm.getUnreadMessageCount(feedId) > 0) { if (fm.resetUnreadMessageCount(uri)) { mResolver.notifyChange(MusubiContentProvider.uriForDir(Provided.FEEDS), null); mResolver.notifyChange(uri, null); } } } return null; } @Override protected void onPostExecute(Void result) { mDbManager.close(); } } public static IdentityCache getContactCache(Context c) { Context app_as_context = c.getApplicationContext(); if(app_as_context instanceof UICacheProvider) { return ((UICacheProvider)app_as_context).getContactCache(); } else { throw new RuntimeException("application or mock missing ui cache source"); } } public static TestSettingsProvider.Settings getTestSettings(Context c) { Context app_as_context = c.getApplicationContext(); if(app_as_context instanceof TestSettingsProvider) { Log.w(TAG, "using test settings from context"); return ((TestSettingsProvider)app_as_context).getSettings(); } return null; } public boolean isScreenOn(){ return !mScreenState.isOff; } @Override public void onCreate() { super.onCreate(); if (mDatabaseCreationLog != null) { synchronized(this) { Log.e(TAG, "Database created prior to App.onCreate(). This is a serious error.", (Throwable)mDatabaseCreationLog); mDatabaseCreationLog = new Object(); } } mMusubi = new Musubi(getApplicationContext()); // Exception handler MusubiExceptionHandler.installHandler(getApplicationContext()); IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_SCREEN_OFF); mScreenState = new ScreenState(); getApplicationContext().registerReceiver(mScreenState, filter); // Sync profile information. SharedPreferences prefs = getSharedPreferences("main", 0); int oldVersion = prefs.getInt(PREF_POSI_VERSION, 0); if (oldVersion < POSI_VERSION) { prefs.edit().putInt(PREF_POSI_VERSION, POSI_VERSION).commit(); Obj updateObj = ProfileObj.getUserAttributesObj(this); Log.d(TAG, "Broadcasting new profile attributes: " + updateObj.getJson()); getContentResolver().notifyChange(MusubiService.MY_PROFILE_UPDATED, null); } } @Override public void onTerminate() { super.onTerminate(); } private class ScreenState extends BroadcastReceiver { public boolean isOff = false; @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { isOff = true; } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) { isOff = false; } } } public Musubi getMusubi() { return mMusubi; } public IdentityCache getContactCache() { if (mContactCache == null) { mContactCache = new IdentityCache(this); } return mContactCache; } @Override public void onLowMemory() { Log.d(TAG, "++++ low system memory ++++"); if (mContactCache != null) { mContactCache.evictAll(); } } }