mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-03 22:38:41 +09:00
pt 1 in reviving the android build (copied from pm 28a1, wish me luck)
This commit is contained in:
parent
efa9662725
commit
d7788a6d6d
4249 changed files with 468189 additions and 0 deletions
|
|
@ -0,0 +1,357 @@
|
|||
/* -*- 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.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.preferences.GeckoPreferences;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TabQueueHelper {
|
||||
private static final String LOGTAG = "Gecko" + TabQueueHelper.class.getSimpleName();
|
||||
|
||||
// Disable Tab Queue for API level 10 (GB) - Bug 1206055
|
||||
public static final boolean TAB_QUEUE_ENABLED = true;
|
||||
|
||||
public static final String FILE_NAME = "tab_queue_url_list.json";
|
||||
public static final String LOAD_URLS_ACTION = "TAB_QUEUE_LOAD_URLS_ACTION";
|
||||
public static final int TAB_QUEUE_NOTIFICATION_ID = R.id.tabQueueNotification;
|
||||
|
||||
public static final String PREF_TAB_QUEUE_COUNT = "tab_queue_count";
|
||||
public static final String PREF_TAB_QUEUE_LAUNCHES = "tab_queue_launches";
|
||||
public static final String PREF_TAB_QUEUE_TIMES_PROMPT_SHOWN = "tab_queue_times_prompt_shown";
|
||||
|
||||
public static final int MAX_TIMES_TO_SHOW_PROMPT = 3;
|
||||
public static final int EXTERNAL_LAUNCHES_BEFORE_SHOWING_PROMPT = 3;
|
||||
|
||||
// result codes for returning from the prompt
|
||||
public static final int TAB_QUEUE_YES = 201;
|
||||
public static final int TAB_QUEUE_NO = 202;
|
||||
|
||||
/**
|
||||
* Checks if the specified context can draw on top of other apps. As of API level 23, an app
|
||||
* cannot draw on top of other apps unless it declares the SYSTEM_ALERT_WINDOW permission in
|
||||
* its manifest, AND the user specifically grants the app this capability.
|
||||
*
|
||||
* @return true if the specified context can draw on top of other apps, false otherwise.
|
||||
*/
|
||||
public static boolean canDrawOverlays(Context context) {
|
||||
if (AppConstants.Versions.preMarshmallow) {
|
||||
return true; // We got the permission at install time.
|
||||
}
|
||||
|
||||
// It would be nice to just use Settings.canDrawOverlays() - but this helper is buggy for
|
||||
// apps using sharedUserId (See bug 1244722).
|
||||
// Instead we'll add and remove an invisible view. If this is successful then we seem to
|
||||
// have permission to draw overlays.
|
||||
|
||||
View view = new View(context);
|
||||
view.setVisibility(View.INVISIBLE);
|
||||
|
||||
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
|
||||
1, 1,
|
||||
WindowManager.LayoutParams.TYPE_PHONE,
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
|
||||
PixelFormat.TRANSLUCENT);
|
||||
|
||||
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||
|
||||
try {
|
||||
windowManager.addView(view, layoutParams);
|
||||
windowManager.removeView(view);
|
||||
return true;
|
||||
} catch (final SecurityException | WindowManager.BadTokenException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we should show the tab queue prompt
|
||||
*
|
||||
* @param context
|
||||
* @return true if we should display the prompt, false if not.
|
||||
*/
|
||||
public static boolean shouldShowTabQueuePrompt(Context context) {
|
||||
final SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
|
||||
|
||||
int numberOfTimesTabQueuePromptSeen = prefs.getInt(PREF_TAB_QUEUE_TIMES_PROMPT_SHOWN, 0);
|
||||
|
||||
// Exit early if the feature is already enabled or the user has seen the
|
||||
// prompt more than MAX_TIMES_TO_SHOW_PROMPT times.
|
||||
if (isTabQueueEnabled(prefs) || numberOfTimesTabQueuePromptSeen >= MAX_TIMES_TO_SHOW_PROMPT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int viewActionIntentLaunches = prefs.getInt(PREF_TAB_QUEUE_LAUNCHES, 0) + 1;
|
||||
if (viewActionIntentLaunches < EXTERNAL_LAUNCHES_BEFORE_SHOWING_PROMPT) {
|
||||
// Allow a few external links to open before we prompt the user.
|
||||
prefs.edit().putInt(PREF_TAB_QUEUE_LAUNCHES, viewActionIntentLaunches).apply();
|
||||
} else if (viewActionIntentLaunches == EXTERNAL_LAUNCHES_BEFORE_SHOWING_PROMPT) {
|
||||
// Reset to avoid repeatedly showing the prompt if the user doesn't interact with it and
|
||||
// we get more external VIEW action intents in.
|
||||
final SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.remove(TabQueueHelper.PREF_TAB_QUEUE_LAUNCHES);
|
||||
|
||||
int timesPromptShown = prefs.getInt(TabQueueHelper.PREF_TAB_QUEUE_TIMES_PROMPT_SHOWN, 0) + 1;
|
||||
editor.putInt(TabQueueHelper.PREF_TAB_QUEUE_TIMES_PROMPT_SHOWN, timesPromptShown);
|
||||
editor.apply();
|
||||
|
||||
// Show the prompt
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads file and converts any content to JSON, adds passed in URL to the data and writes back to the file,
|
||||
* creating the file if it doesn't already exist. This should not be run on the UI thread.
|
||||
*
|
||||
* @param profile
|
||||
* @param url URL to add
|
||||
* @param filename filename to add URL to
|
||||
* @return the number of tabs currently queued
|
||||
*/
|
||||
public static int queueURL(final GeckoProfile profile, final String url, final String filename) {
|
||||
ThreadUtils.assertNotOnUiThread();
|
||||
|
||||
JSONArray jsonArray = profile.readJSONArrayFromFile(filename);
|
||||
|
||||
jsonArray.put(url);
|
||||
|
||||
profile.writeFile(filename, jsonArray.toString());
|
||||
|
||||
return jsonArray.length();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a url from the file, if it exists.
|
||||
* If the url exists multiple times, all instances of it will be removed.
|
||||
* This should not be run on the UI thread.
|
||||
*
|
||||
* @param context
|
||||
* @param urlToRemove URL to remove
|
||||
* @param filename filename to remove URL from
|
||||
* @return the number of queued urls
|
||||
*/
|
||||
public static int removeURLFromFile(final Context context, final String urlToRemove, final String filename) {
|
||||
ThreadUtils.assertNotOnUiThread();
|
||||
|
||||
final GeckoProfile profile = GeckoProfile.get(context);
|
||||
|
||||
JSONArray jsonArray = profile.readJSONArrayFromFile(filename);
|
||||
JSONArray newArray = new JSONArray();
|
||||
String url;
|
||||
|
||||
// Since JSONArray.remove was only added in API 19, we have to use two arrays in order to remove.
|
||||
for (int i = 0; i < jsonArray.length(); i++) {
|
||||
try {
|
||||
url = jsonArray.getString(i);
|
||||
} catch (JSONException e) {
|
||||
url = "";
|
||||
}
|
||||
if (!TextUtils.isEmpty(url) && !urlToRemove.equals(url)) {
|
||||
newArray.put(url);
|
||||
}
|
||||
}
|
||||
|
||||
profile.writeFile(filename, newArray.toString());
|
||||
|
||||
final SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
|
||||
prefs.edit().putInt(PREF_TAB_QUEUE_COUNT, newArray.length()).apply();
|
||||
|
||||
return newArray.length();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get up to eight of the last queued URLs for displaying in the notification.
|
||||
*/
|
||||
public static List<String> getLastURLs(final Context context, final String filename) {
|
||||
final GeckoProfile profile = GeckoProfile.get(context);
|
||||
final JSONArray jsonArray = profile.readJSONArrayFromFile(filename);
|
||||
final List<String> urls = new ArrayList<>(8);
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
try {
|
||||
urls.add(jsonArray.getString(i));
|
||||
} catch (JSONException e) {
|
||||
Log.w(LOGTAG, "Unable to parse URL from tab queue array", e);
|
||||
}
|
||||
}
|
||||
|
||||
return urls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a notification showing the total number of tabs queue. If there is already a notification displayed, it
|
||||
* will be replaced.
|
||||
*
|
||||
* @param context
|
||||
* @param tabsQueued
|
||||
*/
|
||||
public static void showNotification(final Context context, final int tabsQueued, final List<String> urls) {
|
||||
ThreadUtils.assertNotOnUiThread();
|
||||
|
||||
Intent resultIntent = new Intent();
|
||||
resultIntent.setClassName(context, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
|
||||
resultIntent.setAction(TabQueueHelper.LOAD_URLS_ACTION);
|
||||
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, resultIntent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
|
||||
final String text;
|
||||
final Resources resources = context.getResources();
|
||||
if (tabsQueued == 1) {
|
||||
text = resources.getString(R.string.tab_queue_notification_text_singular);
|
||||
} else {
|
||||
text = resources.getString(R.string.tab_queue_notification_text_plural, tabsQueued);
|
||||
}
|
||||
|
||||
NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
|
||||
inboxStyle.setBigContentTitle(text);
|
||||
for (String url : urls) {
|
||||
inboxStyle.addLine(url);
|
||||
}
|
||||
inboxStyle.setSummaryText(resources.getString(R.string.tab_queue_notification_title));
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
|
||||
.setSmallIcon(R.drawable.ic_status_logo)
|
||||
.setContentTitle(text)
|
||||
.setContentText(resources.getString(R.string.tab_queue_notification_title))
|
||||
.setStyle(inboxStyle)
|
||||
.setColor(ContextCompat.getColor(context, R.color.fennec_ui_orange))
|
||||
.setNumber(tabsQueued)
|
||||
.setContentIntent(pendingIntent);
|
||||
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(TabQueueHelper.TAB_QUEUE_NOTIFICATION_ID, builder.build());
|
||||
}
|
||||
|
||||
public static boolean shouldOpenTabQueueUrls(final Context context) {
|
||||
ThreadUtils.assertNotOnUiThread();
|
||||
|
||||
// TODO: Use profile shared prefs when bug 1147925 gets fixed.
|
||||
final SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
|
||||
|
||||
int tabsQueued = prefs.getInt(PREF_TAB_QUEUE_COUNT, 0);
|
||||
|
||||
return isTabQueueEnabled(prefs) && tabsQueued > 0;
|
||||
}
|
||||
|
||||
public static int getTabQueueLength(final Context context) {
|
||||
ThreadUtils.assertNotOnUiThread();
|
||||
|
||||
// TODO: Use profile shared prefs when bug 1147925 gets fixed.
|
||||
final SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
|
||||
return prefs.getInt(PREF_TAB_QUEUE_COUNT, 0);
|
||||
}
|
||||
|
||||
public static void openQueuedUrls(final Context context, final GeckoProfile profile, final String filename, boolean shouldPerformJavaScriptCallback) {
|
||||
ThreadUtils.assertNotOnUiThread();
|
||||
|
||||
removeNotification(context);
|
||||
|
||||
// exit early if we don't have any tabs queued
|
||||
if (getTabQueueLength(context) < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
JSONArray jsonArray = profile.readJSONArrayFromFile(filename);
|
||||
|
||||
if (jsonArray.length() > 0) {
|
||||
JSONObject data = new JSONObject();
|
||||
try {
|
||||
data.put("urls", jsonArray);
|
||||
data.put("shouldNotifyTabsOpenedToJava", shouldPerformJavaScriptCallback);
|
||||
GeckoAppShell.notifyObservers("Tabs:OpenMultiple", data.toString());
|
||||
} catch (JSONException e) {
|
||||
// Don't exit early as we perform cleanup at the end of this function.
|
||||
Log.e(LOGTAG, "Error sending tab queue data", e);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
profile.deleteFileFromProfileDir(filename);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.e(LOGTAG, "Error deleting Tab Queue data file.", e);
|
||||
}
|
||||
|
||||
// TODO: Use profile shared prefs when bug 1147925 gets fixed.
|
||||
final SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
|
||||
prefs.edit().remove(PREF_TAB_QUEUE_COUNT).apply();
|
||||
}
|
||||
|
||||
protected static void removeNotification(Context context) {
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.cancel(TAB_QUEUE_NOTIFICATION_ID);
|
||||
}
|
||||
|
||||
public static boolean processTabQueuePromptResponse(int resultCode, Context context) {
|
||||
final SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
|
||||
final SharedPreferences.Editor editor = prefs.edit();
|
||||
|
||||
switch (resultCode) {
|
||||
case TAB_QUEUE_YES:
|
||||
editor.putBoolean(GeckoPreferences.PREFS_TAB_QUEUE, true);
|
||||
|
||||
// By making this one more than EXTERNAL_LAUNCHES_BEFORE_SHOWING_PROMPT we ensure the prompt
|
||||
// will never show again without having to keep track of an extra pref.
|
||||
editor.putInt(TabQueueHelper.PREF_TAB_QUEUE_LAUNCHES,
|
||||
TabQueueHelper.EXTERNAL_LAUNCHES_BEFORE_SHOWING_PROMPT + 1);
|
||||
break;
|
||||
|
||||
case TAB_QUEUE_NO:
|
||||
// The user clicked the 'no' button, so let's make sure the user never sees the prompt again by
|
||||
// maxing out the pref used to count the VIEW action intents received and times they've seen the prompt.
|
||||
|
||||
editor.putInt(TabQueueHelper.PREF_TAB_QUEUE_LAUNCHES,
|
||||
TabQueueHelper.EXTERNAL_LAUNCHES_BEFORE_SHOWING_PROMPT + 1);
|
||||
|
||||
editor.putInt(TabQueueHelper.PREF_TAB_QUEUE_TIMES_PROMPT_SHOWN,
|
||||
TabQueueHelper.MAX_TIMES_TO_SHOW_PROMPT + 1);
|
||||
break;
|
||||
|
||||
default:
|
||||
// We shouldn't ever get here.
|
||||
Log.w(LOGTAG, "Unrecognized result code received from the tab queue prompt: " + resultCode);
|
||||
}
|
||||
|
||||
editor.apply();
|
||||
|
||||
return resultCode == TAB_QUEUE_YES;
|
||||
}
|
||||
|
||||
public static boolean isTabQueueEnabled(Context context) {
|
||||
return isTabQueueEnabled(GeckoSharedPrefs.forApp(context));
|
||||
}
|
||||
|
||||
public static boolean isTabQueueEnabled(SharedPreferences prefs) {
|
||||
return prefs.getBoolean(GeckoPreferences.PREFS_TAB_QUEUE, false);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,215 @@
|
|||
/* -*- 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.GeckoSharedPrefs;
|
||||
import org.mozilla.gecko.Locales;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
|
||||
public class TabQueuePrompt extends Locales.LocaleAwareActivity {
|
||||
public static final String LOGTAG = "Gecko" + TabQueuePrompt.class.getSimpleName();
|
||||
|
||||
private static final int SETTINGS_REQUEST_CODE = 1;
|
||||
|
||||
// Flag set during animation to prevent animation multiple-start.
|
||||
private boolean isAnimating;
|
||||
|
||||
private View containerView;
|
||||
private View buttonContainer;
|
||||
private View enabledConfirmation;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
showTabQueueEnablePrompt();
|
||||
}
|
||||
|
||||
private void showTabQueueEnablePrompt() {
|
||||
setContentView(R.layout.tab_queue_prompt);
|
||||
|
||||
final View okButton = findViewById(R.id.ok_button);
|
||||
okButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
onConfirmButtonPressed();
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "tabqueue_prompt_yes");
|
||||
}
|
||||
});
|
||||
findViewById(R.id.cancel_button).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "tabqueue_prompt_no");
|
||||
setResult(TabQueueHelper.TAB_QUEUE_NO);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
final View settingsButton = findViewById(R.id.settings_button);
|
||||
settingsButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
onSettingsButtonPressed();
|
||||
}
|
||||
});
|
||||
|
||||
final View tipView = findViewById(R.id.tip_text);
|
||||
final View settingsPermitView = findViewById(R.id.settings_permit_text);
|
||||
|
||||
if (TabQueueHelper.canDrawOverlays(this)) {
|
||||
okButton.setVisibility(View.VISIBLE);
|
||||
settingsButton.setVisibility(View.GONE);
|
||||
tipView.setVisibility(View.VISIBLE);
|
||||
settingsPermitView.setVisibility(View.GONE);
|
||||
} else {
|
||||
okButton.setVisibility(View.GONE);
|
||||
settingsButton.setVisibility(View.VISIBLE);
|
||||
tipView.setVisibility(View.GONE);
|
||||
settingsPermitView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
containerView = findViewById(R.id.tab_queue_container);
|
||||
buttonContainer = findViewById(R.id.button_container);
|
||||
enabledConfirmation = findViewById(R.id.enabled_confirmation);
|
||||
|
||||
containerView.setTranslationY(500);
|
||||
containerView.setAlpha(0);
|
||||
|
||||
final Animator translateAnimator = ObjectAnimator.ofFloat(containerView, "translationY", 0);
|
||||
translateAnimator.setDuration(400);
|
||||
|
||||
final Animator alphaAnimator = ObjectAnimator.ofFloat(containerView, "alpha", 1);
|
||||
alphaAnimator.setStartDelay(200);
|
||||
alphaAnimator.setDuration(600);
|
||||
|
||||
final AnimatorSet set = new AnimatorSet();
|
||||
set.playTogether(alphaAnimator, translateAnimator);
|
||||
set.setStartDelay(400);
|
||||
|
||||
set.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
super.finish();
|
||||
|
||||
// Don't perform an activity-dismiss animation.
|
||||
overridePendingTransition(0, 0);
|
||||
}
|
||||
|
||||
private void onConfirmButtonPressed() {
|
||||
enabledConfirmation.setVisibility(View.VISIBLE);
|
||||
enabledConfirmation.setAlpha(0);
|
||||
|
||||
final Animator buttonsAlphaAnimator = ObjectAnimator.ofFloat(buttonContainer, "alpha", 0);
|
||||
buttonsAlphaAnimator.setDuration(300);
|
||||
|
||||
final Animator messagesAlphaAnimator = ObjectAnimator.ofFloat(enabledConfirmation, "alpha", 1);
|
||||
messagesAlphaAnimator.setDuration(300);
|
||||
messagesAlphaAnimator.setStartDelay(200);
|
||||
|
||||
final AnimatorSet set = new AnimatorSet();
|
||||
set.playTogether(buttonsAlphaAnimator, messagesAlphaAnimator);
|
||||
|
||||
set.addListener(new AnimatorListenerAdapter() {
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
slideOut();
|
||||
setResult(TabQueueHelper.TAB_QUEUE_YES);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
|
||||
set.start();
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
private void onSettingsButtonPressed() {
|
||||
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
|
||||
intent.setData(Uri.parse("package:" + getPackageName()));
|
||||
startActivityForResult(intent, SETTINGS_REQUEST_CODE);
|
||||
|
||||
Toast.makeText(this, R.string.tab_queue_prompt_permit_drawing_over_apps, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode != SETTINGS_REQUEST_CODE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (TabQueueHelper.canDrawOverlays(this)) {
|
||||
// User granted the permission in Android's settings.
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "tabqueue_prompt_yes");
|
||||
|
||||
setResult(TabQueueHelper.TAB_QUEUE_YES);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Slide the overlay down off the screen and destroy it.
|
||||
*/
|
||||
private void slideOut() {
|
||||
if (isAnimating) {
|
||||
return;
|
||||
}
|
||||
|
||||
isAnimating = true;
|
||||
|
||||
ObjectAnimator animator = ObjectAnimator.ofFloat(containerView, "translationY", containerView.getHeight());
|
||||
animator.addListener(new AnimatorListenerAdapter() {
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
finish();
|
||||
}
|
||||
|
||||
});
|
||||
animator.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the dialog if back is pressed.
|
||||
*/
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
slideOut();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the dialog if the anything that isn't a button is tapped.
|
||||
*/
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
slideOut();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,342 @@
|
|||
/* -*- 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
/* 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.BrowserLocaleManager;
|
||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.WorkerThread;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.NotificationManagerCompat;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* An IntentService that displays a notification for a tab sent to this device.
|
||||
*
|
||||
* The expected Intent should contain:
|
||||
* * Data: URI to open in the notification
|
||||
* * EXTRA_TITLE: Page title of the URI to open
|
||||
*/
|
||||
public class TabReceivedService extends IntentService {
|
||||
private static final String LOGTAG = "Gecko" + TabReceivedService.class.getSimpleName();
|
||||
|
||||
private static final String PREF_NOTIFICATION_ID = "tab_received_notification_id";
|
||||
|
||||
private static final int MAX_NOTIFICATION_COUNT = 1000;
|
||||
|
||||
public TabReceivedService() {
|
||||
super(LOGTAG);
|
||||
setIntentRedelivery(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(final Intent intent) {
|
||||
// IntentServices don't keep the process alive so
|
||||
// we need to do this every time. Ideally, we wouldn't.
|
||||
final Resources res = getResources();
|
||||
BrowserLocaleManager.getInstance().correctLocale(this, res, res.getConfiguration());
|
||||
|
||||
final String uri = intent.getDataString();
|
||||
if (uri == null) {
|
||||
Log.d(LOGTAG, "Received null uri – ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
final Intent notificationIntent = new Intent(Intent.ACTION_VIEW, intent.getData());
|
||||
notificationIntent.putExtra(BrowserContract.SKIP_TAB_QUEUE_FLAG, true);
|
||||
final PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
|
||||
|
||||
final String notificationTitle = getNotificationTitle(intent.getStringExtra(BrowserContract.EXTRA_CLIENT_GUID));
|
||||
final NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
|
||||
builder.setSmallIcon(R.drawable.flat_icon);
|
||||
builder.setContentTitle(notificationTitle);
|
||||
builder.setWhen(System.currentTimeMillis());
|
||||
builder.setAutoCancel(true);
|
||||
builder.setContentText(uri);
|
||||
builder.setContentIntent(contentIntent);
|
||||
|
||||
// Trigger "heads-up" notification mode on supported Android versions.
|
||||
builder.setPriority(NotificationCompat.PRIORITY_HIGH);
|
||||
final Uri notificationSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
|
||||
if (notificationSoundUri != null) {
|
||||
builder.setSound(notificationSoundUri);
|
||||
}
|
||||
|
||||
final SharedPreferences prefs = GeckoSharedPrefs.forApp(this);
|
||||
final int notificationId = getNextNotificationId(prefs.getInt(PREF_NOTIFICATION_ID, 0));
|
||||
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
|
||||
notificationManager.notify(notificationId, builder.build());
|
||||
|
||||
// Save the ID last so if the Service is killed and the Intent is redelivered,
|
||||
// the ID is unlikely to have been updated and we would re-use the the old one.
|
||||
// This would prevent two identical notifications from appearing if the
|
||||
// notification was shown during the previous Intent processing attempt.
|
||||
prefs.edit().putInt(PREF_NOTIFICATION_ID, notificationId).apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param clientGUID the guid of the client in the clients table
|
||||
* @return the client's name from the clients table, if possible, else the brand name.
|
||||
*/
|
||||
@WorkerThread
|
||||
private String getNotificationTitle(@Nullable final String clientGUID) {
|
||||
if (clientGUID == null) {
|
||||
Log.w(LOGTAG, "Received null guid, using brand name.");
|
||||
return AppConstants.MOZ_APP_DISPLAYNAME;
|
||||
}
|
||||
|
||||
final Cursor c = getContentResolver().query(BrowserContract.Clients.CONTENT_URI,
|
||||
new String[] { BrowserContract.Clients.NAME },
|
||||
BrowserContract.Clients.GUID + "=?", new String[] { clientGUID }, null);
|
||||
try {
|
||||
if (c != null && c.moveToFirst()) {
|
||||
return c.getString(c.getColumnIndex(BrowserContract.Clients.NAME));
|
||||
} else {
|
||||
Log.w(LOGTAG, "Device not found, using brand name.");
|
||||
return AppConstants.MOZ_APP_DISPLAYNAME;
|
||||
}
|
||||
} finally {
|
||||
if (c != null) {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification IDs must be unique else a notification
|
||||
* will be overwritten so we cycle them.
|
||||
*/
|
||||
private int getNextNotificationId(final int currentId) {
|
||||
if (currentId > MAX_NOTIFICATION_COUNT) {
|
||||
return 0;
|
||||
} else {
|
||||
return currentId + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue