Bug 1412872 - 7. Move background events to GeckoApplication; r=nechen

Move the "Bookmark:Insert" and "Image:SetAs" events from GeckoApp to
GeckoApplication. These events are global to the application, and they
operate on the background thread, which will no longer be an option for
the GeckoView event dispatcher.

MozReview-Commit-ID: 8kesv8sJ8At
This commit is contained in:
Jim Chen 2017-11-01 14:54:04 -04:00
parent e6d6cbb51a
commit 843d67f245
3 changed files with 192 additions and 166 deletions

View File

@ -10,7 +10,6 @@ import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
import org.mozilla.gecko.annotation.RobocopTarget;
import org.mozilla.gecko.annotation.WrapForJNI;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.gfx.FullScreenState;
import org.mozilla.gecko.gfx.LayerView;
import org.mozilla.gecko.health.HealthRecorder;
@ -34,7 +33,6 @@ import org.mozilla.gecko.tabqueue.TabQueueHelper;
import org.mozilla.gecko.text.FloatingToolbarTextSelection;
import org.mozilla.gecko.text.TextSelection;
import org.mozilla.gecko.updater.UpdateServiceHelper;
import org.mozilla.gecko.util.ActivityResultHandler;
import org.mozilla.gecko.util.ActivityUtils;
import org.mozilla.gecko.util.BundleEventListener;
import org.mozilla.gecko.util.EventCallback;
@ -47,7 +45,6 @@ import org.mozilla.gecko.util.ViewUtil;
import org.mozilla.gecko.widget.ActionModePresenter;
import org.mozilla.gecko.widget.AnchoredPopup;
import android.Manifest;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
@ -64,17 +61,14 @@ import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.StrictMode;
import android.provider.ContactsContract;
import android.provider.MediaStore.Images.Media;
import android.support.annotation.NonNull;
import android.support.annotation.WorkerThread;
import android.support.design.widget.Snackbar;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Base64;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
@ -99,11 +93,7 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@ -678,22 +668,6 @@ public abstract class GeckoApp extends GeckoActivity
} else if ("Accessibility:Event".equals(event)) {
GeckoAccessibility.sendAccessibilityEvent(mLayerView, message);
} else if ("Bookmark:Insert".equals(event)) {
final BrowserDB db = BrowserDB.from(getProfile());
final boolean bookmarkAdded = db.addBookmark(
getContentResolver(), message.getString("title"), message.getString("url"));
final int resId = bookmarkAdded ? R.string.bookmark_added
: R.string.bookmark_already_added;
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
SnackbarBuilder.builder(GeckoApp.this)
.message(resId)
.duration(Snackbar.LENGTH_LONG)
.buildAndShow();
}
});
} else if ("Contact:Add".equals(event)) {
final String email = message.getString("email");
final String phone = message.getString("phone");
@ -728,10 +702,6 @@ public abstract class GeckoApp extends GeckoActivity
layerView.setFullScreenState(FullScreenState.NONE);
}
} else if ("Image:SetAs".equals(event)) {
String src = message.getString("url");
setImageAs(src);
} else if ("Locale:Set".equals(event)) {
setLocale(message.getString("locale"));
@ -870,130 +840,6 @@ public abstract class GeckoApp extends GeckoActivity
});
}
private void showSetImageResult(final boolean success, final int message, final String path) {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
if (!success) {
SnackbarBuilder.builder(GeckoApp.this)
.message(message)
.duration(Snackbar.LENGTH_LONG)
.buildAndShow();
return;
}
final Intent intent = new Intent(Intent.ACTION_ATTACH_DATA);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setData(Uri.parse(path));
// Removes the image from storage once the chooser activity ends.
Intent chooser = Intent.createChooser(intent, getString(message));
ActivityResultHandler handler = new ActivityResultHandler() {
@Override
public void onActivityResult (int resultCode, Intent data) {
getContentResolver().delete(intent.getData(), null, null);
}
};
ActivityHandlerHelper.startIntentForActivity(GeckoApp.this, chooser, handler);
}
});
}
// Checks the necessary permissions before attempting to download and set the image as wallpaper.
private void setImageAs(final String aSrc) {
Permissions
.from(this)
.onBackgroundThread()
.withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.andFallback(new Runnable() {
@Override
public void run() {
showSetImageResult(/* success */ false, R.string.set_image_path_fail, null);
}
})
.run(new Runnable() {
@Override
public void run() {
downloadImageForSetImage(aSrc);
}
});
}
/**
* Downloads the image given by <code>aSrc</code> synchronously and then displays the Chooser
* activity to set the image as wallpaper.
*
* @param aSrc The URI to download the image from.
*/
private void downloadImageForSetImage(final String aSrc) {
// Network access from the main thread can cause a StrictMode crash on release builds.
ThreadUtils.assertOnBackgroundThread();
boolean isDataURI = aSrc.startsWith("data:");
Bitmap image = null;
InputStream is = null;
ByteArrayOutputStream os = null;
try {
if (isDataURI) {
int dataStart = aSrc.indexOf(",");
byte[] buf = Base64.decode(aSrc.substring(dataStart + 1), Base64.DEFAULT);
image = BitmapUtils.decodeByteArray(buf);
} else {
int byteRead;
byte[] buf = new byte[4192];
os = new ByteArrayOutputStream();
URL url = new URL(aSrc);
is = url.openStream();
// Cannot read from same stream twice. Also, InputStream from
// URL does not support reset. So converting to byte array.
while ((byteRead = is.read(buf)) != -1) {
os.write(buf, 0, byteRead);
}
byte[] imgBuffer = os.toByteArray();
image = BitmapUtils.decodeByteArray(imgBuffer);
}
if (image != null) {
// Some devices don't have a DCIM folder and the Media.insertImage call will fail.
File dcimDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
if (!dcimDir.mkdirs() && !dcimDir.isDirectory()) {
showSetImageResult(/* success */ false, R.string.set_image_path_fail, null);
return;
}
String path = Media.insertImage(getContentResolver(), image, null, null);
if (path == null) {
showSetImageResult(/* success */ false, R.string.set_image_path_fail, null);
return;
}
showSetImageResult(/* success */ true, R.string.set_image_chooser_title, path);
} else {
showSetImageResult(/* success */ false, R.string.set_image_fail, null);
}
} catch (OutOfMemoryError ome) {
Log.e(LOGTAG, "Out of Memory when converting to byte array", ome);
} catch (IOException ioe) {
Log.e(LOGTAG, "I/O Exception while setting wallpaper", ioe);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException ioe) {
Log.w(LOGTAG, "I/O Exception while closing stream", ioe);
}
}
if (os != null) {
try {
os.close();
} catch (IOException ioe) {
Log.w(LOGTAG, "I/O Exception while closing stream", ioe);
}
}
}
}
private int getBitmapSampleSize(BitmapFactory.Options options, int idealWidth, int idealHeight) {
int width = options.outWidth;
int height = options.outHeight;
@ -1205,11 +1051,6 @@ public abstract class GeckoApp extends GeckoActivity
"Locale:Set",
null);
getAppEventDispatcher().registerBackgroundThreadListener(this,
"Bookmark:Insert",
"Image:SetAs",
null);
getAppEventDispatcher().registerUiThreadListener(this,
"Contact:Add",
"DevToolsAuth:Scan",
@ -2209,11 +2050,6 @@ public abstract class GeckoApp extends GeckoActivity
"Locale:Set",
null);
getAppEventDispatcher().unregisterBackgroundThreadListener(this,
"Bookmark:Insert",
"Image:SetAs",
null);
getAppEventDispatcher().unregisterUiThreadListener(this,
"Contact:Add",
"DevToolsAuth:Scan",

View File

@ -4,6 +4,7 @@
package org.mozilla.gecko;
import android.Manifest;
import android.app.Activity;
import android.app.Application;
import android.content.ContentResolver;
@ -13,9 +14,13 @@ import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.net.Uri;
import android.os.Environment;
import android.os.Process;
import android.os.SystemClock;
import android.provider.MediaStore;
import android.support.design.widget.Snackbar;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import com.squareup.leakcanary.LeakCanary;
@ -26,6 +31,7 @@ import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.db.LocalBrowserDB;
import org.mozilla.gecko.distribution.Distribution;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.home.HomePanelsManager;
import org.mozilla.gecko.icons.IconCallback;
import org.mozilla.gecko.icons.IconResponse;
@ -40,6 +46,7 @@ import org.mozilla.gecko.permissions.Permissions;
import org.mozilla.gecko.preferences.DistroSharedPrefsImport;
import org.mozilla.gecko.util.ActivityUtils;
import org.mozilla.gecko.telemetry.TelemetryBackgroundReceiver;
import org.mozilla.gecko.util.ActivityResultHandler;
import org.mozilla.gecko.util.BundleEventListener;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.GeckoBundle;
@ -49,8 +56,12 @@ import org.mozilla.gecko.util.ShortcutUtils;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.util.UIAsyncTask;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.UUID;
public class GeckoApplication extends Application
@ -299,6 +310,8 @@ public class GeckoApplication extends Application
"Share:Text",
null);
EventDispatcher.getInstance().registerBackgroundThreadListener(mListener,
"Bookmark:Insert",
"Image:SetAs",
"Profile:Create",
null);
@ -320,6 +333,8 @@ public class GeckoApplication extends Application
"Share:Text",
null);
EventDispatcher.getInstance().unregisterBackgroundThreadListener(mListener,
"Bookmark:Insert",
"Image:SetAs",
"Profile:Create",
null);
@ -503,6 +518,32 @@ public class GeckoApplication extends Application
} else if ("Distribution:GetDirectories".equals(event)) {
callback.sendSuccess(Distribution.getDistributionDirectories());
} else if ("Bookmark:Insert".equals(event)) {
final Context context = GeckoAppShell.getApplicationContext();
final BrowserDB db = BrowserDB.from(GeckoThread.getActiveProfile());
final boolean bookmarkAdded = db.addBookmark(context.getContentResolver(),
message.getString("title"),
message.getString("url"));
final int resId = bookmarkAdded ? R.string.bookmark_added
: R.string.bookmark_already_added;
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
final Activity currentActivity =
GeckoActivityMonitor.getInstance().getCurrentActivity();
if (currentActivity == null) {
return;
}
SnackbarBuilder.builder(currentActivity)
.message(resId)
.duration(Snackbar.LENGTH_LONG)
.buildAndShow();
}
});
} else if ("Image:SetAs".equals(event)) {
setImageAs(message.getString("url"));
}
}
}
@ -589,6 +630,155 @@ public class GeckoApplication extends Application
ShortcutUtils.createHomescreenIcon(shortcutIntent, aTitle, aURI, aIcon);
}
/* package */ static void showSetImageResult(final boolean success, final int message,
final String path) {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
final Activity currentActivity =
GeckoActivityMonitor.getInstance().getCurrentActivity();
if (currentActivity == null) {
return;
}
if (!success) {
SnackbarBuilder.builder(currentActivity)
.message(message)
.duration(Snackbar.LENGTH_LONG)
.buildAndShow();
return;
}
final Intent intent = new Intent(Intent.ACTION_ATTACH_DATA);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setData(Uri.parse(path));
// Removes the image from storage once the chooser activity ends.
final Context context = GeckoAppShell.getApplicationContext();
final Intent chooser = Intent.createChooser(intent,
context.getString(message));
ActivityResultHandler handler = new ActivityResultHandler() {
@Override
public void onActivityResult(int resultCode, Intent data) {
context.getContentResolver().delete(intent.getData(), null, null);
}
};
ActivityHandlerHelper.startIntentForActivity(currentActivity, chooser,
handler);
}
});
}
// Checks the necessary permissions before attempting to download and
// set the image as wallpaper.
private static void setImageAs(final String aSrc) {
final Activity currentActivity =
GeckoActivityMonitor.getInstance().getCurrentActivity();
final Context context = (currentActivity != null) ?
currentActivity : GeckoAppShell.getApplicationContext();
Permissions
.from(context)
.doNotPromptIf(currentActivity == null)
.onBackgroundThread()
.withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.andFallback(new Runnable() {
@Override
public void run() {
showSetImageResult(/* success */ false,
R.string.set_image_path_fail, null);
}
})
.run(new Runnable() {
@Override
public void run() {
downloadImageForSetImage(aSrc);
}
});
}
/**
* Downloads the image given by <code>aSrc</code> synchronously and
* then displays the Chooser activity to set the image as wallpaper.
*
* @param aSrc The URI to download the image from.
*/
private static void downloadImageForSetImage(final String aSrc) {
// Network access from the main thread can cause a StrictMode crash on release builds.
ThreadUtils.assertOnBackgroundThread();
final boolean isDataURI = aSrc.startsWith("data:");
Bitmap image = null;
InputStream is = null;
ByteArrayOutputStream os = null;
try {
if (isDataURI) {
int dataStart = aSrc.indexOf(",");
byte[] buf = Base64.decode(aSrc.substring(dataStart + 1), Base64.DEFAULT);
image = BitmapUtils.decodeByteArray(buf);
} else {
int byteRead;
byte[] buf = new byte[4192];
os = new ByteArrayOutputStream();
URL url = new URL(aSrc);
is = url.openStream();
// Cannot read from same stream twice. Also, InputStream from
// URL does not support reset. So converting to byte array.
while ((byteRead = is.read(buf)) != -1) {
os.write(buf, 0, byteRead);
}
byte[] imgBuffer = os.toByteArray();
image = BitmapUtils.decodeByteArray(imgBuffer);
}
if (image != null) {
// Some devices don't have a DCIM folder and the
// Media.insertImage call will fail.
final File dcimDir = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
if (!dcimDir.mkdirs() && !dcimDir.isDirectory()) {
showSetImageResult(/* success */ false,
R.string.set_image_path_fail, null);
return;
}
final Context context = GeckoAppShell.getApplicationContext();
final String path = MediaStore.Images.Media.insertImage(
context.getContentResolver(), image, null, null);
if (path == null) {
showSetImageResult(/* success */ false,
R.string.set_image_path_fail, null);
return;
}
showSetImageResult(/* success */ true,
R.string.set_image_chooser_title, path);
} else {
showSetImageResult(/* success */ false, R.string.set_image_fail, null);
}
} catch (final OutOfMemoryError ome) {
Log.e(LOG_TAG, "Out of Memory when converting to byte array", ome);
} catch (final IOException ioe) {
Log.e(LOG_TAG, "I/O Exception while setting wallpaper", ioe);
} finally {
if (is != null) {
try {
is.close();
} catch (final IOException ioe) {
Log.w(LOG_TAG, "I/O Exception while closing stream", ioe);
}
}
if (os != null) {
try {
os.close();
} catch (final IOException ioe) {
Log.w(LOG_TAG, "I/O Exception while closing stream", ioe);
}
}
}
}
@Override // HapticFeedbackDelegate
public void performHapticFeedback(final int effect) {
final Activity currentActivity =

View File

@ -752,7 +752,7 @@ var BrowserApp = {
let url = NativeWindow.contextmenus._getLinkURL(aTarget);
let title = aTarget.textContent || aTarget.title || url;
WindowEventDispatcher.sendRequest({
GlobalEventDispatcher.sendRequest({
type: "Bookmark:Insert",
url: url,
title: title
@ -893,7 +893,7 @@ var BrowserApp = {
UITelemetry.addEvent("action.1", "contextmenu", null, "web_background_image");
let src = aTarget.src;
WindowEventDispatcher.sendRequest({
GlobalEventDispatcher.sendRequest({
type: "Image:SetAs",
url: src
});