summaryrefslogtreecommitdiffstats
path: root/mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueueService.java
diff options
context:
space:
mode:
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.java342
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