/* * 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 java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import mobisocial.metrics.UsageMetrics; import mobisocial.musubi.model.helpers.DatabaseFile; import mobisocial.musubi.model.helpers.EncodedMessageManager; import mobisocial.musubi.service.AddressBookUpdateHandler; import mobisocial.musubi.service.MusubiService; import mobisocial.musubi.ui.SettingsActivity; import mobisocial.musubi.ui.fragments.EulaFragment; import mobisocial.musubi.util.CertifiedHttpClient; import mobisocial.socialkit.musubi.Musubi; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import org.apache.http.params.HttpProtocolParams; import org.json.JSONException; import org.json.JSONObject; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.database.ContentObserver; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.media.RingtoneManager; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.os.Parcelable; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.util.Log; import android.widget.TextView; public class BootstrapActivity extends FragmentActivity { static final String TAG = BootstrapActivity.class.getSimpleName(); public static final String EXTRA_ORIGINAL_INTENT = "original-intent"; public static final String PREF_INSTALLED_VERSION = "installed_version"; public static final String PREF_LAST_UPDATE_CHECK = "version_checked"; static final long ONE_DAY = 86400000; static boolean sBootstrapped = false; boolean mNeedsEULA; boolean mNeedsIntro; boolean mNeedsFriends; EulaFragment.Callback mEulaCallback = new EulaFragment.Callback() { @Override public void eulaResult(boolean accepted) { if(accepted) { EulaFragment.acceptedEULA(BootstrapActivity.this); mNeedsEULA = false; } else { setResult(RESULT_CANCELED); finish(); } } }; /** * finishes the caller if bootstrapping is necessary * @param activity * @return true if the bootstrap activity will be started */ public static boolean bootstrapIfNecessary(Activity activity) { if (sBootstrapped) { return false; } Intent intent = new Intent(activity, BootstrapActivity.class); intent.putExtra(EXTRA_ORIGINAL_INTENT, (Parcelable)activity.getIntent()); intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); activity.startActivity(intent); activity.finish(); return true; } public static boolean isBootstrapped() { return sBootstrapped; } @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); new InitializeMusubiTask().execute(); } //TODO: do these settings really belong here? maybe... //TODO: add these settings to the database instead? private void ensureRingtone() { SharedPreferences settings = getSharedPreferences(SettingsActivity.PREFS_NAME, 0); if (settings.getString("ringtone", null) != null) { return; } RingtoneManager ringtoneManager = new RingtoneManager(this); ringtoneManager.setType(RingtoneManager.TYPE_NOTIFICATION); String ringtoneUri = null; String backupUri = null; Cursor cursor = ringtoneManager.getCursor(); try { cursor.moveToFirst(); while (!cursor.isAfterLast()) { String ringtone = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX); if (ringtone.equalsIgnoreCase("dDeneb")) { ringtoneUri = cursor.getString(RingtoneManager.URI_COLUMN_INDEX) + "/" + cursor.getString(RingtoneManager.ID_COLUMN_INDEX); break; } if (backupUri == null) { backupUri = cursor.getString(RingtoneManager.URI_COLUMN_INDEX) + "/" + cursor.getString(RingtoneManager.ID_COLUMN_INDEX); } cursor.moveToNext(); } } finally { cursor.deactivate(); } SharedPreferences.Editor editor = settings.edit(); if (ringtoneUri != null) { editor.putString("ringtone", ringtoneUri); } else { if (backupUri != null) { editor.putString("ringtone", backupUri.toString()); } else { editor.putString("ringtone", "none"); } } editor.commit(); } Integer getRequiredVersion() { if (UsageMetrics.CHIRP_VERSIONING_ENDPOINT == null) { return null; } HttpClient http = new CertifiedHttpClient(this); HttpParams params = http.getParams(); HttpProtocolParams.setUseExpectContinue(params, false); HttpConnectionParams.setConnectionTimeout(params, 6000); HttpConnectionParams.setSoTimeout(params, 6000); HttpGet get = new HttpGet(UsageMetrics.CHIRP_VERSIONING_ENDPOINT); try { HttpResponse response = http.execute(get); BufferedReader in = new BufferedReader(new InputStreamReader( response.getEntity().getContent())); StringBuffer sb = new StringBuffer(""); String line; while ((line = in.readLine()) != null) { sb.append(line); } in.close(); JSONObject json = new JSONObject(sb.toString()); return json.getInt("required"); } catch (IOException e) { Log.e(TAG, "Error getting versioning info", e); return null; } catch (JSONException e) { Log.e(TAG, "Bad versioning format", e); return null; } } /** * Attempts to make a database connection in order to trigger a * database upgrade. Before doing so, we display a DialogFragment * alerting the user of an upgrade, and afterwards we dismiss the dialog. * The dialog is not displayed if the connection is done quickly. */ class InitializeMusubiTask extends AsyncTask<Void, String, Integer> { final Integer RESULT_INITIALIZED_OK = 0; final Integer RESULT_NEEDS_UPGRADE = 1; SharedPreferences mPreferences; int mInstalledVersion; int mPreviousVersion; boolean mNeedsVersionCheck; boolean mVersionChanged; boolean mQuickBootsrap = false; @Override protected void onPreExecute() { mPreferences = getSharedPreferences(SettingsActivity.PREFS_NAME, 0); mPreviousVersion = mPreferences.getInt(PREF_INSTALLED_VERSION, 0); try { PackageInfo info = getPackageManager().getPackageInfo(getPackageName(), 0); mInstalledVersion = info.versionCode; } catch (NameNotFoundException e) { throw new RuntimeException("Couldn't find my own package", e); } long time = System.currentTimeMillis(); long versionChecked = mPreferences.getLong(PREF_LAST_UPDATE_CHECK, 0); mNeedsVersionCheck = (time - versionChecked > ONE_DAY); mVersionChanged = (mPreviousVersion != mInstalledVersion); mNeedsEULA = EulaFragment.needsEULA(BootstrapActivity.this); mQuickBootsrap = !(mNeedsVersionCheck || mVersionChanged || mNeedsEULA); if (!mQuickBootsrap) { setContentView(R.layout.splash); } } @Override protected Integer doInBackground(Void... params) { SQLiteOpenHelper databaseSource = App.getDatabaseSource(BootstrapActivity.this); if (databaseSource instanceof DatabaseFile) { DatabaseFile dbf = (DatabaseFile)databaseSource; dbf.setActivityForEmergencyUI(BootstrapActivity.this); } if (mQuickBootsrap) { return RESULT_INITIALIZED_OK; } publishProgress("Loading..."); if (mNeedsVersionCheck) { publishProgress("Checking for updates..."); // Make sure this version is okay to use Integer requiredVersion = getRequiredVersion(); if (requiredVersion != null && mInstalledVersion < requiredVersion) { // Update required. return RESULT_NEEDS_UPGRADE; } if (requiredVersion != null) { // verified latest version. long time = System.currentTimeMillis(); mPreferences.edit().putLong(PREF_LAST_UPDATE_CHECK, time).commit(); } } if (!mVersionChanged) { // No need to check for further bootstrapping. // The goal is to entirely skip the splash screen even if the system // has killed our application. return RESULT_INITIALIZED_OK; } // Handle basic initial settings ensureRingtone(); // Force database upgrade publishProgress("Preparing database..."); databaseSource.getReadableDatabase(); // Require EULA boolean neededEula = mNeedsEULA; if (mNeedsEULA) { FragmentManager fm = getSupportFragmentManager(); EulaFragment eula = (EulaFragment)fm.findFragmentByTag("eula"); if (eula == null) { eula = new EulaFragment(true); eula.show(fm, "eula"); } eula.addCallback(mEulaCallback); } while (mNeedsEULA) { publishProgress("Laweyering up..."); try { Thread.sleep(100); } catch (InterruptedException e) { } } if (neededEula) { // wait for first message to become available mNeedsIntro = true; publishProgress("Preparing greeting..."); Uri firstPost = MusubiService.WIZARD_READY; ContentObserver intro = new ContentObserver(new Handler(getMainLooper())) { @Override public void onChange(boolean selfChange) { mNeedsIntro = false; getContentResolver().unregisterContentObserver(this); } }; getContentResolver().registerContentObserver(firstPost, false, intro); startService(new Intent(BootstrapActivity.this, MusubiService.class)); while (mNeedsIntro) { try { Thread.sleep(100); } catch (InterruptedException e) { } } } return RESULT_INITIALIZED_OK; } @Override protected void onPostExecute(Integer result) { if (result == RESULT_INITIALIZED_OK) { if (mPreviousVersion != mInstalledVersion) { mPreferences.edit().putInt(PREF_INSTALLED_VERSION, mInstalledVersion).commit(); } sBootstrapped = true; new CleanUpMusubiTask().execute(); Intent original = (Intent)getIntent().getParcelableExtra(EXTRA_ORIGINAL_INTENT); original.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); startActivity(original); finish(); } else if (result == RESULT_NEEDS_UPGRADE) { FragmentManager fm = getSupportFragmentManager(); UpgradeDialog upgrade = (UpgradeDialog)fm.findFragmentByTag("upgrade"); if (upgrade == null) { upgrade = UpgradeDialog.newInstance(); upgrade.show(fm, "upgrade"); } } } @Override protected void onProgressUpdate(String... values) { TextView loading_text_view = (TextView)findViewById(mobisocial.musubi.R.id.loading_text); loading_text_view.setText(values[0]); } } class CleanUpMusubiTask extends AsyncTask<Void, Void, Void> { @Override protected Void doInBackground(Void... params) { long startTime = System.currentTimeMillis(); SQLiteOpenHelper db = App.getDatabaseSource(getApplicationContext()); new EncodedMessageManager(db).deleteProcessedOldItems(7); long totalTime = System.currentTimeMillis() - startTime; Log.d(TAG, String.format("++++ Object truncation took %d ms.", totalTime)); return null; } } static class UpgradeDialog extends DialogFragment { public static UpgradeDialog newInstance() { Bundle b = new Bundle(); UpgradeDialog d = new UpgradeDialog(); d.setArguments(b); return d; } public UpgradeDialog() { } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { return new AlertDialog.Builder(getActivity()).setTitle("Upgrade Required") .setMessage("An important update is required to use Musubi. Upgrade now?") .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Intent market = Musubi.getMarketIntent(); getActivity().startActivity(market); getActivity().finish(); } }).setNegativeButton(R.string.no, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { getActivity().finish(); } }).create(); } } }