Bug 1130368 - Add toast feedback after a user clicks a link (r=mcomella)

This commit is contained in:
Martyn Haigh 2015-03-18 12:15:53 +00:00
Родитель eb35e37139
Коммит fff1910dc6
7 изменённых файлов: 325 добавлений и 1 удалений

Просмотреть файл

@ -60,6 +60,11 @@
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
#endif
#ifdef MOZ_ANDROID_TAB_QUEUE
<!-- Tab Queue -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
#endif
#ifdef MOZ_ANDROID_BEAM
<!-- Android Beam support -->
<uses-permission android:name="android.permission.NFC"/>
@ -165,6 +170,11 @@
<action android:name="org.mozilla.gecko.UPDATE"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
#ifndef MOZ_ANDROID_TAB_QUEUE
<!-- The main reason for the Tab Queue build flag is to not mess with the VIEW intent filter
before the rest of the plumbing is in place -->
<!-- The entry point for Intent.VIEW actions will move to the TabQueue activity -->
<!-- Default browser intents -->
<intent-filter>
@ -187,7 +197,7 @@
<data android:mimeType="text/plain"/>
<data android:mimeType="application/xhtml+xml"/>
</intent-filter>
#endif
<intent-filter>
<action android:name="android.intent.action.WEB_SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
@ -239,6 +249,41 @@
</intent-filter>
</activity-alias>
#ifdef MOZ_ANDROID_TAB_QUEUE
<!-- The main reason for the Tab Queue build flag is to not mess with the VIEW intent filter
before the rest of the plumbing is in place -->
<service android:name="org.mozilla.gecko.tabqueue.TabQueueService" />
<activity android:name="org.mozilla.gecko.tabqueue.TabQueueDispatcher"
android:label="@MOZ_APP_DISPLAYNAME@"
android:noHistory="true"
android:excludeFromRecents="true"
android:launchMode="singleTask"
android:theme="@style/TabQueueActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:scheme="about" />
<data android:scheme="javascript" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:mimeType="text/html"/>
<data android:mimeType="text/plain"/>
<data android:mimeType="application/xhtml+xml"/>
</intent-filter>
</activity>
#endif
<activity android:name="org.mozilla.gecko.StartPane"
android:theme="@style/GeckoStartPane"
android:excludeFromRecents="true"/>

Просмотреть файл

@ -184,6 +184,9 @@
<!ENTITY pref_donottrack_title "Do not track">
<!ENTITY pref_donottrack_summary "&brandShortName; will tell sites that you do not want to be tracked">
<!ENTITY tab_queue_toast_message "Open later">
<!ENTITY tab_queue_toast_action "Open now">
<!ENTITY pref_char_encoding "Character encoding">
<!ENTITY pref_char_encoding_on "Show menu">
<!ENTITY pref_char_encoding_off "Don\'t show menu">

Просмотреть файл

@ -421,6 +421,8 @@ gbjar.sources += [
'SuggestClient.java',
'SurfaceBits.java',
'Tab.java',
'tabqueue/TabQueueDispatcher.java',
'tabqueue/TabQueueService.java',
'Tabs.java',
'tabs/PrivateTabsPanel.java',
'tabs/TabCurve.java',

Просмотреть файл

@ -894,4 +894,5 @@
<item name="android:paddingRight">8dp</item>
</style>
<style name="TabQueueActivity" parent="android:style/Theme.NoDisplay" />
</resources>

Просмотреть файл

@ -239,6 +239,9 @@
<string name="pref_tab_queue_title">&pref_tab_queue_title;</string>
<string name="pref_tab_queue_summary">&pref_tab_queue_summary;</string>
<string name="tab_queue_toast_message">&tab_queue_toast_message;</string>
<string name="tab_queue_toast_action">&tab_queue_toast_action;</string>
<string name="pref_about_firefox">&pref_about_firefox;</string>
<string name="pref_vendor_faqs">&pref_vendor_faqs;</string>
<string name="pref_vendor_feedback">&pref_vendor_feedback;</string>

Просмотреть файл

@ -0,0 +1,81 @@
/* -*- 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.BrowserApp;
import org.mozilla.gecko.Locales;
import org.mozilla.gecko.sync.setup.activities.WebURLFinder;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
/**
* This class takes over external url loads (Intent.VIEW) from the BrowserApp class. It determines if
* the tab queue functionality is enabled and forwards the intent to the TabQueueService to process if it is.
*
* If the tab queue functionality is not enabled then it forwards the intent to BrowserApp to handle as normal.
*/
public class TabQueueDispatcher extends Locales.LocaleAwareActivity {
private static final String LOGTAG = "Gecko" + TabQueueDispatcher.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
// For the moment lets exit early and start fennec as normal if we're not in nightly with
// the tab queue build flag.
if (!AppConstants.MOZ_ANDROID_TAB_QUEUE) {
loadNormally(intent);
finish();
}
// The URL is usually hiding somewhere in the extra text. Extract it.
final String dataString = intent.getDataString();
if (TextUtils.isEmpty(dataString)) {
abortDueToNoURL(dataString);
return;
}
// TODO: This code is shared with ShareDialog - we should extract this to a helper class.
final String pageUrl = new WebURLFinder(dataString).bestWebURL();
if (TextUtils.isEmpty(pageUrl)) {
abortDueToNoURL(dataString);
return;
}
showToast(intent);
}
private void showToast(Intent intent) {
intent.setClass(getApplicationContext(), TabQueueService.class);
startService(intent);
finish();
}
/**
* Start fennec with the supplied intent.
*/
private void loadNormally(Intent intent) {
intent.setClass(getApplicationContext(), BrowserApp.class);
startActivity(intent);
finish();
}
/**
* Abort as we were started with no URL.
* @param dataString
*/
private void abortDueToNoURL(String dataString) {
// TODO: Lets decide what to do here in bug 1134148
Log.w(LOGTAG, "Unable to process tab queue insertion. No URL found! - passed data string: " + dataString);
finish();
}
}

Просмотреть файл

@ -0,0 +1,189 @@
/* -*- 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 android.app.Service;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
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 org.mozilla.gecko.BrowserApp;
import org.mozilla.gecko.R;
import org.mozilla.gecko.mozglue.ContextUtils;
/**
* 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 WindowManager windowManager;
private View toastLayout;
private Button openNowButton;
private Handler tabQueueHandler;
private WindowManager.LayoutParams toastLayoutParams;
private volatile StopServiceRunnable stopServiceRunnable;
private HandlerThread handlerThread;
@Override
public IBinder onBind(Intent intent) {
// Not used
return null;
}
@Override
public void onCreate() {
super.onCreate();
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.button_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.WRAP_CONTENT,
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 (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 {
windowManager.addView(toastLayout, toastLayoutParams);
}
stopServiceRunnable = new StopServiceRunnable(startId) {
@Override
public void onRun() {
addUrlToTabQueue(intent);
stopServiceRunnable = null;
}
};
openNowButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
tabQueueHandler.removeCallbacks(stopServiceRunnable);
stopServiceRunnable = null;
Intent forwardIntent = new Intent(intent);
forwardIntent.setClass(getApplicationContext(), BrowserApp.class);
forwardIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(forwardIntent);
removeView();
}
});
tabQueueHandler.postDelayed(stopServiceRunnable, TOAST_TIMEOUT);
return START_FLAG_REDELIVERY;
}
private void removeView() {
windowManager.removeView(toastLayout);
}
private void addUrlToTabQueue(Intent intentParam) {
if (intentParam == null) {
// This should never happen, but let's return silently instead of crash if it does.
Log.w(LOGTAG, "Error adding URL to tab queue - invalid intent passed in.");
return;
}
final ContextUtils.SafeIntent intent = new ContextUtils.SafeIntent(intentParam);
final String intentData = intent.getDataString();
// TODO Add url to tab queue here - bug 1134235
Log.d(LOGTAG, "Adding URL to tab queue: " + intentData);
}
@Override
public void onDestroy() {
super.onDestroy();
tabQueueHandler = null;
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(int startId) {
this.startId = startId;
}
public void run(boolean shouldStopService) {
onRun();
if (shouldStopService) {
removeView();
}
stopSelfResult(startId);
}
public void run() {
run(true);
}
public abstract void onRun();
}
}