diff options
Diffstat (limited to 'mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueueService.java')
-rw-r--r-- | mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueueService.java | 342 |
1 files changed, 0 insertions, 342 deletions
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueueService.java b/mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueueService.java deleted file mode 100644 index ebb1bd761..000000000 --- a/mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueueService.java +++ /dev/null @@ -1,342 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.gecko.tabqueue; - -import org.mozilla.gecko.AppConstants; -import org.mozilla.gecko.GeckoProfile; -import org.mozilla.gecko.GeckoSharedPrefs; -import org.mozilla.gecko.R; -import org.mozilla.gecko.Telemetry; -import org.mozilla.gecko.TelemetryContract; -import org.mozilla.gecko.preferences.GeckoPreferences; - -import android.annotation.TargetApi; -import android.app.Notification; -import android.app.PendingIntent; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.res.Resources; -import android.graphics.PixelFormat; -import android.net.Uri; -import android.os.Build; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.IBinder; -import android.provider.Settings; -import android.support.v4.app.NotificationCompat; -import android.support.v4.app.NotificationManagerCompat; -import android.text.TextUtils; -import android.util.Log; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.WindowManager; -import android.widget.Button; -import android.widget.TextView; -import android.widget.Toast; -import org.mozilla.gecko.mozglue.SafeIntent; - -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - - -/** - * On launch this Service displays a View over the currently running process with an action to open the url in Fennec - * immediately. If the user takes no action, allowing the runnable to be processed after the specified - * timeout (TOAST_TIMEOUT), the url is added to a file which is then read in Fennec on next launch, this allows the - * user to quickly queue urls to open without having to open Fennec each time. If the Service receives an Intent whilst - * the created View is still active, the old url is immediately processed and the View is re-purposed with the new - * Intent data. - * <p/> - * The SYSTEM_ALERT_WINDOW permission is used to allow us to insert a View from this Service which responds to user - * interaction, whilst still allowing whatever is in the background to be seen and interacted with. - * <p/> - * Using an Activity to do this doesn't seem to work as there's an issue to do with the native android intent resolver - * dialog not being hidden when the toast is shown. Using an IntentService instead of a Service doesn't work as - * each new Intent received kicks off the IntentService lifecycle anew which means that a new View is created each time, - * meaning that we can't quickly queue the current data and re-purpose the View. The asynchronous nature of the - * IntentService is another prohibitive factor. - * <p/> - * General approach taken is similar to the FB chat heads functionality: - * http://stackoverflow.com/questions/15975988/what-apis-in-android-is-facebook-using-to-create-chat-heads - */ -public class TabQueueService extends Service { - private static final String LOGTAG = "Gecko" + TabQueueService.class.getSimpleName(); - - private static final long TOAST_TIMEOUT = 3000; - private static final long TOAST_DOUBLE_TAP_TIMEOUT_MILLIS = 6000; - - private WindowManager windowManager; - private View toastLayout; - private Button openNowButton; - private Handler tabQueueHandler; - private WindowManager.LayoutParams toastLayoutParams; - private volatile StopServiceRunnable stopServiceRunnable; - private HandlerThread handlerThread; - private ExecutorService executorService; - - @Override - public IBinder onBind(Intent intent) { - // Not used - return null; - } - - @Override - public void onCreate() { - super.onCreate(); - executorService = Executors.newSingleThreadExecutor(); - - handlerThread = new HandlerThread("TabQueueHandlerThread"); - handlerThread.start(); - tabQueueHandler = new Handler(handlerThread.getLooper()); - - windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); - - LayoutInflater layoutInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); - toastLayout = layoutInflater.inflate(R.layout.tab_queue_toast, null); - - final Resources resources = getResources(); - - TextView messageView = (TextView) toastLayout.findViewById(R.id.toast_message); - messageView.setText(resources.getText(R.string.tab_queue_toast_message)); - - openNowButton = (Button) toastLayout.findViewById(R.id.toast_button); - openNowButton.setText(resources.getText(R.string.tab_queue_toast_action)); - - toastLayoutParams = new WindowManager.LayoutParams( - WindowManager.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.WRAP_CONTENT, - WindowManager.LayoutParams.TYPE_PHONE, - WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | - WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, - PixelFormat.TRANSLUCENT); - - toastLayoutParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; - } - - @Override - public int onStartCommand(final Intent intent, final int flags, final int startId) { - // If this is a redelivery then lets bypass the entire double tap to open now code as that's a big can of worms, - // we also don't expect redeliveries because of the short time window associated with this feature. - if (flags != START_FLAG_REDELIVERY) { - final Context applicationContext = getApplicationContext(); - final SharedPreferences sharedPreferences = GeckoSharedPrefs.forApp(applicationContext); - - final String lastUrl = sharedPreferences.getString(GeckoPreferences.PREFS_TAB_QUEUE_LAST_SITE, ""); - - final SafeIntent safeIntent = new SafeIntent(intent); - final String intentUrl = safeIntent.getDataString(); - - final long lastRunTime = sharedPreferences.getLong(GeckoPreferences.PREFS_TAB_QUEUE_LAST_TIME, 0); - final boolean isWithinDoubleTapTimeLimit = System.currentTimeMillis() - lastRunTime < TOAST_DOUBLE_TAP_TIMEOUT_MILLIS; - - if (!TextUtils.isEmpty(lastUrl) && lastUrl.equals(intentUrl) && isWithinDoubleTapTimeLimit) { - // Background thread because we could do some file IO if we have to remove a url from the list. - tabQueueHandler.post(new Runnable() { - @Override - public void run() { - // If there is a runnable around, that means that the previous process hasn't yet completed, so - // we will need to prevent it from running and remove the view from the window manager. - // If there is no runnable around then the url has already been added to the list, so we'll - // need to remove it before proceeding or that url will open multiple times. - if (stopServiceRunnable != null) { - tabQueueHandler.removeCallbacks(stopServiceRunnable); - stopSelfResult(stopServiceRunnable.getStartId()); - stopServiceRunnable = null; - removeView(); - } else { - TabQueueHelper.removeURLFromFile(applicationContext, intentUrl, TabQueueHelper.FILE_NAME); - } - openNow(safeIntent.getUnsafe()); - - Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, "tabqueue-doubletap"); - stopSelfResult(startId); - } - }); - - return START_REDELIVER_INTENT; - } - - sharedPreferences.edit().putString(GeckoPreferences.PREFS_TAB_QUEUE_LAST_SITE, intentUrl) - .putLong(GeckoPreferences.PREFS_TAB_QUEUE_LAST_TIME, System.currentTimeMillis()) - .apply(); - } - - if (stopServiceRunnable != null) { - // If we're already displaying a toast, keep displaying it but store the previous url. - // The open button will refer to the most recently opened link. - tabQueueHandler.removeCallbacks(stopServiceRunnable); - stopServiceRunnable.run(false); - } else { - try { - windowManager.addView(toastLayout, toastLayoutParams); - } catch (final SecurityException | WindowManager.BadTokenException e) { - Toast.makeText(this, getText(R.string.tab_queue_toast_message), Toast.LENGTH_SHORT).show(); - showSettingsNotification(); - } - } - - stopServiceRunnable = new StopServiceRunnable(startId) { - @Override - public void onRun() { - addURLToTabQueue(intent, TabQueueHelper.FILE_NAME); - stopServiceRunnable = null; - } - }; - - openNowButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(final View view) { - tabQueueHandler.removeCallbacks(stopServiceRunnable); - stopServiceRunnable = null; - removeView(); - openNow(intent); - - Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, "tabqueue-now"); - stopSelfResult(startId); - } - }); - - tabQueueHandler.postDelayed(stopServiceRunnable, TOAST_TIMEOUT); - - return START_REDELIVER_INTENT; - } - - private void openNow(Intent intent) { - Intent forwardIntent = new Intent(intent); - forwardIntent.setClassName(getApplicationContext(), AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS); - forwardIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(forwardIntent); - - TabQueueHelper.removeNotification(getApplicationContext()); - - GeckoSharedPrefs.forApp(getApplicationContext()).edit().remove(GeckoPreferences.PREFS_TAB_QUEUE_LAST_SITE) - .remove(GeckoPreferences.PREFS_TAB_QUEUE_LAST_TIME) - .apply(); - - executorService.submit(new Runnable() { - @Override - public void run() { - int queuedTabCount = TabQueueHelper.getTabQueueLength(TabQueueService.this); - Telemetry.addToHistogram("FENNEC_TABQUEUE_QUEUESIZE", queuedTabCount); - } - }); - - } - - @TargetApi(Build.VERSION_CODES.M) - private void showSettingsNotification() { - if (AppConstants.Versions.preMarshmallow) { - return; - } - - final Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); - intent.setData(Uri.parse("package:" + getPackageName())); - PendingIntent pendingIntent = PendingIntent.getActivity(this, intent.hashCode(), intent, 0); - - final String text = getString(R.string.tab_queue_notification_settings); - - final NotificationCompat.BigTextStyle style = new NotificationCompat.BigTextStyle() - .bigText(text); - - final Notification notification = new NotificationCompat.Builder(this) - .setContentTitle(getString(R.string.pref_tab_queue_title)) - .setContentText(text) - .setCategory(NotificationCompat.CATEGORY_ERROR) - .setStyle(style) - .setSmallIcon(R.drawable.ic_status_logo) - .setContentIntent(pendingIntent) - .setPriority(NotificationCompat.PRIORITY_MAX) - .setAutoCancel(true) - .addAction(R.drawable.ic_action_settings, getString(R.string.tab_queue_prompt_settings_button), pendingIntent) - .build(); - - NotificationManagerCompat.from(this).notify(R.id.tabQueueSettingsNotification, notification); - } - - private void removeView() { - try { - windowManager.removeView(toastLayout); - } catch (IllegalArgumentException | IllegalStateException e) { - // This can happen if the Service is killed by the system. If this happens the View will have already - // been removed but the runnable will have been kept alive. - Log.e(LOGTAG, "Error removing Tab Queue toast from service", e); - } - } - - private void addURLToTabQueue(final Intent intent, final String filename) { - if (intent == null) { - // This should never happen, but let's return silently instead of crashing if it does. - Log.w(LOGTAG, "Error adding URL to tab queue - invalid intent passed in."); - return; - } - final SafeIntent safeIntent = new SafeIntent(intent); - final String intentData = safeIntent.getDataString(); - - // As we're doing disk IO, let's run this stuff in a separate thread. - executorService.submit(new Runnable() { - @Override - public void run() { - Context applicationContext = getApplicationContext(); - final GeckoProfile profile = GeckoProfile.get(applicationContext); - int tabsQueued = TabQueueHelper.queueURL(profile, intentData, filename); - List<String> urls = TabQueueHelper.getLastURLs(applicationContext, filename); - - TabQueueHelper.showNotification(applicationContext, tabsQueued, urls); - - // Store the number of URLs queued so that we don't have to read and process the file to see if we have - // any urls to open. - // TODO: Use profile shared prefs when bug 1147925 gets fixed. - final SharedPreferences prefs = GeckoSharedPrefs.forApp(applicationContext); - - prefs.edit().putInt(TabQueueHelper.PREF_TAB_QUEUE_COUNT, tabsQueued).apply(); - } - }); - } - - @Override - public void onDestroy() { - super.onDestroy(); - handlerThread.quit(); - } - - /** - * A modified Runnable which additionally removes the view from the window view hierarchy and stops the service - * when run, unless explicitly instructed not to. - */ - private abstract class StopServiceRunnable implements Runnable { - - private final int startId; - - public StopServiceRunnable(final int startId) { - this.startId = startId; - } - - public void run() { - run(true); - } - - public void run(final boolean shouldRemoveView) { - onRun(); - - if (shouldRemoveView) { - removeView(); - } - - stopSelfResult(startId); - } - - public int getStartId() { - return startId; - } - - public abstract void onRun(); - } -}
\ No newline at end of file |