package com.ichi2.anki; import android.annotation.SuppressLint; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Color; import android.media.AudioManager; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v4.app.LoaderManager; import android.support.v4.app.NotificationCompat; import android.support.v4.content.ContextCompat; import android.support.v4.content.IntentCompat; import android.support.v4.content.Loader; import android.support.v7.app.AppCompatActivity; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup.LayoutParams; import android.view.animation.Animation; import android.widget.ProgressBar; import com.ichi2.anim.ActivityTransitionAnimation; import com.ichi2.anki.dialogs.AsyncDialogFragment; import com.ichi2.anki.dialogs.DialogHandler; import com.ichi2.anki.dialogs.SimpleMessageDialog; import com.ichi2.async.CollectionLoader; import com.ichi2.compat.CompatHelper; import com.ichi2.compat.customtabs.CustomTabActivityHelper; import com.ichi2.libanki.Collection; import com.ichi2.themes.Themes; import timber.log.Timber; public class AnkiActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Collection>, SimpleMessageDialog.SimpleMessageDialogListener { public final int SIMPLE_NOTIFICATION_ID = 0; public static final int REQUEST_REVIEW = 901; private DialogHandler mHandler = new DialogHandler(this); // custom tabs private CustomTabActivityHelper mCustomTabActivityHelper; @Override protected void onCreate(Bundle savedInstanceState) { // The hardware buttons should control the music volume setVolumeControlStream(AudioManager.STREAM_MUSIC); // Set the theme Themes.setTheme(this); super.onCreate(savedInstanceState); mCustomTabActivityHelper = new CustomTabActivityHelper(); } @Override protected void onStart() { super.onStart(); mCustomTabActivityHelper.bindCustomTabsService(this); } @Override protected void onStop() { super.onStop(); mCustomTabActivityHelper.unbindCustomTabsService(this); } @Override protected void onResume() { super.onResume(); ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).cancel(SIMPLE_NOTIFICATION_ID); // Show any pending dialogs which were stored persistently mHandler.readMessage(); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: Timber.i("Home button pressed"); finishWithoutAnimation(); return true; default: return super.onOptionsItemSelected(item); } } // called when the CollectionLoader finishes... usually will be over-ridden protected void onCollectionLoaded(Collection col) { } public Collection getCol() { return CollectionHelper.getInstance().getCol(this); } public boolean colIsOpen() { return CollectionHelper.getInstance().colIsOpen(); } public boolean animationDisabled() { SharedPreferences preferences = AnkiDroidApp.getSharedPrefs(this); return preferences.getBoolean("eInkDisplay", false); } public boolean animationEnabled() { return !animationDisabled(); } @Override public void setContentView(View view) { if (animationDisabled()) { view.clearAnimation(); } super.setContentView(view); } @Override public void setContentView(View view, LayoutParams params) { if (animationDisabled()) { view.clearAnimation(); } super.setContentView(view, params); } @Override public void addContentView(View view, LayoutParams params) { if (animationDisabled()) { view.clearAnimation(); } super.addContentView(view, params); } @Deprecated @Override public void startActivity(Intent intent) { super.startActivity(intent); } public void startActivityWithoutAnimation(Intent intent) { disableIntentAnimation(intent); super.startActivity(intent); disableActivityAnimation(); } public void startActivityWithAnimation(Intent intent, int animation) { enableIntentAnimation(intent); super.startActivity(intent); enableActivityAnimation(animation); } @Deprecated @Override public void startActivityForResult(Intent intent, int requestCode) { super.startActivityForResult(intent, requestCode); } public void startActivityForResultWithoutAnimation(Intent intent, int requestCode) { disableIntentAnimation(intent); super.startActivityForResult(intent, requestCode); disableActivityAnimation(); } public void startActivityForResultWithAnimation(Intent intent, int requestCode, int animation) { enableIntentAnimation(intent); super.startActivityForResult(intent, requestCode); enableActivityAnimation(animation); } @Deprecated @Override public void finish() { super.finish(); } public void finishWithoutAnimation() { super.finish(); disableActivityAnimation(); } public void finishWithAnimation(int animation) { super.finish(); enableActivityAnimation(animation); } protected void disableViewAnimation(View view) { view.clearAnimation(); } protected void enableViewAnimation(View view, Animation animation) { if (animationDisabled()) { disableViewAnimation(view); } else { view.setAnimation(animation); } } private void disableIntentAnimation(Intent intent) { intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); } private void disableActivityAnimation() { ActivityTransitionAnimation.slide(this, ActivityTransitionAnimation.NONE); } private void enableIntentAnimation(Intent intent) { if (animationDisabled()) { disableIntentAnimation(intent); } else { // Nothing for now } } private void enableActivityAnimation(int animation) { if (animationDisabled()) { disableActivityAnimation(); } else { ActivityTransitionAnimation.slide(this, animation); } } // Method for loading the collection which is inherited by all AnkiActivitys public void startLoadingCollection() { // Initialize the open collection loader Timber.d("AnkiActivity.startLoadingCollection()"); if (!colIsOpen()) { showProgressBar(); } getSupportLoaderManager().restartLoader(0, null, this); } // Kick user back to DeckPicker on collection load error unless this method is overridden protected void onCollectionLoadError() { Intent deckPicker = new Intent(this, DeckPicker.class); deckPicker.putExtra("collectionLoadError", true); // don't currently do anything with this deckPicker.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); startActivityWithAnimation(deckPicker, ActivityTransitionAnimation.LEFT); } // CollectionLoader Listener callbacks @Override public Loader<Collection> onCreateLoader(int id, Bundle args) { // Currently only using one loader, so ignore id return new CollectionLoader(this); } @Override public void onLoadFinished(Loader<Collection> loader, Collection col) { hideProgressBar(); if (col != null && colIsOpen()) { onCollectionLoaded(col); } else { onCollectionLoadError(); } } @Override public void onLoaderReset(Loader<Collection> arg0) { // We don't currently retain any references, so no need to free any data here } public void showProgressBar() { ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress_bar); if (progressBar != null) { progressBar.setVisibility(View.VISIBLE); } } public void hideProgressBar() { ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress_bar); if (progressBar != null) { progressBar.setVisibility(View.GONE); } } protected void mayOpenUrl(Uri url) { boolean success = mCustomTabActivityHelper.mayLaunchUrl(url, null, null); if (!success) { Timber.w("Couldn't preload url: %s", url.toString()); } } protected void openUrl(Uri url) { CompatHelper.getCompat().openUrl(this, url); } public CustomTabActivityHelper getCustomTabActivityHelper() { return mCustomTabActivityHelper; } /** * Global method to show dialog fragment including adding it to back stack Note: DO NOT call this from an async * task! If you need to show a dialog from an async task, use showAsyncDialogFragment() * * @param newFragment the DialogFragment you want to show */ public void showDialogFragment(DialogFragment newFragment) { // DialogFragment.show() will take care of adding the fragment // in a transaction. We also want to remove any currently showing // dialog, so make our own transaction and take care of that here. FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); Fragment prev = getSupportFragmentManager().findFragmentByTag("dialog"); if (prev != null) { ft.remove(prev); } // save transaction to the back stack ft.addToBackStack("dialog"); newFragment.show(ft, "dialog"); getSupportFragmentManager().executePendingTransactions(); } /** * Global method to show a dialog fragment including adding it to back stack and handling the case where the dialog * is shown from an async task, by showing the message in the notification bar if the activity was stopped before the * AsyncTask completed * * @param newFragment the AsyncDialogFragment you want to show */ public void showAsyncDialogFragment(AsyncDialogFragment newFragment) { try { showDialogFragment(newFragment); } catch (IllegalStateException e) { // Store a persistent message to SharedPreferences instructing AnkiDroid to show dialog DialogHandler.storeMessage(newFragment.getDialogHandlerMessage()); // Show a basic notification to the user in the notification bar in the meantime String title = newFragment.getNotificationTitle(); String message = newFragment.getNotificationMessage(); showSimpleNotification(title, message); } } /** * Show a simple message dialog, dismissing the message without taking any further action when OK button is pressed. * If a DialogFragment cannot be shown due to the Activity being stopped then the message is shown in the * notification bar instead. * * @param message */ protected void showSimpleMessageDialog(String message) { showSimpleMessageDialog(message, false); } protected void showSimpleMessageDialog(String title, String message){ showSimpleMessageDialog(title, message, false); } /** * Show a simple message dialog, dismissing the message without taking any further action when OK button is pressed. * If a DialogFragment cannot be shown due to the Activity being stopped then the message is shown in the * notification bar instead. * * @param message * @param reload flag which forces app to be restarted when true */ protected void showSimpleMessageDialog(String message, boolean reload) { AsyncDialogFragment newFragment = SimpleMessageDialog.newInstance(message, reload); showAsyncDialogFragment(newFragment); } protected void showSimpleMessageDialog(String title, String message, boolean reload) { AsyncDialogFragment newFragment = SimpleMessageDialog.newInstance(title, message, reload); showAsyncDialogFragment(newFragment); } public void showSimpleNotification(String title, String message) { SharedPreferences prefs = AnkiDroidApp.getSharedPrefs(this); // Don't show notification if disabled in preferences if (Integer.parseInt(prefs.getString("minimumCardsDueForNotification", "0")) <= 1000000) { // Use the title as the ticker unless the title is simply "AnkiDroid" String ticker = title; if (title.equals(getResources().getString(R.string.app_name))) { ticker = message; } // Build basic notification NotificationCompat.Builder builder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_stat_notify) .setContentTitle(title) .setContentText(message) .setColor(ContextCompat.getColor(this, R.color.material_light_blue_500)) .setStyle(new NotificationCompat.BigTextStyle().bigText(message)) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setTicker(ticker); // Enable vibrate and blink if set in preferences if (prefs.getBoolean("widgetVibrate", false)) { builder.setVibrate(new long[] { 1000, 1000, 1000}); } if (prefs.getBoolean("widgetBlink", false)) { builder.setLights(Color.BLUE, 1000, 1000); } // Creates an explicit intent for an Activity in your app Intent resultIntent = new Intent(this, DeckPicker.class); resultIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | IntentCompat.FLAG_ACTIVITY_CLEAR_TASK); PendingIntent resultPendingIntent = PendingIntent.getActivity(this, 0, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT); builder.setContentIntent(resultPendingIntent); NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); // mId allows you to update the notification later on. notificationManager.notify(SIMPLE_NOTIFICATION_ID, builder.build()); } } public DialogHandler getDialogHandler() { return mHandler; } // Handle closing simple message dialog @Override public void dismissSimpleMessageDialog(boolean reload) { dismissAllDialogFragments(); if (reload) { Intent deckPicker = new Intent(this, DeckPicker.class); deckPicker.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); startActivityWithoutAnimation(deckPicker); } } // Dismiss whatever dialog is showing public void dismissAllDialogFragments() { getSupportFragmentManager().popBackStack("dialog", FragmentManager.POP_BACK_STACK_INCLUSIVE); } // Restart the activity @SuppressLint("NewApi") public void restartActivity() { Timber.i("AnkiActivity -- restartActivity()"); Intent intent = new Intent(); intent.setClass(this, this.getClass()); intent.putExtras(new Bundle()); this.startActivityWithoutAnimation(intent); this.finishWithoutAnimation(); } }