diff options
author | Matt A. Tobin <email@mattatobin.com> | 2019-04-23 15:32:23 -0400 |
---|---|---|
committer | Matt A. Tobin <email@mattatobin.com> | 2019-04-23 15:32:23 -0400 |
commit | abe80cc31d5a40ebed743085011fbcda0c1a9a10 (patch) | |
tree | fb3762f06b84745b182af281abb107b95a9fcf01 /mobile/android/geckoview/src | |
parent | 63295d0087eb58a6eb34cad324c4c53d1b220491 (diff) | |
download | UXP-abe80cc31d5a40ebed743085011fbcda0c1a9a10.tar UXP-abe80cc31d5a40ebed743085011fbcda0c1a9a10.tar.gz UXP-abe80cc31d5a40ebed743085011fbcda0c1a9a10.tar.lz UXP-abe80cc31d5a40ebed743085011fbcda0c1a9a10.tar.xz UXP-abe80cc31d5a40ebed743085011fbcda0c1a9a10.zip |
Issue #1053 - Drop support Android and remove Fennec - Part 1a: Remove mobile/android
Diffstat (limited to 'mobile/android/geckoview/src')
118 files changed, 0 insertions, 24006 deletions
diff --git a/mobile/android/geckoview/src/main/AndroidManifest.xml b/mobile/android/geckoview/src/main/AndroidManifest.xml deleted file mode 100644 index 4e2aaf447..000000000 --- a/mobile/android/geckoview/src/main/AndroidManifest.xml +++ /dev/null @@ -1,39 +0,0 @@ -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="org.mozilla.geckoview"> - - <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> - <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> - <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> - <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> - <uses-permission android:name="android.permission.INTERNET"/> - <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> - <!-- READ_EXTERNAL_STORAGE was added in API 16, and is only enforced in API - 19+. We declare it so that the bouncer APK and the main APK have the - same set of permissions. --> - <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> - <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/> - <uses-permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT"/> - - <uses-permission android:name="android.permission.WAKE_LOCK"/> - <uses-permission android:name="android.permission.VIBRATE"/> - - <uses-feature android:name="android.hardware.location" android:required="false"/> - <uses-feature android:name="android.hardware.location.gps" android:required="false"/> - <uses-feature android:name="android.hardware.touchscreen"/> - - <!--#ifdef MOZ_WEBRTC--> - <!--<uses-permission android:name="android.permission.RECORD_AUDIO"/>--> - <!--<uses-feature android:name="android.hardware.audio.low_latency" android:required="false"/>--> - <!--<uses-feature android:name="android.hardware.camera.any" android:required="false"/>--> - <!--<uses-feature android:name="android.hardware.microphone" android:required="false"/>--> - <!--#endif--> - - <uses-permission android:name="android.permission.CAMERA" /> - <uses-feature android:name="android.hardware.camera" android:required="false"/> - <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/> - - <!-- App requires OpenGL ES 2.0 --> - <uses-feature android:glEsVersion="0x00020000" android:required="true" /> - -</manifest> diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/AlarmReceiver.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/AlarmReceiver.java deleted file mode 100644 index a098113fa..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/AlarmReceiver.java +++ /dev/null @@ -1,42 +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; - -import org.mozilla.gecko.annotation.WrapForJNI; - -import android.app.IntentService; -import android.content.Context; -import android.content.Intent; -import android.content.BroadcastReceiver; -import android.os.PowerManager; -import android.os.PowerManager.WakeLock; -import android.util.Log; - -import java.util.Timer; -import java.util.TimerTask; - -public class AlarmReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); - final WakeLock wakeLock = powerManager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, "GeckoAlarm"); - wakeLock.acquire(); - - AlarmReceiver.notifyAlarmFired(); - TimerTask releaseLockTask = new TimerTask() { - @Override - public void run() { - wakeLock.release(); - } - }; - Timer timer = new Timer(); - // 5 seconds ought to be enough for anybody - timer.schedule(releaseLockTask, 5 * 1000); - } - - @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko") - private static native void notifyAlarmFired(); -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/AndroidGamepadManager.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/AndroidGamepadManager.java deleted file mode 100644 index 54e1b0931..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/AndroidGamepadManager.java +++ /dev/null @@ -1,425 +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; - -import java.util.ArrayList; -import java.util.List; -import java.util.Timer; -import java.util.TimerTask; - -import org.mozilla.gecko.AppConstants.Versions; -import org.mozilla.gecko.annotation.WrapForJNI; -import org.mozilla.gecko.util.GamepadUtils; -import org.mozilla.gecko.util.ThreadUtils; - -import android.content.Context; -import android.hardware.input.InputManager; -import android.util.SparseArray; -import android.view.InputDevice; -import android.view.KeyEvent; -import android.view.MotionEvent; - - -public class AndroidGamepadManager { - // This is completely arbitrary. - private static final float TRIGGER_PRESSED_THRESHOLD = 0.25f; - private static final long POLL_TIMER_PERIOD = 1000; // milliseconds - - private static enum Axis { - X(MotionEvent.AXIS_X), - Y(MotionEvent.AXIS_Y), - Z(MotionEvent.AXIS_Z), - RZ(MotionEvent.AXIS_RZ); - - public final int axis; - - private Axis(int axis) { - this.axis = axis; - } - } - - // A list of gamepad button mappings. Axes are determined at - // runtime, as they vary by Android version. - private static enum Trigger { - Left(6), - Right(7); - - public final int button; - - private Trigger(int button) { - this.button = button; - } - } - - private static final int FIRST_DPAD_BUTTON = 12; - // A list of axis number, gamepad button mappings for negative, positive. - // Button mappings are added to FIRST_DPAD_BUTTON. - private static enum DpadAxis { - UpDown(MotionEvent.AXIS_HAT_Y, 0, 1), - LeftRight(MotionEvent.AXIS_HAT_X, 2, 3); - - public final int axis; - public final int negativeButton; - public final int positiveButton; - - private DpadAxis(int axis, int negativeButton, int positiveButton) { - this.axis = axis; - this.negativeButton = negativeButton; - this.positiveButton = positiveButton; - } - } - - private static enum Button { - A(KeyEvent.KEYCODE_BUTTON_A), - B(KeyEvent.KEYCODE_BUTTON_B), - X(KeyEvent.KEYCODE_BUTTON_X), - Y(KeyEvent.KEYCODE_BUTTON_Y), - L1(KeyEvent.KEYCODE_BUTTON_L1), - R1(KeyEvent.KEYCODE_BUTTON_R1), - L2(KeyEvent.KEYCODE_BUTTON_L2), - R2(KeyEvent.KEYCODE_BUTTON_R2), - SELECT(KeyEvent.KEYCODE_BUTTON_SELECT), - START(KeyEvent.KEYCODE_BUTTON_START), - THUMBL(KeyEvent.KEYCODE_BUTTON_THUMBL), - THUMBR(KeyEvent.KEYCODE_BUTTON_THUMBR), - DPAD_UP(KeyEvent.KEYCODE_DPAD_UP), - DPAD_DOWN(KeyEvent.KEYCODE_DPAD_DOWN), - DPAD_LEFT(KeyEvent.KEYCODE_DPAD_LEFT), - DPAD_RIGHT(KeyEvent.KEYCODE_DPAD_RIGHT); - - public final int button; - - private Button(int button) { - this.button = button; - } - } - - private static class Gamepad { - // ID from GamepadService - public int id; - // Retain axis state so we can determine changes. - public float axes[]; - public boolean dpad[]; - public int triggerAxes[]; - public float triggers[]; - - public Gamepad(int serviceId, int deviceId) { - id = serviceId; - axes = new float[Axis.values().length]; - dpad = new boolean[4]; - triggers = new float[2]; - - InputDevice device = InputDevice.getDevice(deviceId); - if (device != null) { - // LTRIGGER/RTRIGGER don't seem to be exposed on older - // versions of Android. - if (device.getMotionRange(MotionEvent.AXIS_LTRIGGER) != null && device.getMotionRange(MotionEvent.AXIS_RTRIGGER) != null) { - triggerAxes = new int[]{MotionEvent.AXIS_LTRIGGER, - MotionEvent.AXIS_RTRIGGER}; - } else if (device.getMotionRange(MotionEvent.AXIS_BRAKE) != null && device.getMotionRange(MotionEvent.AXIS_GAS) != null) { - triggerAxes = new int[]{MotionEvent.AXIS_BRAKE, - MotionEvent.AXIS_GAS}; - } else { - triggerAxes = null; - } - } - } - } - - @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko") - private static native void onGamepadChange(int id, boolean added); - @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko") - private static native void onButtonChange(int id, int button, boolean pressed, float value); - @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko") - private static native void onAxisChange(int id, boolean[] valid, float[] values); - - private static boolean sStarted; - private static final SparseArray<Gamepad> sGamepads = new SparseArray<>(); - private static final SparseArray<List<KeyEvent>> sPendingGamepads = new SparseArray<>(); - private static InputManager.InputDeviceListener sListener; - private static Timer sPollTimer; - - private AndroidGamepadManager() { - } - - @WrapForJNI - private static void start() { - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - doStart(); - } - }); - } - - /* package */ static void doStart() { - ThreadUtils.assertOnUiThread(); - if (!sStarted) { - scanForGamepads(); - addDeviceListener(); - sStarted = true; - } - } - - @WrapForJNI - private static void stop() { - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - doStop(); - } - }); - } - - /* package */ static void doStop() { - ThreadUtils.assertOnUiThread(); - if (sStarted) { - removeDeviceListener(); - sPendingGamepads.clear(); - sGamepads.clear(); - sStarted = false; - } - } - - @WrapForJNI(calledFrom = "gecko") - private static void onGamepadAdded(final int device_id, final int service_id) { - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - handleGamepadAdded(device_id, service_id); - } - }); - } - - /* package */ static void handleGamepadAdded(int deviceId, int serviceId) { - ThreadUtils.assertOnUiThread(); - if (!sStarted) { - return; - } - - final List<KeyEvent> pending = sPendingGamepads.get(deviceId); - if (pending == null) { - removeGamepad(deviceId); - return; - } - - sPendingGamepads.remove(deviceId); - sGamepads.put(deviceId, new Gamepad(serviceId, deviceId)); - // Handle queued KeyEvents - for (KeyEvent ev : pending) { - handleKeyEvent(ev); - } - } - - private static float deadZone(MotionEvent ev, int axis) { - if (GamepadUtils.isValueInDeadZone(ev, axis)) { - return 0.0f; - } - return ev.getAxisValue(axis); - } - - private static void mapDpadAxis(Gamepad gamepad, - boolean pressed, - float value, - int which) { - if (pressed != gamepad.dpad[which]) { - gamepad.dpad[which] = pressed; - onButtonChange(gamepad.id, FIRST_DPAD_BUTTON + which, pressed, Math.abs(value)); - } - } - - public static boolean handleMotionEvent(MotionEvent ev) { - ThreadUtils.assertOnUiThread(); - if (!sStarted) { - return false; - } - - final Gamepad gamepad = sGamepads.get(ev.getDeviceId()); - if (gamepad == null) { - // Not a device we care about. - return false; - } - - // First check the analog stick axes - boolean[] valid = new boolean[Axis.values().length]; - float[] axes = new float[Axis.values().length]; - boolean anyValidAxes = false; - for (Axis axis : Axis.values()) { - float value = deadZone(ev, axis.axis); - int i = axis.ordinal(); - if (value != gamepad.axes[i]) { - axes[i] = value; - gamepad.axes[i] = value; - valid[i] = true; - anyValidAxes = true; - } - } - if (anyValidAxes) { - // Send an axismove event. - onAxisChange(gamepad.id, valid, axes); - } - - // Map triggers to buttons. - if (gamepad.triggerAxes != null) { - for (Trigger trigger : Trigger.values()) { - int i = trigger.ordinal(); - int axis = gamepad.triggerAxes[i]; - float value = deadZone(ev, axis); - if (value != gamepad.triggers[i]) { - gamepad.triggers[i] = value; - boolean pressed = value > TRIGGER_PRESSED_THRESHOLD; - onButtonChange(gamepad.id, trigger.button, pressed, value); - } - } - } - // Map d-pad to buttons. - for (DpadAxis dpadaxis : DpadAxis.values()) { - float value = deadZone(ev, dpadaxis.axis); - mapDpadAxis(gamepad, value < 0.0f, value, dpadaxis.negativeButton); - mapDpadAxis(gamepad, value > 0.0f, value, dpadaxis.positiveButton); - } - return true; - } - - public static boolean handleKeyEvent(KeyEvent ev) { - ThreadUtils.assertOnUiThread(); - if (!sStarted) { - return false; - } - - int deviceId = ev.getDeviceId(); - final List<KeyEvent> pendingGamepad = sPendingGamepads.get(deviceId); - if (pendingGamepad != null) { - // Queue up key events for pending devices. - pendingGamepad.add(ev); - return true; - } - - if (sGamepads.get(deviceId) == null) { - InputDevice device = ev.getDevice(); - if (device != null && - (device.getSources() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) { - // This is a gamepad we haven't seen yet. - addGamepad(device); - sPendingGamepads.get(deviceId).add(ev); - return true; - } - // Not a device we care about. - return false; - } - - int key = -1; - for (Button button : Button.values()) { - if (button.button == ev.getKeyCode()) { - key = button.ordinal(); - break; - } - } - if (key == -1) { - // Not a key we know how to handle. - return false; - } - if (ev.getRepeatCount() > 0) { - // We would handle this key, but we're not interested in - // repeats. Eat it. - return true; - } - - Gamepad gamepad = sGamepads.get(deviceId); - boolean pressed = ev.getAction() == KeyEvent.ACTION_DOWN; - onButtonChange(gamepad.id, key, pressed, pressed ? 1.0f : 0.0f); - return true; - } - - private static void scanForGamepads() { - int[] deviceIds = InputDevice.getDeviceIds(); - if (deviceIds == null) { - return; - } - for (int i = 0; i < deviceIds.length; i++) { - InputDevice device = InputDevice.getDevice(deviceIds[i]); - if (device == null) { - continue; - } - if ((device.getSources() & InputDevice.SOURCE_GAMEPAD) != InputDevice.SOURCE_GAMEPAD) { - continue; - } - addGamepad(device); - } - } - - private static void addGamepad(InputDevice device) { - sPendingGamepads.put(device.getId(), new ArrayList<KeyEvent>()); - onGamepadChange(device.getId(), true); - } - - private static void removeGamepad(int deviceId) { - Gamepad gamepad = sGamepads.get(deviceId); - onGamepadChange(gamepad.id, false); - sGamepads.remove(deviceId); - } - - private static void addDeviceListener() { - if (Versions.preJB) { - // Poll known gamepads to see if they've disappeared. - sPollTimer = new Timer(); - sPollTimer.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - for (int i = 0; i < sGamepads.size(); ++i) { - final int deviceId = sGamepads.keyAt(i); - if (InputDevice.getDevice(deviceId) == null) { - removeGamepad(deviceId); - } - } - } - }, POLL_TIMER_PERIOD, POLL_TIMER_PERIOD); - return; - } - sListener = new InputManager.InputDeviceListener() { - @Override - public void onInputDeviceAdded(int deviceId) { - InputDevice device = InputDevice.getDevice(deviceId); - if (device == null) { - return; - } - if ((device.getSources() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) { - addGamepad(device); - } - } - - @Override - public void onInputDeviceRemoved(int deviceId) { - if (sPendingGamepads.get(deviceId) != null) { - // Got removed before Gecko's ack reached us. - // gamepadAdded will deal with it. - sPendingGamepads.remove(deviceId); - return; - } - if (sGamepads.get(deviceId) != null) { - removeGamepad(deviceId); - } - } - - @Override - public void onInputDeviceChanged(int deviceId) { - } - }; - ((InputManager)GeckoAppShell.getContext().getSystemService(Context.INPUT_SERVICE)).registerInputDeviceListener(sListener, ThreadUtils.getUiHandler()); - } - - private static void removeDeviceListener() { - if (Versions.preJB) { - if (sPollTimer != null) { - sPollTimer.cancel(); - sPollTimer = null; - } - return; - } - ((InputManager)GeckoAppShell.getContext().getSystemService(Context.INPUT_SERVICE)).unregisterInputDeviceListener(sListener); - sListener = null; - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/BaseGeckoInterface.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/BaseGeckoInterface.java deleted file mode 100644 index c4f64fd3d..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/BaseGeckoInterface.java +++ /dev/null @@ -1,169 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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; - -import org.mozilla.gecko.util.ActivityUtils; -import org.mozilla.gecko.util.HardwareUtils; -import org.mozilla.gecko.util.ThreadUtils; - -import android.app.Activity; -import android.content.Context; -import android.graphics.RectF; -import android.hardware.SensorEventListener; -import android.location.LocationListener; -import android.view.View; -import android.widget.AbsoluteLayout; - -public class BaseGeckoInterface implements GeckoAppShell.GeckoInterface { - // Bug 908744: Implement GeckoEventListener - // Bug 908752: Implement SensorEventListener - // Bug 908755: Implement LocationListener - // Bug 908756: Implement Tabs.OnTabsChangedListener - // Bug 908760: Implement GeckoEventResponder - - private final Context mContext; - private GeckoProfile mProfile; - private final EventDispatcher eventDispatcher; - - public BaseGeckoInterface(Context context) { - mContext = context; - eventDispatcher = new EventDispatcher(); - } - - @Override - public EventDispatcher getAppEventDispatcher() { - return eventDispatcher; - } - - @Override - public GeckoProfile getProfile() { - // Fall back to default profile if we didn't load a specific one - if (mProfile == null) { - mProfile = GeckoProfile.get(mContext); - } - return mProfile; - } - - @Override - public Activity getActivity() { - return (Activity)mContext; - } - - @Override - public String getDefaultUAString() { - return HardwareUtils.isTablet() ? AppConstants.USER_AGENT_FENNEC_TABLET : - AppConstants.USER_AGENT_FENNEC_MOBILE; - } - - // Bug 908775: Implement this - @Override - public void doRestart() {} - - @Override - public void setFullScreen(final boolean fullscreen) { - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - ActivityUtils.setFullScreen(getActivity(), fullscreen); - } - }); - } - - // Bug 908779: Implement this - @Override - public void addPluginView(final View view) {} - - // Bug 908781: Implement this - @Override - public void removePluginView(final View view) {} - - @Override - public void enableOrientationListener() {} - - @Override - public void disableOrientationListener() {} - - // Bug 908786: Implement this - @Override - public void addAppStateListener(GeckoAppShell.AppStateListener listener) {} - - // Bug 908787: Implement this - @Override - public void removeAppStateListener(GeckoAppShell.AppStateListener listener) {} - - // Bug 908789: Implement this - @Override - public void notifyWakeLockChanged(String topic, String state) {} - - @Override - public boolean areTabsShown() { - return false; - } - - // Bug 908791: Implement this - @Override - public AbsoluteLayout getPluginContainer() { - return null; - } - - @Override - public void notifyCheckUpdateResult(String result) { - GeckoAppShell.notifyObservers("Update:CheckResult", result); - } - - // Bug 908792: Implement this - @Override - public void invalidateOptionsMenu() {} - - @Override - public void createShortcut(String title, String URI) { - // By default, do nothing. - } - - @Override - public void checkUriVisited(String uri) { - // By default, no URIs are considered visited. - } - - @Override - public void markUriVisited(final String uri) { - // By default, no URIs are marked as visited. - } - - @Override - public void setUriTitle(final String uri, final String title) { - // By default, no titles are associated with URIs. - } - - @Override - public void setAccessibilityEnabled(boolean enabled) { - // By default, take no action when accessibility is toggled on or off. - } - - @Override - public boolean openUriExternal(String targetURI, String mimeType, String packageName, String className, String action, String title) { - // By default, never open external URIs. - return false; - } - - @Override - public String[] getHandlersForMimeType(String mimeType, String action) { - // By default, offer no handlers for any MIME type. - return new String[] {}; - } - - @Override - public String[] getHandlersForURL(String url, String action) { - // By default, offer no handlers for any URL. - return new String[] {}; - } - - @Override - public String getDefaultChromeURI() { - // By default, use the GeckoView-specific chrome URI. - return "chrome://browser/content/geckoview.xul"; - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/ContextGetter.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/ContextGetter.java deleted file mode 100644 index 315854633..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/ContextGetter.java +++ /dev/null @@ -1,15 +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; - -import android.content.Context; -import android.content.SharedPreferences; - -public interface ContextGetter { - Context getContext(); - SharedPreferences getSharedPreferences(); -} - diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/CrashHandler.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/CrashHandler.java deleted file mode 100644 index 0c8eeff9e..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/CrashHandler.java +++ /dev/null @@ -1,474 +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; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.Arrays; -import java.util.UUID; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Process; -import android.util.Log; - -public class CrashHandler implements Thread.UncaughtExceptionHandler { - - private static final String LOGTAG = "GeckoCrashHandler"; - private static final Thread MAIN_THREAD = Thread.currentThread(); - private static final String DEFAULT_SERVER_URL = - "https://crash-reports.mozilla.com/submit?id=%1$s&version=%2$s&buildid=%3$s"; - - // Context for getting device information - protected final Context appContext; - // Thread that this handler applies to, or null for a global handler - protected final Thread handlerThread; - protected final Thread.UncaughtExceptionHandler systemUncaughtHandler; - - protected boolean crashing; - protected boolean unregistered; - - /** - * Get the root exception from the 'cause' chain of an exception. - * - * @param exc An exception - * @return The root exception - */ - public static Throwable getRootException(Throwable exc) { - for (Throwable cause = exc; cause != null; cause = cause.getCause()) { - exc = cause; - } - return exc; - } - - /** - * Get the standard stack trace string of an exception. - * - * @param exc An exception - * @return The exception stack trace. - */ - public static String getExceptionStackTrace(final Throwable exc) { - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - exc.printStackTrace(pw); - pw.flush(); - return sw.toString(); - } - - /** - * Terminate the current process. - */ - public static void terminateProcess() { - Process.killProcess(Process.myPid()); - } - - /** - * Create and register a CrashHandler for all threads and thread groups. - */ - public CrashHandler() { - this((Context) null); - } - - /** - * Create and register a CrashHandler for all threads and thread groups. - * - * @param appContext A Context for retrieving application information. - */ - public CrashHandler(final Context appContext) { - this.appContext = appContext; - this.handlerThread = null; - this.systemUncaughtHandler = Thread.getDefaultUncaughtExceptionHandler(); - Thread.setDefaultUncaughtExceptionHandler(this); - } - - /** - * Create and register a CrashHandler for a particular thread. - * - * @param thread A thread to register the CrashHandler - */ - public CrashHandler(final Thread thread) { - this(thread, null); - } - - /** - * Create and register a CrashHandler for a particular thread. - * - * @param thread A thread to register the CrashHandler - * @param appContext A Context for retrieving application information. - */ - public CrashHandler(final Thread thread, final Context appContext) { - this.appContext = appContext; - this.handlerThread = thread; - this.systemUncaughtHandler = thread.getUncaughtExceptionHandler(); - thread.setUncaughtExceptionHandler(this); - } - - /** - * Unregister this CrashHandler for exception handling. - */ - public void unregister() { - unregistered = true; - - // Restore the previous handler if we are still the topmost handler. - // If not, we are part of a chain of handlers, and we cannot just restore the previous - // handler, because that would replace whatever handler that's above us in the chain. - - if (handlerThread != null) { - if (handlerThread.getUncaughtExceptionHandler() == this) { - handlerThread.setUncaughtExceptionHandler(systemUncaughtHandler); - } - } else { - if (Thread.getDefaultUncaughtExceptionHandler() == this) { - Thread.setDefaultUncaughtExceptionHandler(systemUncaughtHandler); - } - } - } - - /** - * Record an exception stack in logs. - * - * @param thread The exception thread - * @param exc An exception - */ - public static void logException(final Thread thread, final Throwable exc) { - try { - Log.e(LOGTAG, ">>> REPORTING UNCAUGHT EXCEPTION FROM THREAD " - + thread.getId() + " (\"" + thread.getName() + "\")", exc); - - if (MAIN_THREAD != thread) { - Log.e(LOGTAG, "Main thread (" + MAIN_THREAD.getId() + ") stack:"); - for (StackTraceElement ste : MAIN_THREAD.getStackTrace()) { - Log.e(LOGTAG, " " + ste.toString()); - } - } - } catch (final Throwable e) { - // If something throws here, we want to continue to report the exception, - // so we catch all exceptions and ignore them. - } - } - - private static long getCrashTime() { - return System.currentTimeMillis() / 1000; - } - - private static long getStartupTime() { - // Process start time is also the proc file modified time. - final long uptimeMins = (new File("/proc/self/cmdline")).lastModified(); - if (uptimeMins == 0L) { - return getCrashTime(); - } - return uptimeMins / 1000; - } - - private static String getJavaPackageName() { - return CrashHandler.class.getPackage().getName(); - } - - private static String getProcessName() { - try { - final FileReader reader = new FileReader("/proc/self/cmdline"); - final char[] buffer = new char[64]; - try { - if (reader.read(buffer) > 0) { - // cmdline is delimited by '\0', and we want the first token. - final int nul = Arrays.asList(buffer).indexOf('\0'); - return (new String(buffer, 0, nul < 0 ? buffer.length : nul)).trim(); - } - } finally { - reader.close(); - } - } catch (final IOException e) { - } - - return null; - } - - protected String getAppPackageName() { - final Context context = getAppContext(); - - if (context != null) { - return context.getPackageName(); - } - - // Package name is also the process name in most cases. - String processName = getProcessName(); - if (processName != null) { - return processName; - } - - // Fallback to using CrashHandler's package name. - return getJavaPackageName(); - } - - protected Context getAppContext() { - return appContext; - } - - /** - * Get the crash "extras" to be reported. - * - * @param thread The exception thread - * @param exc An exception - * @return "Extras" in the from of a Bundle - */ - protected Bundle getCrashExtras(final Thread thread, final Throwable exc) { - final Context context = getAppContext(); - final Bundle extras = new Bundle(); - final String pkgName = getAppPackageName(); - final String processName = getProcessName(); - - extras.putString("ProductName", pkgName); - extras.putLong("CrashTime", getCrashTime()); - extras.putLong("StartupTime", getStartupTime()); - extras.putString("AndroidProcessName", getProcessName()); - - if (context != null) { - final PackageManager pkgMgr = context.getPackageManager(); - try { - final PackageInfo pkgInfo = pkgMgr.getPackageInfo(pkgName, 0); - extras.putString("Version", pkgInfo.versionName); - extras.putInt("BuildID", pkgInfo.versionCode); - extras.putLong("InstallTime", pkgInfo.lastUpdateTime / 1000); - } catch (final PackageManager.NameNotFoundException e) { - Log.i(LOGTAG, "Error getting package info", e); - } - } - - extras.putString("JavaStackTrace", getExceptionStackTrace(exc)); - return extras; - } - - /** - * Get the crash minidump content to be reported. - * - * @param thread The exception thread - * @param exc An exception - * @return Minidump content - */ - protected byte[] getCrashDump(final Thread thread, final Throwable exc) { - return new byte[0]; // No minidump. - } - - protected static String normalizeUrlString(final String str) { - if (str == null) { - return ""; - } - return Uri.encode(str); - } - - /** - * Get the server URL to send the crash report to. - * - * @param extras The crash extras Bundle - */ - protected String getServerUrl(final Bundle extras) { - return String.format(DEFAULT_SERVER_URL, - normalizeUrlString(extras.getString("ProductID")), - normalizeUrlString(extras.getString("Version")), - normalizeUrlString(extras.getString("BuildID"))); - } - - /** - * Launch the crash reporter activity that sends the crash report to the server. - * - * @param dumpFile Path for the minidump file - * @param extraFile Path for the crash extra file - * @return Whether the crash reporter was successfully launched - */ - protected boolean launchCrashReporter(final String dumpFile, final String extraFile) { - try { - final Context context = getAppContext(); - final String javaPkg = getJavaPackageName(); - final String pkg = getAppPackageName(); - final String component = javaPkg + ".CrashReporter"; - final String action = javaPkg + ".reportCrash"; - final ProcessBuilder pb; - - if (context != null) { - final Intent intent = new Intent(action); - intent.setComponent(new ComponentName(pkg, component)); - intent.putExtra("minidumpPath", dumpFile); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(intent); - return true; - } - - // Avoid AppConstants dependency for SDK version constants, - // because CrashHandler could be used outside of Fennec code. - if (Build.VERSION.SDK_INT < 17) { - pb = new ProcessBuilder( - "/system/bin/am", "start", - "-a", action, - "-n", pkg + '/' + component, - "--es", "minidumpPath", dumpFile); - } else { - pb = new ProcessBuilder( - "/system/bin/am", "start", - "--user", /* USER_CURRENT_OR_SELF */ "-3", - "-a", action, - "-n", pkg + '/' + component, - "--es", "minidumpPath", dumpFile); - } - - pb.start().waitFor(); - - } catch (final IOException e) { - Log.e(LOGTAG, "Error launching crash reporter", e); - return false; - - } catch (final InterruptedException e) { - Log.i(LOGTAG, "Interrupted while waiting to launch crash reporter", e); - // Fall-through - } - return true; - } - - /** - * Report an exception to Socorro. - * - * @param thread The exception thread - * @param exc An exception - * @return Whether the exception was successfully reported - */ - protected boolean reportException(final Thread thread, final Throwable exc) { - final Context context = getAppContext(); - final String id = UUID.randomUUID().toString(); - - // Use the cache directory under the app directory to store crash files. - final File dir; - if (context != null) { - dir = context.getCacheDir(); - } else { - dir = new File("/data/data/" + getAppPackageName() + "/cache"); - } - - dir.mkdirs(); - if (!dir.exists()) { - return false; - } - - final File dmpFile = new File(dir, id + ".dmp"); - final File extraFile = new File(dir, id + ".extra"); - - try { - // Write out minidump file as binary. - - final byte[] minidump = getCrashDump(thread, exc); - final FileOutputStream dmpStream = new FileOutputStream(dmpFile); - try { - dmpStream.write(minidump); - } finally { - dmpStream.close(); - } - - } catch (final IOException e) { - Log.e(LOGTAG, "Error writing minidump file", e); - return false; - } - - try { - // Write out crash extra file as text. - - final Bundle extras = getCrashExtras(thread, exc); - final String url = getServerUrl(extras); - extras.putString("ServerURL", url); - - final BufferedWriter extraWriter = new BufferedWriter(new FileWriter(extraFile)); - try { - for (String key : extras.keySet()) { - // Each extra line is in the format, key=value, with newlines escaped. - extraWriter.write(key); - extraWriter.write('='); - extraWriter.write(String.valueOf(extras.get(key)).replace("\n", "\\n")); - extraWriter.write('\n'); - } - } finally { - extraWriter.close(); - } - - } catch (final IOException e) { - Log.e(LOGTAG, "Error writing extra file", e); - return false; - } - - return launchCrashReporter(dmpFile.getAbsolutePath(), extraFile.getAbsolutePath()); - } - - /** - * Implements the default behavior for handling uncaught exceptions. - * - * @param thread The exception thread - * @param exc An uncaught exception - */ - @Override - public void uncaughtException(Thread thread, Throwable exc) { - if (this.crashing) { - // Prevent possible infinite recusions. - return; - } - - if (thread == null) { - // Gecko may pass in null for thread to denote the current thread. - thread = Thread.currentThread(); - } - - try { - if (!this.unregistered) { - // Only process crash ourselves if we have not been unregistered. - - this.crashing = true; - exc = getRootException(exc); - logException(thread, exc); - - if (reportException(thread, exc)) { - // Reporting succeeded; we can terminate our process now. - return; - } - } - - if (systemUncaughtHandler != null) { - // Follow the chain of uncaught handlers. - systemUncaughtHandler.uncaughtException(thread, exc); - } - } finally { - terminateProcess(); - } - } - - public static CrashHandler createDefaultCrashHandler(final Context context) { - return new CrashHandler(context) { - @Override - protected Bundle getCrashExtras(final Thread thread, final Throwable exc) { - final Bundle extras = super.getCrashExtras(thread, exc); - - extras.putString("ProductName", AppConstants.MOZ_APP_BASENAME); - extras.putString("ProductID", AppConstants.MOZ_APP_ID); - extras.putString("Version", AppConstants.MOZ_APP_VERSION); - extras.putString("BuildID", AppConstants.MOZ_APP_BUILDID); - extras.putString("Vendor", AppConstants.MOZ_APP_VENDOR); - extras.putString("ReleaseChannel", AppConstants.MOZ_UPDATE_CHANNEL); - return extras; - } - - @Override - public boolean reportException(final Thread thread, final Throwable exc) { - return false; - } - }; - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/EventDispatcher.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/EventDispatcher.java deleted file mode 100644 index 6c4e67b43..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/EventDispatcher.java +++ /dev/null @@ -1,503 +0,0 @@ -/* 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; - -import org.mozilla.gecko.annotation.ReflectionTarget; -import org.mozilla.gecko.annotation.RobocopTarget; -import org.mozilla.gecko.GeckoAppShell; -import org.mozilla.gecko.util.BundleEventListener; -import org.mozilla.gecko.util.EventCallback; -import org.mozilla.gecko.util.GeckoEventListener; -import org.mozilla.gecko.util.NativeEventListener; -import org.mozilla.gecko.util.NativeJSContainer; -import org.mozilla.gecko.util.NativeJSObject; -import org.mozilla.gecko.util.ThreadUtils; - -import org.json.JSONException; -import org.json.JSONObject; - -import android.os.Bundle; -import android.os.Handler; -import android.util.Log; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CopyOnWriteArrayList; - -@RobocopTarget -public final class EventDispatcher { - private static final String LOGTAG = "GeckoEventDispatcher"; - /* package */ static final String GUID = "__guid__"; - private static final String STATUS_ERROR = "error"; - private static final String STATUS_SUCCESS = "success"; - - private static final EventDispatcher INSTANCE = new EventDispatcher(); - - /** - * The capacity of a HashMap is rounded up to the next power-of-2. Every time the size - * of the map goes beyond 75% of the capacity, the map is rehashed. Therefore, to - * empirically determine the initial capacity that avoids rehashing, we need to - * determine the initial size, divide it by 75%, and round up to the next power-of-2. - */ - private static final int DEFAULT_GECKO_NATIVE_EVENTS_COUNT = 0; // Default for HashMap - private static final int DEFAULT_GECKO_JSON_EVENTS_COUNT = 256; // Empirically measured - private static final int DEFAULT_UI_EVENTS_COUNT = 0; // Default for HashMap - private static final int DEFAULT_BACKGROUND_EVENTS_COUNT = 0; // Default for HashMap - - private final Map<String, List<NativeEventListener>> mGeckoThreadNativeListeners = - new HashMap<String, List<NativeEventListener>>(DEFAULT_GECKO_NATIVE_EVENTS_COUNT); - private final Map<String, List<GeckoEventListener>> mGeckoThreadJSONListeners = - new HashMap<String, List<GeckoEventListener>>(DEFAULT_GECKO_JSON_EVENTS_COUNT); - private final Map<String, List<BundleEventListener>> mUiThreadListeners = - new HashMap<String, List<BundleEventListener>>(DEFAULT_UI_EVENTS_COUNT); - private final Map<String, List<BundleEventListener>> mBackgroundThreadListeners = - new HashMap<String, List<BundleEventListener>>(DEFAULT_BACKGROUND_EVENTS_COUNT); - - @ReflectionTarget - public static EventDispatcher getInstance() { - return INSTANCE; - } - - public EventDispatcher() { - } - - private <T> void registerListener(final Class<?> listType, - final Map<String, List<T>> listenersMap, - final T listener, - final String[] events) { - try { - synchronized (listenersMap) { - for (final String event : events) { - List<T> listeners = listenersMap.get(event); - if (listeners == null) { - // Java doesn't let us put Class<? extends List<T>> as the type for listType. - @SuppressWarnings("unchecked") - final Class<? extends List<T>> type = (Class) listType; - listeners = type.newInstance(); - listenersMap.put(event, listeners); - } - if (!AppConstants.RELEASE_OR_BETA && listeners.contains(listener)) { - throw new IllegalStateException("Already registered " + event); - } - listeners.add(listener); - } - } - } catch (final IllegalAccessException | InstantiationException e) { - throw new IllegalArgumentException("Invalid new list type", e); - } - } - - private void checkNotRegisteredElsewhere(final Map<String, ?> allowedMap, - final String[] events) { - if (AppConstants.RELEASE_OR_BETA) { - // for performance reasons, we only check for - // already-registered listeners in non-release builds. - return; - } - for (final Map<String, ?> listenersMap : Arrays.asList(mGeckoThreadNativeListeners, - mGeckoThreadJSONListeners, - mUiThreadListeners, - mBackgroundThreadListeners)) { - if (listenersMap == allowedMap) { - continue; - } - synchronized (listenersMap) { - for (final String event : events) { - if (listenersMap.get(event) != null) { - throw new IllegalStateException( - "Already registered " + event + " under a different type"); - } - } - } - } - } - - private <T> void unregisterListener(final Map<String, List<T>> listenersMap, - final T listener, - final String[] events) { - synchronized (listenersMap) { - for (final String event : events) { - List<T> listeners = listenersMap.get(event); - if ((listeners == null || - !listeners.remove(listener)) && !AppConstants.RELEASE_OR_BETA) { - throw new IllegalArgumentException(event + " was not registered"); - } - } - } - } - - public void registerGeckoThreadListener(final NativeEventListener listener, - final String... events) { - checkNotRegisteredElsewhere(mGeckoThreadNativeListeners, events); - - // For listeners running on the Gecko thread, we want to notify the listeners - // outside of our synchronized block, because the listeners may take an - // indeterminate amount of time to run. Therefore, to ensure concurrency when - // iterating the list outside of the synchronized block, we use a - // CopyOnWriteArrayList. - registerListener(CopyOnWriteArrayList.class, - mGeckoThreadNativeListeners, listener, events); - } - - @Deprecated // Use NativeEventListener instead - public void registerGeckoThreadListener(final GeckoEventListener listener, - final String... events) { - checkNotRegisteredElsewhere(mGeckoThreadJSONListeners, events); - - registerListener(CopyOnWriteArrayList.class, - mGeckoThreadJSONListeners, listener, events); - } - - public void registerUiThreadListener(final BundleEventListener listener, - final String... events) { - checkNotRegisteredElsewhere(mUiThreadListeners, events); - - registerListener(ArrayList.class, - mUiThreadListeners, listener, events); - } - - @ReflectionTarget - public void registerBackgroundThreadListener(final BundleEventListener listener, - final String... events) { - checkNotRegisteredElsewhere(mBackgroundThreadListeners, events); - - registerListener(ArrayList.class, - mBackgroundThreadListeners, listener, events); - } - - public void unregisterGeckoThreadListener(final NativeEventListener listener, - final String... events) { - unregisterListener(mGeckoThreadNativeListeners, listener, events); - } - - @Deprecated // Use NativeEventListener instead - public void unregisterGeckoThreadListener(final GeckoEventListener listener, - final String... events) { - unregisterListener(mGeckoThreadJSONListeners, listener, events); - } - - public void unregisterUiThreadListener(final BundleEventListener listener, - final String... events) { - unregisterListener(mUiThreadListeners, listener, events); - } - - public void unregisterBackgroundThreadListener(final BundleEventListener listener, - final String... events) { - unregisterListener(mBackgroundThreadListeners, listener, events); - } - - private List<NativeEventListener> getNativeListeners(final String type) { - final List<NativeEventListener> listeners; - synchronized (mGeckoThreadNativeListeners) { - listeners = mGeckoThreadNativeListeners.get(type); - } - return listeners; - } - - private List<GeckoEventListener> getGeckoListeners(final String type) { - final List<GeckoEventListener> listeners; - synchronized (mGeckoThreadJSONListeners) { - listeners = mGeckoThreadJSONListeners.get(type); - } - return listeners; - } - - public boolean dispatchEvent(final NativeJSContainer message) { - // First try native listeners. - final String type = message.optString("type", null); - if (type == null) { - Log.e(LOGTAG, "JSON message must have a type property"); - return true; // It may seem odd to return true here, but it's necessary to preserve the correct behavior. - } - - final List<NativeEventListener> listeners = getNativeListeners(type); - - final String guid = message.optString(GUID, null); - EventCallback callback = null; - if (guid != null) { - callback = new GeckoEventCallback(guid, type); - } - - if (listeners != null) { - if (listeners.isEmpty()) { - Log.w(LOGTAG, "No listeners for " + type); - - // There were native listeners, and they're gone. Return a failure rather than - // looking for JSON listeners. This is an optimization, as we can safely assume - // that an event which previously had native listeners will never have JSON - // listeners. - return false; - } - try { - for (final NativeEventListener listener : listeners) { - listener.handleMessage(type, message, callback); - } - } catch (final NativeJSObject.InvalidPropertyException e) { - Log.e(LOGTAG, "Exception occurred while handling " + type, e); - } - // If we found native listeners, we assume we don't have any other types of listeners - // and return early. This assumption is checked when registering listeners. - return true; - } - - // Check for thread event listeners before checking for JSON event listeners, - // because checking for thread listeners is very fast and doesn't require us to - // serialize into JSON and construct a JSONObject. - if (dispatchToThreads(type, message, /* bundle */ null, callback)) { - // If we found thread listeners, we assume we don't have any other types of listeners - // and return early. This assumption is checked when registering listeners. - return true; - } - - try { - // If we didn't find native listeners, try JSON listeners. - return dispatchEvent(new JSONObject(message.toString()), callback); - } catch (final JSONException e) { - Log.e(LOGTAG, "Cannot parse JSON", e); - } catch (final UnsupportedOperationException e) { - Log.e(LOGTAG, "Cannot convert message to JSON", e); - } - - return true; - } - - /** - * Dispatch event to any registered Bundle listeners (non-Gecko thread listeners). - * - * @param message Bundle message with "type" value specifying the event type. - */ - public void dispatch(final Bundle message) { - dispatch(message, /* callback */ null); - } - - /** - * Dispatch event to any registered Bundle listeners (non-Gecko thread listeners). - * - * @param message Bundle message with "type" value specifying the event type. - * @param callback Optional object for callbacks from events. - */ - public void dispatch(final Bundle message, final EventCallback callback) { - if (message == null) { - throw new IllegalArgumentException("Null message"); - } - - final String type = message.getCharSequence("type").toString(); - if (type == null) { - Log.e(LOGTAG, "Bundle message must have a type property"); - return; - } - dispatchToThreads(type, /* js */ null, message, /* callback */ callback); - } - - /** - * Dispatch event to any registered Bundle listeners (non-Gecko thread listeners). - * - * @param type Event type - * @param message Bundle message - */ - public void dispatch(final String type, final Bundle message) { - dispatch(type, message, /* callback */ null); - } - - /** - * Dispatch event to any registered Bundle listeners (non-Gecko thread listeners). - * - * @param type Event type - * @param message Bundle message - * @param callback Optional object for callbacks from events. - */ - public void dispatch(final String type, final Bundle message, final EventCallback callback) { - dispatchToThreads(type, /* js */ null, message, /* callback */ callback); - } - - private boolean dispatchToThreads(final String type, - final NativeJSObject jsMessage, - final Bundle bundleMessage, - final EventCallback callback) { - if (dispatchToThread(type, jsMessage, bundleMessage, callback, - mUiThreadListeners, ThreadUtils.getUiHandler())) { - return true; - } - - if (dispatchToThread(type, jsMessage, bundleMessage, callback, - mBackgroundThreadListeners, ThreadUtils.getBackgroundHandler())) { - return true; - } - - if (jsMessage == null) { - Log.w(LOGTAG, "No listeners for " + type + " in dispatchToThreads"); - } - - if (!AppConstants.RELEASE_OR_BETA && jsMessage == null) { - // We're dispatching a Bundle message. Because Gecko thread listeners are not - // supported for Bundle messages, do a sanity check to make sure we don't have - // matching Gecko thread listeners. - boolean hasGeckoListener = false; - synchronized (mGeckoThreadNativeListeners) { - hasGeckoListener |= mGeckoThreadNativeListeners.containsKey(type); - } - synchronized (mGeckoThreadJSONListeners) { - hasGeckoListener |= mGeckoThreadJSONListeners.containsKey(type); - } - if (hasGeckoListener) { - throw new IllegalStateException( - "Dispatching Bundle message to Gecko listener " + type); - } - } - - return false; - } - - private boolean dispatchToThread(final String type, - final NativeJSObject jsMessage, - final Bundle bundleMessage, - final EventCallback callback, - final Map<String, List<BundleEventListener>> listenersMap, - final Handler thread) { - // We need to hold the lock throughout dispatching, to ensure the listeners list - // is consistent, while we iterate over it. We don't have to worry about listeners - // running for a long time while we have the lock, because the listeners will run - // on a separate thread. - synchronized (listenersMap) { - final List<BundleEventListener> listeners = listenersMap.get(type); - if (listeners == null) { - return false; - } - - if (listeners.isEmpty()) { - Log.w(LOGTAG, "No listeners for " + type + " in dispatchToThread"); - - // There were native listeners, and they're gone. - return false; - } - - final Bundle messageAsBundle; - try { - messageAsBundle = jsMessage != null ? jsMessage.toBundle() : bundleMessage; - } catch (final NativeJSObject.InvalidPropertyException e) { - Log.e(LOGTAG, "Exception occurred while handling " + type, e); - return true; - } - - // Event listeners will call | callback.sendError | if applicable. - for (final BundleEventListener listener : listeners) { - thread.post(new Runnable() { - @Override - public void run() { - listener.handleMessage(type, messageAsBundle, callback); - } - }); - } - return true; - } - } - - public boolean dispatchEvent(final JSONObject message, final EventCallback callback) { - // { - // "type": "value", - // "event_specific": "value", - // ... - try { - final String type = message.getString("type"); - - final List<GeckoEventListener> listeners = getGeckoListeners(type); - - if (listeners == null || listeners.isEmpty()) { - Log.w(LOGTAG, "No listeners for " + type + " in dispatchEvent"); - - return false; - } - - for (final GeckoEventListener listener : listeners) { - listener.handleMessage(type, message); - } - } catch (final JSONException e) { - Log.e(LOGTAG, "handleGeckoMessage throws " + e, e); - } - - return true; - } - - @RobocopTarget - @Deprecated - public static void sendResponse(JSONObject message, Object response) { - sendResponseHelper(STATUS_SUCCESS, message, response); - } - - @Deprecated - public static void sendError(JSONObject message, Object response) { - sendResponseHelper(STATUS_ERROR, message, response); - } - - @Deprecated - private static void sendResponseHelper(String status, JSONObject message, Object response) { - try { - final String topic = message.getString("type") + ":Response"; - final JSONObject wrapper = new JSONObject(); - wrapper.put(GUID, message.getString(GUID)); - wrapper.put("status", status); - wrapper.put("response", response); - - if (ThreadUtils.isOnGeckoThread()) { - GeckoAppShell.syncNotifyObservers(topic, wrapper.toString()); - } else { - GeckoAppShell.notifyObservers(topic, wrapper.toString(), - GeckoThread.State.PROFILE_READY); - } - } catch (final JSONException e) { - Log.e(LOGTAG, "Unable to send response", e); - } - } - - /* package */ static class GeckoEventCallback implements EventCallback { - private final String guid; - private final String type; - private boolean sent; - - public GeckoEventCallback(final String guid, final String type) { - this.guid = guid; - this.type = type; - } - - @Override - public void sendSuccess(final Object response) { - sendResponse(STATUS_SUCCESS, response); - } - - @Override - public void sendError(final Object response) { - sendResponse(STATUS_ERROR, response); - } - - private void sendResponse(final String status, final Object response) { - if (sent) { - throw new IllegalStateException("Callback has already been executed for type=" + - type + ", guid=" + guid); - } - - sent = true; - - try { - final String topic = type + ":Response"; - final JSONObject wrapper = new JSONObject(); - wrapper.put(GUID, guid); - wrapper.put("status", status); - wrapper.put("response", response); - - if (ThreadUtils.isOnGeckoThread()) { - GeckoAppShell.syncNotifyObservers(topic, wrapper.toString()); - } else { - GeckoAppShell.notifyObservers(topic, wrapper.toString(), - GeckoThread.State.PROFILE_READY); - } - } catch (final JSONException e) { - Log.e(LOGTAG, "Unable to send response for: " + type, e); - } - } - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAccessibility.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAccessibility.java deleted file mode 100644 index 8d4c0fb2a..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAccessibility.java +++ /dev/null @@ -1,410 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.mozilla.gecko.AppConstants.Versions; -import org.mozilla.gecko.util.ThreadUtils; -import org.mozilla.gecko.util.UIAsyncTask; - -import android.content.Context; -import android.graphics.Rect; -import android.os.Bundle; -import android.util.Log; -import android.view.View; -import android.view.ViewParent; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; -import android.view.accessibility.AccessibilityNodeInfo; -import android.view.accessibility.AccessibilityNodeProvider; - -import com.googlecode.eyesfree.braille.selfbraille.SelfBrailleClient; -import com.googlecode.eyesfree.braille.selfbraille.WriteData; - -public class GeckoAccessibility { - private static final String LOGTAG = "GeckoAccessibility"; - private static final int VIRTUAL_ENTRY_POINT_BEFORE = 1; - private static final int VIRTUAL_CURSOR_POSITION = 2; - private static final int VIRTUAL_ENTRY_POINT_AFTER = 3; - - private static boolean sEnabled; - // Used to store the JSON message and populate the event later in the code path. - private static JSONObject sHoverEnter; - private static AccessibilityNodeInfo sVirtualCursorNode; - private static int sCurrentNode; - - // This is the number Brailleback uses to start indexing routing keys. - private static final int BRAILLE_CLICK_BASE_INDEX = -275000000; - private static SelfBrailleClient sSelfBrailleClient; - - public static void updateAccessibilitySettings (final Context context) { - new UIAsyncTask.WithoutParams<Void>(ThreadUtils.getBackgroundHandler()) { - @Override - public Void doInBackground() { - JSONObject ret = new JSONObject(); - sEnabled = false; - AccessibilityManager accessibilityManager = - (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); - sEnabled = accessibilityManager.isEnabled() && accessibilityManager.isTouchExplorationEnabled(); - if (Versions.feature16Plus && sEnabled && sSelfBrailleClient == null) { - sSelfBrailleClient = new SelfBrailleClient(context, false); - } - - try { - ret.put("enabled", sEnabled); - } catch (Exception ex) { - Log.e(LOGTAG, "Error building JSON arguments for Accessibility:Settings:", ex); - } - - GeckoAppShell.notifyObservers("Accessibility:Settings", ret.toString()); - return null; - } - - @Override - public void onPostExecute(Void args) { - final GeckoAppShell.GeckoInterface geckoInterface = GeckoAppShell.getGeckoInterface(); - if (geckoInterface == null) { - return; - } - geckoInterface.setAccessibilityEnabled(sEnabled); - } - }.execute(); - } - - private static void populateEventFromJSON (AccessibilityEvent event, JSONObject message) { - final JSONArray textArray = message.optJSONArray("text"); - if (textArray != null) { - for (int i = 0; i < textArray.length(); i++) - event.getText().add(textArray.optString(i)); - } - - event.setContentDescription(message.optString("description")); - event.setEnabled(message.optBoolean("enabled", true)); - event.setChecked(message.optBoolean("checked")); - event.setPassword(message.optBoolean("password")); - event.setAddedCount(message.optInt("addedCount", -1)); - event.setRemovedCount(message.optInt("removedCount", -1)); - event.setFromIndex(message.optInt("fromIndex", -1)); - event.setItemCount(message.optInt("itemCount", -1)); - event.setCurrentItemIndex(message.optInt("currentItemIndex", -1)); - event.setBeforeText(message.optString("beforeText")); - event.setToIndex(message.optInt("toIndex", -1)); - event.setScrollable(message.optBoolean("scrollable")); - event.setScrollX(message.optInt("scrollX", -1)); - event.setScrollY(message.optInt("scrollY", -1)); - event.setMaxScrollX(message.optInt("maxScrollX", -1)); - event.setMaxScrollY(message.optInt("maxScrollY", -1)); - } - - private static void sendDirectAccessibilityEvent(int eventType, JSONObject message) { - final Context context = GeckoAppShell.getApplicationContext(); - final AccessibilityEvent accEvent = AccessibilityEvent.obtain(eventType); - accEvent.setClassName(GeckoAccessibility.class.getName()); - accEvent.setPackageName(context.getPackageName()); - populateEventFromJSON(accEvent, message); - AccessibilityManager accessibilityManager = - (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); - try { - accessibilityManager.sendAccessibilityEvent(accEvent); - } catch (IllegalStateException e) { - // Accessibility is off. - } - } - - public static boolean isEnabled() { - return sEnabled; - } - - public static void sendAccessibilityEvent (final JSONObject message) { - if (!sEnabled) - return; - - final int eventType = message.optInt("eventType", -1); - if (eventType < 0) { - Log.e(LOGTAG, "No accessibility event type provided"); - return; - } - - sendAccessibilityEvent(message, eventType); - } - - public static void sendAccessibilityEvent (final JSONObject message, final int eventType) { - if (!sEnabled) - return; - - final String exitView = message.optString("exitView"); - if (exitView.equals("moveNext")) { - sCurrentNode = VIRTUAL_ENTRY_POINT_AFTER; - } else if (exitView.equals("movePrevious")) { - sCurrentNode = VIRTUAL_ENTRY_POINT_BEFORE; - } else { - sCurrentNode = VIRTUAL_CURSOR_POSITION; - } - - if (Versions.preJB) { - // Before Jelly Bean we send events directly from here while spoofing the source by setting - // the package and class name manually. - ThreadUtils.postToBackgroundThread(new Runnable() { - @Override - public void run() { - sendDirectAccessibilityEvent(eventType, message); - } - }); - } else { - // In Jelly Bean we populate an AccessibilityNodeInfo with the minimal amount of data to have - // it work with TalkBack. - final View view = GeckoAppShell.getLayerView(); - if (view == null) - return; - - if (sVirtualCursorNode == null) - sVirtualCursorNode = AccessibilityNodeInfo.obtain(view, VIRTUAL_CURSOR_POSITION); - sVirtualCursorNode.setEnabled(message.optBoolean("enabled", true)); - sVirtualCursorNode.setClickable(message.optBoolean("clickable")); - sVirtualCursorNode.setCheckable(message.optBoolean("checkable")); - sVirtualCursorNode.setChecked(message.optBoolean("checked")); - sVirtualCursorNode.setPassword(message.optBoolean("password")); - - final JSONArray textArray = message.optJSONArray("text"); - StringBuilder sb = new StringBuilder(); - if (textArray != null && textArray.length() > 0) { - sb.append(textArray.optString(0)); - for (int i = 1; i < textArray.length(); i++) { - sb.append(" ").append(textArray.optString(i)); - } - sVirtualCursorNode.setText(sb.toString()); - } - sVirtualCursorNode.setContentDescription(message.optString("description")); - - JSONObject bounds = message.optJSONObject("bounds"); - if (bounds != null) { - Rect relativeBounds = new Rect(bounds.optInt("left"), bounds.optInt("top"), - bounds.optInt("right"), bounds.optInt("bottom")); - sVirtualCursorNode.setBoundsInParent(relativeBounds); - int[] locationOnScreen = new int[2]; - view.getLocationOnScreen(locationOnScreen); - Rect screenBounds = new Rect(relativeBounds); - screenBounds.offset(locationOnScreen[0], locationOnScreen[1]); - sVirtualCursorNode.setBoundsInScreen(screenBounds); - } - - final JSONObject braille = message.optJSONObject("brailleOutput"); - if (braille != null) { - sendBrailleText(view, braille.optString("text"), - braille.optInt("selectionStart"), braille.optInt("selectionEnd")); - } - - if (eventType == AccessibilityEvent.TYPE_VIEW_HOVER_ENTER) { - sHoverEnter = message; - } - - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - final AccessibilityEvent event = AccessibilityEvent.obtain(eventType); - event.setPackageName(GeckoAppShell.getApplicationContext().getPackageName()); - event.setClassName(GeckoAccessibility.class.getName()); - if (eventType == AccessibilityEvent.TYPE_ANNOUNCEMENT || - eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) { - event.setSource(view, View.NO_ID); - } else { - event.setSource(view, VIRTUAL_CURSOR_POSITION); - } - populateEventFromJSON(event, message); - ((ViewParent) view).requestSendAccessibilityEvent(view, event); - } - }); - - } - } - - private static void sendBrailleText(final View view, final String text, final int selectionStart, final int selectionEnd) { - AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(view, VIRTUAL_CURSOR_POSITION); - WriteData data = WriteData.forInfo(info); - data.setText(text); - // Set either the focus blink or the current caret position/selection - data.setSelectionStart(selectionStart); - data.setSelectionEnd(selectionEnd); - sSelfBrailleClient.write(data); - } - - public static void setDelegate(View view) { - // Only use this delegate in Jelly Bean. - if (Versions.feature16Plus) { - view.setAccessibilityDelegate(new GeckoAccessibilityDelegate()); - view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); - } - } - - public static void setAccessibilityManagerListeners(final Context context) { - AccessibilityManager accessibilityManager = - (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); - - accessibilityManager.addAccessibilityStateChangeListener(new AccessibilityManager.AccessibilityStateChangeListener() { - @Override - public void onAccessibilityStateChanged(boolean enabled) { - updateAccessibilitySettings(context); - } - }); - - if (Versions.feature19Plus) { - accessibilityManager.addTouchExplorationStateChangeListener(new AccessibilityManager.TouchExplorationStateChangeListener() { - @Override - public void onTouchExplorationStateChanged(boolean enabled) { - updateAccessibilitySettings(context); - } - }); - } - } - - public static void onLayerViewFocusChanged(boolean gainFocus) { - if (sEnabled) - GeckoAppShell.notifyObservers("Accessibility:Focus", gainFocus ? "true" : "false"); - } - - public static class GeckoAccessibilityDelegate extends View.AccessibilityDelegate { - AccessibilityNodeProvider mAccessibilityNodeProvider; - - @Override - public AccessibilityNodeProvider getAccessibilityNodeProvider(final View host) { - if (mAccessibilityNodeProvider == null) - // The accessibility node structure for web content consists of 3 LayerView child nodes: - // 1. VIRTUAL_ENTRY_POINT_BEFORE: Represents the entry point before the LayerView. - // 2. VIRTUAL_CURSOR_POSITION: Represents the current position of the virtual cursor. - // 3. VIRTUAL_ENTRY_POINT_AFTER: Represents the entry point after the LayerView. - mAccessibilityNodeProvider = new AccessibilityNodeProvider() { - @Override - public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualDescendantId) { - AccessibilityNodeInfo info = (virtualDescendantId == VIRTUAL_CURSOR_POSITION && sVirtualCursorNode != null) ? - AccessibilityNodeInfo.obtain(sVirtualCursorNode) : - AccessibilityNodeInfo.obtain(host, virtualDescendantId); - - switch (virtualDescendantId) { - case View.NO_ID: - // This is the parent LayerView node, populate it with children. - onInitializeAccessibilityNodeInfo(host, info); - info.addChild(host, VIRTUAL_ENTRY_POINT_BEFORE); - info.addChild(host, VIRTUAL_CURSOR_POSITION); - info.addChild(host, VIRTUAL_ENTRY_POINT_AFTER); - break; - default: - info.setParent(host); - info.setSource(host, virtualDescendantId); - info.setVisibleToUser(host.isShown()); - info.setPackageName(GeckoAppShell.getApplicationContext().getPackageName()); - info.setClassName(host.getClass().getName()); - info.setEnabled(true); - info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS); - info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS); - info.addAction(AccessibilityNodeInfo.ACTION_CLICK); - info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK); - info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); - info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); - info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); - info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); - info.addAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT); - info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT); - info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER | - AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD | - AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE | - AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH); - break; - } - return info; - } - - @Override - public boolean performAction (int virtualViewId, int action, Bundle arguments) { - if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) { - // The accessibility focus is permanently on the middle node, VIRTUAL_CURSOR_POSITION. - // When we enter the view forward or backward we just ask Gecko to get focus, keeping the current position. - if (virtualViewId == VIRTUAL_CURSOR_POSITION && sHoverEnter != null) { - GeckoAccessibility.sendAccessibilityEvent(sHoverEnter, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED); - } else { - GeckoAppShell.notifyObservers("Accessibility:Focus", "true"); - } - return true; - } else if (action == AccessibilityNodeInfo.ACTION_CLICK && virtualViewId == VIRTUAL_CURSOR_POSITION) { - GeckoAppShell.notifyObservers("Accessibility:ActivateObject", null); - return true; - } else if (action == AccessibilityNodeInfo.ACTION_LONG_CLICK && virtualViewId == VIRTUAL_CURSOR_POSITION) { - GeckoAppShell.notifyObservers("Accessibility:LongPress", null); - return true; - } else if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD && virtualViewId == VIRTUAL_CURSOR_POSITION) { - GeckoAppShell.notifyObservers("Accessibility:ScrollForward", null); - return true; - } else if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD && virtualViewId == VIRTUAL_CURSOR_POSITION) { - GeckoAppShell.notifyObservers("Accessibility:ScrollBackward", null); - return true; - } else if (action == AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT && virtualViewId == VIRTUAL_CURSOR_POSITION) { - String traversalRule = ""; - if (arguments != null) { - traversalRule = arguments.getString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING); - } - GeckoAppShell.notifyObservers("Accessibility:NextObject", traversalRule); - return true; - } else if (action == AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT && virtualViewId == VIRTUAL_CURSOR_POSITION) { - String traversalRule = ""; - if (arguments != null) { - traversalRule = arguments.getString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING); - } - GeckoAppShell.notifyObservers("Accessibility:PreviousObject", traversalRule); - return true; - } else if (action == AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY && - virtualViewId == VIRTUAL_CURSOR_POSITION) { - // XXX: Self brailling gives this action with a bogus argument instead of an actual click action; - // the argument value is the BRAILLE_CLICK_BASE_INDEX - the index of the routing key that was hit. - // Other negative values are used by ChromeVox, but we don't support them. - // FAKE_GRANULARITY_READ_CURRENT = -1 - // FAKE_GRANULARITY_READ_TITLE = -2 - // FAKE_GRANULARITY_STOP_SPEECH = -3 - // FAKE_GRANULARITY_CHANGE_SHIFTER = -4 - int granularity = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT); - if (granularity <= BRAILLE_CLICK_BASE_INDEX) { - int keyIndex = BRAILLE_CLICK_BASE_INDEX - granularity; - JSONObject activationData = new JSONObject(); - try { - activationData.put("keyIndex", keyIndex); - } catch (JSONException e) { - return true; - } - GeckoAppShell.notifyObservers("Accessibility:ActivateObject", activationData.toString()); - } else if (granularity > 0) { - JSONObject movementData = new JSONObject(); - try { - movementData.put("direction", "Next"); - movementData.put("granularity", granularity); - } catch (JSONException e) { - return true; - } - GeckoAppShell.notifyObservers("Accessibility:MoveByGranularity", movementData.toString()); - } - return true; - } else if (action == AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY && - virtualViewId == VIRTUAL_CURSOR_POSITION) { - JSONObject movementData = new JSONObject(); - int granularity = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT); - try { - movementData.put("direction", "Previous"); - movementData.put("granularity", granularity); - } catch (JSONException e) { - return true; - } - if (granularity > 0) { - GeckoAppShell.notifyObservers("Accessibility:MoveByGranularity", movementData.toString()); - } - return true; - } - return host.performAccessibilityAction(action, arguments); - } - }; - - return mAccessibilityNodeProvider; - } - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java deleted file mode 100644 index 152981649..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java +++ /dev/null @@ -1,2235 +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; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.PipedInputStream; -import java.io.PipedOutputStream; -import java.net.MalformedURLException; -import java.net.Proxy; -import java.net.URLConnection; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.StringTokenizer; -import java.util.TreeMap; -import java.util.concurrent.ConcurrentHashMap; - -import android.annotation.SuppressLint; -import org.mozilla.gecko.annotation.JNITarget; -import org.mozilla.gecko.annotation.RobocopTarget; -import org.mozilla.gecko.annotation.WrapForJNI; -import org.mozilla.gecko.AppConstants.Versions; -import org.mozilla.gecko.gfx.BitmapUtils; -import org.mozilla.gecko.gfx.LayerView; -import org.mozilla.gecko.gfx.PanZoomController; -import org.mozilla.gecko.permissions.Permissions; -import org.mozilla.gecko.util.EventCallback; -import org.mozilla.gecko.util.GeckoRequest; -import org.mozilla.gecko.util.HardwareCodecCapabilityUtils; -import org.mozilla.gecko.util.HardwareUtils; -import org.mozilla.gecko.util.NativeEventListener; -import org.mozilla.gecko.util.NativeJSContainer; -import org.mozilla.gecko.util.NativeJSObject; -import org.mozilla.gecko.util.ProxySelector; -import org.mozilla.gecko.util.ThreadUtils; - -import android.Manifest; -import android.app.Activity; -import android.app.ActivityManager; -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.content.pm.Signature; -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.ImageFormat; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.SurfaceTexture; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.hardware.Camera; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; -import android.location.Criteria; -import android.location.Location; -import android.location.LocationListener; -import android.location.LocationManager; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.net.Uri; -import android.os.Bundle; -import android.os.Environment; -import android.os.Looper; -import android.os.SystemClock; -import android.os.Vibrator; -import android.provider.Settings; -import android.telephony.TelephonyManager; -import android.text.TextUtils; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.ContextThemeWrapper; -import android.view.Display; -import android.view.HapticFeedbackConstants; -import android.view.Surface; -import android.view.SurfaceView; -import android.view.TextureView; -import android.view.View; -import android.view.WindowManager; -import android.view.inputmethod.InputMethodManager; -import android.webkit.MimeTypeMap; -import android.widget.AbsoluteLayout; - -public class GeckoAppShell -{ - private static final String LOGTAG = "GeckoAppShell"; - - // We have static members only. - private GeckoAppShell() { } - - private static final CrashHandler CRASH_HANDLER = new CrashHandler() { - @Override - protected String getAppPackageName() { - return AppConstants.ANDROID_PACKAGE_NAME; - } - - @Override - protected Context getAppContext() { - return sContextGetter != null ? getApplicationContext() : null; - } - - @Override - protected Bundle getCrashExtras(final Thread thread, final Throwable exc) { - final Bundle extras = super.getCrashExtras(thread, exc); - - extras.putString("ProductName", AppConstants.MOZ_APP_BASENAME); - extras.putString("ProductID", AppConstants.MOZ_APP_ID); - extras.putString("Version", AppConstants.MOZ_APP_VERSION); - extras.putString("BuildID", AppConstants.MOZ_APP_BUILDID); - extras.putString("Vendor", AppConstants.MOZ_APP_VENDOR); - extras.putString("ReleaseChannel", AppConstants.MOZ_UPDATE_CHANNEL); - return extras; - } - - @Override - public void uncaughtException(final Thread thread, final Throwable exc) { - if (GeckoThread.isState(GeckoThread.State.EXITING) || - GeckoThread.isState(GeckoThread.State.EXITED)) { - // We've called System.exit. All exceptions after this point are Android - // berating us for being nasty to it. - return; - } - - super.uncaughtException(thread, exc); - } - - @Override - public boolean reportException(final Thread thread, final Throwable exc) { - try { - if (exc instanceof OutOfMemoryError) { - SharedPreferences prefs = getSharedPreferences(); - SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean(PREFS_OOM_EXCEPTION, true); - - // Synchronously write to disk so we know it's done before we - // shutdown - editor.commit(); - } - - reportJavaCrash(exc, getExceptionStackTrace(exc)); - - } catch (final Throwable e) { - } - - // reportJavaCrash should have caused us to hard crash. If we're still here, - // it probably means Gecko is not loaded, and we should do something else. - return false; - } - }; - - public static CrashHandler ensureCrashHandling() { - // Crash handling is automatically enabled when GeckoAppShell is loaded. - return CRASH_HANDLER; - } - - private static volatile boolean locationHighAccuracyEnabled; - - // See also HardwareUtils.LOW_MEMORY_THRESHOLD_MB. - private static final int HIGH_MEMORY_DEVICE_THRESHOLD_MB = 768; - - static private int sDensityDpi; - static private int sScreenDepth; - - /* Is the value in sVibrationEndTime valid? */ - private static boolean sVibrationMaybePlaying; - - /* Time (in System.nanoTime() units) when the currently-playing vibration - * is scheduled to end. This value is valid only when - * sVibrationMaybePlaying is true. */ - private static long sVibrationEndTime; - - private static Sensor gAccelerometerSensor; - private static Sensor gLinearAccelerometerSensor; - private static Sensor gGyroscopeSensor; - private static Sensor gOrientationSensor; - private static Sensor gProximitySensor; - private static Sensor gLightSensor; - private static Sensor gRotationVectorSensor; - private static Sensor gGameRotationVectorSensor; - - private static final String GECKOREQUEST_RESPONSE_KEY = "response"; - private static final String GECKOREQUEST_ERROR_KEY = "error"; - - /* - * Keep in sync with constants found here: - * http://dxr.mozilla.org/mozilla-central/source/uriloader/base/nsIWebProgressListener.idl - */ - static public final int WPL_STATE_START = 0x00000001; - static public final int WPL_STATE_STOP = 0x00000010; - static public final int WPL_STATE_IS_DOCUMENT = 0x00020000; - static public final int WPL_STATE_IS_NETWORK = 0x00040000; - - /* Keep in sync with constants found here: - http://dxr.mozilla.org/mozilla-central/source/netwerk/base/nsINetworkLinkService.idl - */ - static public final int LINK_TYPE_UNKNOWN = 0; - static public final int LINK_TYPE_ETHERNET = 1; - static public final int LINK_TYPE_USB = 2; - static public final int LINK_TYPE_WIFI = 3; - static public final int LINK_TYPE_WIMAX = 4; - static public final int LINK_TYPE_2G = 5; - static public final int LINK_TYPE_3G = 6; - static public final int LINK_TYPE_4G = 7; - - public static final String PREFS_OOM_EXCEPTION = "OOMException"; - - /* The Android-side API: API methods that Android calls */ - - // helper methods - @WrapForJNI - /* package */ static native void reportJavaCrash(Throwable exc, String stackTrace); - - @WrapForJNI(dispatchTo = "gecko") - public static native void notifyUriVisited(String uri); - - private static LayerView sLayerView; - private static Rect sScreenSize; - - public static void setLayerView(LayerView lv) { - if (sLayerView == lv) { - return; - } - sLayerView = lv; - } - - @RobocopTarget - public static LayerView getLayerView() { - return sLayerView; - } - - /** - * Sends an asynchronous request to Gecko. - * - * The response data will be passed to {@link GeckoRequest#onResponse(NativeJSObject)} if the - * request succeeds; otherwise, {@link GeckoRequest#onError()} will fire. - * - * It can be called from any thread. The GeckoRequest callbacks will be executed on the Gecko thread. - * - * @param request The request to dispatch. Cannot be null. - */ - @RobocopTarget - public static void sendRequestToGecko(final GeckoRequest request) { - final String responseMessage = "Gecko:Request" + request.getId(); - - EventDispatcher.getInstance().registerGeckoThreadListener(new NativeEventListener() { - @Override - public void handleMessage(String event, NativeJSObject message, EventCallback callback) { - EventDispatcher.getInstance().unregisterGeckoThreadListener(this, event); - if (!message.has(GECKOREQUEST_RESPONSE_KEY)) { - request.onError(message.getObject(GECKOREQUEST_ERROR_KEY)); - return; - } - request.onResponse(message.getObject(GECKOREQUEST_RESPONSE_KEY)); - } - }, responseMessage); - - notifyObservers(request.getName(), request.getData()); - } - - // Synchronously notify a Gecko observer; must be called from Gecko thread. - @WrapForJNI(calledFrom = "gecko") - public static native void syncNotifyObservers(String topic, String data); - - @WrapForJNI(stubName = "NotifyObservers", dispatchTo = "gecko") - private static native void nativeNotifyObservers(String topic, String data); - - @RobocopTarget - public static void notifyObservers(final String topic, final String data) { - notifyObservers(topic, data, GeckoThread.State.RUNNING); - } - - public static void notifyObservers(final String topic, final String data, final GeckoThread.State state) { - if (GeckoThread.isStateAtLeast(state)) { - nativeNotifyObservers(topic, data); - } else { - GeckoThread.queueNativeCallUntil( - state, GeckoAppShell.class, "nativeNotifyObservers", - String.class, topic, String.class, data); - } - } - - /* - * The Gecko-side API: API methods that Gecko calls - */ - - @WrapForJNI(exceptionMode = "ignore") - private static String getExceptionStackTrace(Throwable e) { - return CrashHandler.getExceptionStackTrace(CrashHandler.getRootException(e)); - } - - @WrapForJNI(exceptionMode = "ignore") - private static void handleUncaughtException(Throwable e) { - CRASH_HANDLER.uncaughtException(null, e); - } - - @WrapForJNI - public static void openWindowForNotification() { - Intent intent = new Intent(Intent.ACTION_MAIN); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); - intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS); - - getApplicationContext().startActivity(intent); - } - - private static float getLocationAccuracy(Location location) { - float radius = location.getAccuracy(); - return (location.hasAccuracy() && radius > 0) ? radius : 1001; - } - - @SuppressLint("MissingPermission") // Permissions are explicitly checked for in enableLocation() - private static Location getLastKnownLocation(LocationManager lm) { - Location lastKnownLocation = null; - List<String> providers = lm.getAllProviders(); - - for (String provider : providers) { - Location location = lm.getLastKnownLocation(provider); - if (location == null) { - continue; - } - - if (lastKnownLocation == null) { - lastKnownLocation = location; - continue; - } - - long timeDiff = location.getTime() - lastKnownLocation.getTime(); - if (timeDiff > 0 || - (timeDiff == 0 && - getLocationAccuracy(location) < getLocationAccuracy(lastKnownLocation))) { - lastKnownLocation = location; - } - } - - return lastKnownLocation; - } - - @WrapForJNI(calledFrom = "gecko") - @SuppressLint("MissingPermission") // Permissions are explicitly checked for within this method - private static void enableLocation(final boolean enable) { - final Runnable requestLocation = new Runnable() { - @Override - public void run() { - LocationManager lm = getLocationManager(getApplicationContext()); - if (lm == null) { - return; - } - - if (!enable) { - lm.removeUpdates(getLocationListener()); - return; - } - - Location lastKnownLocation = getLastKnownLocation(lm); - if (lastKnownLocation != null) { - getLocationListener().onLocationChanged(lastKnownLocation); - } - - Criteria criteria = new Criteria(); - criteria.setSpeedRequired(false); - criteria.setBearingRequired(false); - criteria.setAltitudeRequired(false); - if (locationHighAccuracyEnabled) { - criteria.setAccuracy(Criteria.ACCURACY_FINE); - criteria.setCostAllowed(true); - criteria.setPowerRequirement(Criteria.POWER_HIGH); - } else { - criteria.setAccuracy(Criteria.ACCURACY_COARSE); - criteria.setCostAllowed(false); - criteria.setPowerRequirement(Criteria.POWER_LOW); - } - - String provider = lm.getBestProvider(criteria, true); - if (provider == null) - return; - - Looper l = Looper.getMainLooper(); - lm.requestLocationUpdates(provider, 100, 0.5f, getLocationListener(), l); - } - }; - - Permissions - .from((Activity) getContext()) - .withPermissions(Manifest.permission.ACCESS_FINE_LOCATION) - .onUIThread() - .doNotPromptIf(!enable) - .run(requestLocation); - } - - private static LocationManager getLocationManager(Context context) { - try { - return (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); - } catch (NoSuchFieldError e) { - // Some Tegras throw exceptions about missing the CONTROL_LOCATION_UPDATES permission, - // which allows enabling/disabling location update notifications from the cell radio. - // CONTROL_LOCATION_UPDATES is not for use by normal applications, but we might be - // hitting this problem if the Tegras are confused about missing cell radios. - Log.e(LOGTAG, "LOCATION_SERVICE not found?!", e); - return null; - } - } - - @WrapForJNI(calledFrom = "gecko") - private static void enableLocationHighAccuracy(final boolean enable) { - locationHighAccuracyEnabled = enable; - } - - @WrapForJNI(calledFrom = "gecko") - private static boolean setAlarm(int aSeconds, int aNanoSeconds) { - AlarmManager am = (AlarmManager) - getApplicationContext().getSystemService(Context.ALARM_SERVICE); - - Intent intent = new Intent(getApplicationContext(), AlarmReceiver.class); - PendingIntent pi = PendingIntent.getBroadcast( - getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); - - // AlarmManager only supports millisecond precision - long time = ((long) aSeconds * 1000) + ((long) aNanoSeconds / 1_000_000L); - am.setExact(AlarmManager.RTC_WAKEUP, time, pi); - - return true; - } - - @WrapForJNI(calledFrom = "gecko") - private static void disableAlarm() { - AlarmManager am = (AlarmManager) - getApplicationContext().getSystemService(Context.ALARM_SERVICE); - - Intent intent = new Intent(getApplicationContext(), AlarmReceiver.class); - PendingIntent pi = PendingIntent.getBroadcast( - getApplicationContext(), 0, intent, - PendingIntent.FLAG_UPDATE_CURRENT); - am.cancel(pi); - } - - @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko") - /* package */ static native void onSensorChanged(int hal_type, float x, float y, float z, - float w, int accuracy, long time); - - @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko") - /* package */ static native void onLocationChanged(double latitude, double longitude, - double altitude, float accuracy, - float bearing, float speed, long time); - - private static class DefaultListeners - implements SensorEventListener, LocationListener, NotificationListener { - @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) { - } - - private static int HalSensorAccuracyFor(int androidAccuracy) { - switch (androidAccuracy) { - case SensorManager.SENSOR_STATUS_UNRELIABLE: - return GeckoHalDefines.SENSOR_ACCURACY_UNRELIABLE; - case SensorManager.SENSOR_STATUS_ACCURACY_LOW: - return GeckoHalDefines.SENSOR_ACCURACY_LOW; - case SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM: - return GeckoHalDefines.SENSOR_ACCURACY_MED; - case SensorManager.SENSOR_STATUS_ACCURACY_HIGH: - return GeckoHalDefines.SENSOR_ACCURACY_HIGH; - } - return GeckoHalDefines.SENSOR_ACCURACY_UNKNOWN; - } - - @Override - public void onSensorChanged(SensorEvent s) { - int sensor_type = s.sensor.getType(); - int hal_type = 0; - float x = 0.0f, y = 0.0f, z = 0.0f, w = 0.0f; - final int accuracy = HalSensorAccuracyFor(s.accuracy); - // SensorEvent timestamp is in nanoseconds, Gecko expects microseconds. - final long time = s.timestamp / 1000; - - switch (sensor_type) { - case Sensor.TYPE_ACCELEROMETER: - case Sensor.TYPE_LINEAR_ACCELERATION: - case Sensor.TYPE_ORIENTATION: - if (sensor_type == Sensor.TYPE_ACCELEROMETER) { - hal_type = GeckoHalDefines.SENSOR_ACCELERATION; - } else if (sensor_type == Sensor.TYPE_LINEAR_ACCELERATION) { - hal_type = GeckoHalDefines.SENSOR_LINEAR_ACCELERATION; - } else { - hal_type = GeckoHalDefines.SENSOR_ORIENTATION; - } - x = s.values[0]; - y = s.values[1]; - z = s.values[2]; - break; - - case Sensor.TYPE_GYROSCOPE: - hal_type = GeckoHalDefines.SENSOR_GYROSCOPE; - x = (float) Math.toDegrees(s.values[0]); - y = (float) Math.toDegrees(s.values[1]); - z = (float) Math.toDegrees(s.values[2]); - break; - - case Sensor.TYPE_PROXIMITY: - hal_type = GeckoHalDefines.SENSOR_PROXIMITY; - x = s.values[0]; - z = s.sensor.getMaximumRange(); - break; - - case Sensor.TYPE_LIGHT: - hal_type = GeckoHalDefines.SENSOR_LIGHT; - x = s.values[0]; - break; - - case Sensor.TYPE_ROTATION_VECTOR: - case Sensor.TYPE_GAME_ROTATION_VECTOR: // API >= 18 - hal_type = (sensor_type == Sensor.TYPE_ROTATION_VECTOR ? - GeckoHalDefines.SENSOR_ROTATION_VECTOR : - GeckoHalDefines.SENSOR_GAME_ROTATION_VECTOR); - x = s.values[0]; - y = s.values[1]; - z = s.values[2]; - if (s.values.length >= 4) { - w = s.values[3]; - } else { - // s.values[3] was optional in API <= 18, so we need to compute it - // The values form a unit quaternion, so we can compute the angle of - // rotation purely based on the given 3 values. - w = 1.0f - s.values[0] * s.values[0] - - s.values[1] * s.values[1] - s.values[2] * s.values[2]; - w = (w > 0.0f) ? (float) Math.sqrt(w) : 0.0f; - } - break; - } - - GeckoAppShell.onSensorChanged(hal_type, x, y, z, w, accuracy, time); - } - - // Geolocation. - @Override - public void onLocationChanged(Location location) { - // No logging here: user-identifying information. - GeckoAppShell.onLocationChanged(location.getLatitude(), location.getLongitude(), - location.getAltitude(), location.getAccuracy(), - location.getBearing(), location.getSpeed(), - location.getTime()); - } - - @Override - public void onProviderDisabled(String provider) - { - } - - @Override - public void onProviderEnabled(String provider) - { - } - - @Override - public void onStatusChanged(String provider, int status, Bundle extras) - { - } - - @Override // NotificationListener - public void showNotification(String name, String cookie, String host, - String title, String text, String imageUrl) { - // Default is to not show the notification, and immediate send close message. - GeckoAppShell.onNotificationClose(name, cookie); - } - - @Override // NotificationListener - public void showPersistentNotification(String name, String cookie, String host, - String title, String text, String imageUrl, - String data) { - // Default is to not show the notification, and immediate send close message. - GeckoAppShell.onNotificationClose(name, cookie); - } - - @Override // NotificationListener - public void closeNotification(String name) { - // Do nothing. - } - } - - private static final DefaultListeners DEFAULT_LISTENERS = new DefaultListeners(); - private static SensorEventListener sSensorListener = DEFAULT_LISTENERS; - private static LocationListener sLocationListener = DEFAULT_LISTENERS; - private static NotificationListener sNotificationListener = DEFAULT_LISTENERS; - - public static SensorEventListener getSensorListener() { - return sSensorListener; - } - - public static void setSensorListener(final SensorEventListener listener) { - sSensorListener = (listener != null) ? listener : DEFAULT_LISTENERS; - } - - public static LocationListener getLocationListener() { - return sLocationListener; - } - - public static void setLocationListener(final LocationListener listener) { - sLocationListener = (listener != null) ? listener : DEFAULT_LISTENERS; - } - - public static NotificationListener getNotificationListener() { - return sNotificationListener; - } - - public static void setNotificationListener(final NotificationListener listener) { - sNotificationListener = (listener != null) ? listener : DEFAULT_LISTENERS; - } - - @WrapForJNI(calledFrom = "gecko") - private static void enableSensor(int aSensortype) { - GeckoInterface gi = getGeckoInterface(); - if (gi == null) { - return; - } - SensorManager sm = (SensorManager) - getApplicationContext().getSystemService(Context.SENSOR_SERVICE); - - switch (aSensortype) { - case GeckoHalDefines.SENSOR_GAME_ROTATION_VECTOR: - if (gGameRotationVectorSensor == null) { - gGameRotationVectorSensor = sm.getDefaultSensor(15); - // sm.getDefaultSensor( - // Sensor.TYPE_GAME_ROTATION_VECTOR); // API >= 18 - } - if (gGameRotationVectorSensor != null) { - sm.registerListener(getSensorListener(), - gGameRotationVectorSensor, - SensorManager.SENSOR_DELAY_FASTEST); - } - if (gGameRotationVectorSensor != null) { - break; - } - // Fallthrough - - case GeckoHalDefines.SENSOR_ROTATION_VECTOR: - if (gRotationVectorSensor == null) { - gRotationVectorSensor = sm.getDefaultSensor( - Sensor.TYPE_ROTATION_VECTOR); - } - if (gRotationVectorSensor != null) { - sm.registerListener(getSensorListener(), - gRotationVectorSensor, - SensorManager.SENSOR_DELAY_FASTEST); - } - if (gRotationVectorSensor != null) { - break; - } - // Fallthrough - - case GeckoHalDefines.SENSOR_ORIENTATION: - if (gOrientationSensor == null) { - gOrientationSensor = sm.getDefaultSensor( - Sensor.TYPE_ORIENTATION); - } - if (gOrientationSensor != null) { - sm.registerListener(getSensorListener(), - gOrientationSensor, - SensorManager.SENSOR_DELAY_FASTEST); - } - break; - - case GeckoHalDefines.SENSOR_ACCELERATION: - if (gAccelerometerSensor == null) { - gAccelerometerSensor = sm.getDefaultSensor( - Sensor.TYPE_ACCELEROMETER); - } - if (gAccelerometerSensor != null) { - sm.registerListener(getSensorListener(), - gAccelerometerSensor, - SensorManager.SENSOR_DELAY_FASTEST); - } - break; - - case GeckoHalDefines.SENSOR_PROXIMITY: - if (gProximitySensor == null) { - gProximitySensor = sm.getDefaultSensor(Sensor.TYPE_PROXIMITY); - } - if (gProximitySensor != null) { - sm.registerListener(getSensorListener(), - gProximitySensor, - SensorManager.SENSOR_DELAY_NORMAL); - } - break; - - case GeckoHalDefines.SENSOR_LIGHT: - if (gLightSensor == null) { - gLightSensor = sm.getDefaultSensor(Sensor.TYPE_LIGHT); - } - if (gLightSensor != null) { - sm.registerListener(getSensorListener(), - gLightSensor, - SensorManager.SENSOR_DELAY_NORMAL); - } - break; - - case GeckoHalDefines.SENSOR_LINEAR_ACCELERATION: - if (gLinearAccelerometerSensor == null) { - gLinearAccelerometerSensor = sm.getDefaultSensor( - Sensor.TYPE_LINEAR_ACCELERATION); - } - if (gLinearAccelerometerSensor != null) { - sm.registerListener(getSensorListener(), - gLinearAccelerometerSensor, - SensorManager.SENSOR_DELAY_FASTEST); - } - break; - - case GeckoHalDefines.SENSOR_GYROSCOPE: - if (gGyroscopeSensor == null) { - gGyroscopeSensor = sm.getDefaultSensor(Sensor.TYPE_GYROSCOPE); - } - if (gGyroscopeSensor != null) { - sm.registerListener(getSensorListener(), - gGyroscopeSensor, - SensorManager.SENSOR_DELAY_FASTEST); - } - break; - - default: - Log.w(LOGTAG, "Error! Can't enable unknown SENSOR type " + - aSensortype); - } - } - - @WrapForJNI(calledFrom = "gecko") - private static void disableSensor(int aSensortype) { - GeckoInterface gi = getGeckoInterface(); - if (gi == null) - return; - - SensorManager sm = (SensorManager) - getApplicationContext().getSystemService(Context.SENSOR_SERVICE); - - switch (aSensortype) { - case GeckoHalDefines.SENSOR_GAME_ROTATION_VECTOR: - if (gGameRotationVectorSensor != null) { - sm.unregisterListener(getSensorListener(), gGameRotationVectorSensor); - break; - } - // Fallthrough - - case GeckoHalDefines.SENSOR_ROTATION_VECTOR: - if (gRotationVectorSensor != null) { - sm.unregisterListener(getSensorListener(), gRotationVectorSensor); - break; - } - // Fallthrough - - case GeckoHalDefines.SENSOR_ORIENTATION: - if (gOrientationSensor != null) { - sm.unregisterListener(getSensorListener(), gOrientationSensor); - } - break; - - case GeckoHalDefines.SENSOR_ACCELERATION: - if (gAccelerometerSensor != null) { - sm.unregisterListener(getSensorListener(), gAccelerometerSensor); - } - break; - - case GeckoHalDefines.SENSOR_PROXIMITY: - if (gProximitySensor != null) { - sm.unregisterListener(getSensorListener(), gProximitySensor); - } - break; - - case GeckoHalDefines.SENSOR_LIGHT: - if (gLightSensor != null) { - sm.unregisterListener(getSensorListener(), gLightSensor); - } - break; - - case GeckoHalDefines.SENSOR_LINEAR_ACCELERATION: - if (gLinearAccelerometerSensor != null) { - sm.unregisterListener(getSensorListener(), gLinearAccelerometerSensor); - } - break; - - case GeckoHalDefines.SENSOR_GYROSCOPE: - if (gGyroscopeSensor != null) { - sm.unregisterListener(getSensorListener(), gGyroscopeSensor); - } - break; - default: - Log.w(LOGTAG, "Error! Can't disable unknown SENSOR type " + aSensortype); - } - } - - @WrapForJNI(calledFrom = "gecko") - private static void moveTaskToBack() { - if (getGeckoInterface() != null) - getGeckoInterface().getActivity().moveTaskToBack(true); - } - - @WrapForJNI(calledFrom = "gecko") - public static void scheduleRestart() { - getGeckoInterface().doRestart(); - } - - // Creates a homescreen shortcut for a web page. - // This is the entry point from nsIShellService. - @WrapForJNI(calledFrom = "gecko") - public static void createShortcut(final String aTitle, final String aURI) { - final GeckoInterface geckoInterface = getGeckoInterface(); - if (geckoInterface == null) { - return; - } - geckoInterface.createShortcut(aTitle, aURI); - } - - @JNITarget - static public int getPreferredIconSize() { - ActivityManager am = (ActivityManager) - getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE); - return am.getLauncherLargeIconSize(); - } - - @WrapForJNI(calledFrom = "gecko") - private static String[] getHandlersForMimeType(String aMimeType, String aAction) { - final GeckoInterface geckoInterface = getGeckoInterface(); - if (geckoInterface == null) { - return new String[] {}; - } - return geckoInterface.getHandlersForMimeType(aMimeType, aAction); - } - - @WrapForJNI(calledFrom = "gecko") - private static String[] getHandlersForURL(String aURL, String aAction) { - final GeckoInterface geckoInterface = getGeckoInterface(); - if (geckoInterface == null) { - return new String[] {}; - } - return geckoInterface.getHandlersForURL(aURL, aAction); - } - - @WrapForJNI(calledFrom = "gecko") - private static boolean getHWEncoderCapability() { - return HardwareCodecCapabilityUtils.getHWEncoderCapability(); - } - - @WrapForJNI(calledFrom = "gecko") - private static boolean getHWDecoderCapability() { - return HardwareCodecCapabilityUtils.getHWDecoderCapability(); - } - - static List<ResolveInfo> queryIntentActivities(Intent intent) { - final PackageManager pm = getApplicationContext().getPackageManager(); - - // Exclude any non-exported activities: we can't open them even if we want to! - // Bug 1031569 has some details. - final ArrayList<ResolveInfo> list = new ArrayList<>(); - for (ResolveInfo ri: pm.queryIntentActivities(intent, 0)) { - if (ri.activityInfo.exported) { - list.add(ri); - } - } - - return list; - } - - @WrapForJNI(calledFrom = "gecko") - public static String getExtensionFromMimeType(String aMimeType) { - return MimeTypeMap.getSingleton().getExtensionFromMimeType(aMimeType); - } - - @WrapForJNI(calledFrom = "gecko") - public static String getMimeTypeFromExtensions(String aFileExt) { - StringTokenizer st = new StringTokenizer(aFileExt, ".,; "); - String type = null; - String subType = null; - while (st.hasMoreElements()) { - String ext = st.nextToken(); - String mt = getMimeTypeFromExtension(ext); - if (mt == null) - continue; - int slash = mt.indexOf('/'); - String tmpType = mt.substring(0, slash); - if (!tmpType.equalsIgnoreCase(type)) - type = type == null ? tmpType : "*"; - String tmpSubType = mt.substring(slash + 1); - if (!tmpSubType.equalsIgnoreCase(subType)) - subType = subType == null ? tmpSubType : "*"; - } - if (type == null) - type = "*"; - if (subType == null) - subType = "*"; - return type + "/" + subType; - } - - static boolean isUriSafeForScheme(Uri aUri) { - // Bug 794034 - We don't want to pass MWI or USSD codes to the - // dialer, and ensure the Uri class doesn't parse a URI - // containing a fragment ('#') - final String scheme = aUri.getScheme(); - if ("tel".equals(scheme) || "sms".equals(scheme)) { - final String number = aUri.getSchemeSpecificPart(); - if (number.contains("#") || number.contains("*") || aUri.getFragment() != null) { - return false; - } - } - return true; - } - - @WrapForJNI(calledFrom = "gecko") - private static boolean openUriExternal(String targetURI, - String mimeType, - String packageName, - String className, - String action, - String title) { - final GeckoInterface geckoInterface = getGeckoInterface(); - if (geckoInterface == null) { - return false; - } - return geckoInterface.openUriExternal(targetURI, mimeType, packageName, className, action, title); - } - - @WrapForJNI(dispatchTo = "gecko") - private static native void notifyAlertListener(String name, String topic, String cookie); - - /** - * Called by the NotificationListener to notify Gecko that a notification has been - * shown. - */ - public static void onNotificationShow(final String name, final String cookie) { - if (GeckoThread.isRunning()) { - notifyAlertListener(name, "alertshow", cookie); - } - } - - /** - * Called by the NotificationListener to notify Gecko that a previously shown - * notification has been closed. - */ - public static void onNotificationClose(final String name, final String cookie) { - if (GeckoThread.isRunning()) { - notifyAlertListener(name, "alertfinished", cookie); - } - } - - /** - * Called by the NotificationListener to notify Gecko that a previously shown - * notification has been clicked on. - */ - public static void onNotificationClick(final String name, final String cookie) { - if (GeckoThread.isRunning()) { - notifyAlertListener(name, "alertclickcallback", cookie); - } - } - - @WrapForJNI(calledFrom = "gecko") - private static void showNotification(String name, String cookie, String title, - String text, String host, String imageUrl, - String persistentData) { - if (persistentData == null) { - getNotificationListener().showNotification(name, cookie, title, text, host, imageUrl); - return; - } - - getNotificationListener().showPersistentNotification( - name, cookie, title, text, host, imageUrl, persistentData); - } - - @WrapForJNI(calledFrom = "gecko") - private static void closeNotification(String name) { - getNotificationListener().closeNotification(name); - } - - @WrapForJNI(calledFrom = "gecko") - public static int getDpi() { - if (sDensityDpi == 0) { - sDensityDpi = getApplicationContext().getResources().getDisplayMetrics().densityDpi; - } - - return sDensityDpi; - } - - @WrapForJNI(calledFrom = "gecko") - private static float getDensity() { - return getApplicationContext().getResources().getDisplayMetrics().density; - } - - private static boolean isHighMemoryDevice() { - return HardwareUtils.getMemSize() > HIGH_MEMORY_DEVICE_THRESHOLD_MB; - } - - /** - * Returns the colour depth of the default screen. This will either be - * 24 or 16. - */ - @WrapForJNI(calledFrom = "gecko") - public static synchronized int getScreenDepth() { - if (sScreenDepth == 0) { - sScreenDepth = 16; - PixelFormat info = new PixelFormat(); - final WindowManager wm = (WindowManager) - getApplicationContext().getSystemService(Context.WINDOW_SERVICE); - PixelFormat.getPixelFormatInfo(wm.getDefaultDisplay().getPixelFormat(), info); - if (info.bitsPerPixel >= 24 && isHighMemoryDevice()) { - sScreenDepth = 24; - } - } - - return sScreenDepth; - } - - @WrapForJNI(calledFrom = "gecko") - private static synchronized void setScreenDepthOverride(int aScreenDepth) { - if (sScreenDepth != 0) { - Log.e(LOGTAG, "Tried to override screen depth after it's already been set"); - return; - } - - sScreenDepth = aScreenDepth; - } - - @WrapForJNI(calledFrom = "gecko") - private static void setFullScreen(boolean fullscreen) { - if (getGeckoInterface() != null) - getGeckoInterface().setFullScreen(fullscreen); - } - - @WrapForJNI(calledFrom = "gecko") - private static void performHapticFeedback(boolean aIsLongPress) { - // Don't perform haptic feedback if a vibration is currently playing, - // because the haptic feedback will nuke the vibration. - if (!sVibrationMaybePlaying || System.nanoTime() >= sVibrationEndTime) { - LayerView layerView = getLayerView(); - layerView.performHapticFeedback(aIsLongPress ? - HapticFeedbackConstants.LONG_PRESS : - HapticFeedbackConstants.VIRTUAL_KEY); - } - } - - private static Vibrator vibrator() { - return (Vibrator) getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE); - } - - // Helper method to convert integer array to long array. - private static long[] convertIntToLongArray(int[] input) { - long[] output = new long[input.length]; - for (int i = 0; i < input.length; i++) { - output[i] = input[i]; - } - return output; - } - - // Vibrate only if haptic feedback is enabled. - public static void vibrateOnHapticFeedbackEnabled(int[] milliseconds) { - if (Settings.System.getInt(getApplicationContext().getContentResolver(), - Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) > 0) { - vibrate(convertIntToLongArray(milliseconds), -1); - } - } - - @WrapForJNI(calledFrom = "gecko") - private static void vibrate(long milliseconds) { - sVibrationEndTime = System.nanoTime() + milliseconds * 1000000; - sVibrationMaybePlaying = true; - vibrator().vibrate(milliseconds); - } - - @WrapForJNI(calledFrom = "gecko") - private static void vibrate(long[] pattern, int repeat) { - // If pattern.length is even, the last element in the pattern is a - // meaningless delay, so don't include it in vibrationDuration. - long vibrationDuration = 0; - int iterLen = pattern.length - (pattern.length % 2 == 0 ? 1 : 0); - for (int i = 0; i < iterLen; i++) { - vibrationDuration += pattern[i]; - } - - sVibrationEndTime = System.nanoTime() + vibrationDuration * 1000000; - sVibrationMaybePlaying = true; - vibrator().vibrate(pattern, repeat); - } - - @WrapForJNI(calledFrom = "gecko") - private static void cancelVibrate() { - sVibrationMaybePlaying = false; - sVibrationEndTime = 0; - vibrator().cancel(); - } - - @WrapForJNI(calledFrom = "gecko") - private static void setKeepScreenOn(final boolean on) { - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - // TODO - } - }); - } - - @WrapForJNI(calledFrom = "gecko") - private static boolean isNetworkLinkUp() { - ConnectivityManager cm = (ConnectivityManager) - getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE); - try { - NetworkInfo info = cm.getActiveNetworkInfo(); - if (info == null || !info.isConnected()) - return false; - } catch (SecurityException se) { - return false; - } - return true; - } - - @WrapForJNI(calledFrom = "gecko") - private static boolean isNetworkLinkKnown() { - ConnectivityManager cm = (ConnectivityManager) - getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE); - try { - if (cm.getActiveNetworkInfo() == null) - return false; - } catch (SecurityException se) { - return false; - } - return true; - } - - @WrapForJNI(calledFrom = "gecko") - private static int getNetworkLinkType() { - ConnectivityManager cm = (ConnectivityManager) - getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo info = cm.getActiveNetworkInfo(); - if (info == null) { - return LINK_TYPE_UNKNOWN; - } - - switch (info.getType()) { - case ConnectivityManager.TYPE_ETHERNET: - return LINK_TYPE_ETHERNET; - case ConnectivityManager.TYPE_WIFI: - return LINK_TYPE_WIFI; - case ConnectivityManager.TYPE_WIMAX: - return LINK_TYPE_WIMAX; - case ConnectivityManager.TYPE_MOBILE: - break; // We will handle sub-types after the switch. - default: - Log.w(LOGTAG, "Ignoring the current network type."); - return LINK_TYPE_UNKNOWN; - } - - TelephonyManager tm = (TelephonyManager) - getApplicationContext().getSystemService(Context.TELEPHONY_SERVICE); - if (tm == null) { - Log.e(LOGTAG, "Telephony service does not exist"); - return LINK_TYPE_UNKNOWN; - } - - switch (tm.getNetworkType()) { - case TelephonyManager.NETWORK_TYPE_IDEN: - case TelephonyManager.NETWORK_TYPE_CDMA: - case TelephonyManager.NETWORK_TYPE_GPRS: - return LINK_TYPE_2G; - case TelephonyManager.NETWORK_TYPE_1xRTT: - case TelephonyManager.NETWORK_TYPE_EDGE: - return LINK_TYPE_2G; // 2.5G - case TelephonyManager.NETWORK_TYPE_UMTS: - case TelephonyManager.NETWORK_TYPE_EVDO_0: - return LINK_TYPE_3G; - case TelephonyManager.NETWORK_TYPE_HSPA: - case TelephonyManager.NETWORK_TYPE_HSDPA: - case TelephonyManager.NETWORK_TYPE_HSUPA: - case TelephonyManager.NETWORK_TYPE_EVDO_A: - case TelephonyManager.NETWORK_TYPE_EVDO_B: - case TelephonyManager.NETWORK_TYPE_EHRPD: - return LINK_TYPE_3G; // 3.5G - case TelephonyManager.NETWORK_TYPE_HSPAP: - return LINK_TYPE_3G; // 3.75G - case TelephonyManager.NETWORK_TYPE_LTE: - return LINK_TYPE_4G; // 3.9G - case TelephonyManager.NETWORK_TYPE_UNKNOWN: - default: - Log.w(LOGTAG, "Connected to an unknown mobile network!"); - return LINK_TYPE_UNKNOWN; - } - } - - @WrapForJNI(calledFrom = "gecko") - private static int[] getSystemColors() { - // attrsAppearance[] must correspond to AndroidSystemColors structure in android/AndroidBridge.h - final int[] attrsAppearance = { - android.R.attr.textColor, - android.R.attr.textColorPrimary, - android.R.attr.textColorPrimaryInverse, - android.R.attr.textColorSecondary, - android.R.attr.textColorSecondaryInverse, - android.R.attr.textColorTertiary, - android.R.attr.textColorTertiaryInverse, - android.R.attr.textColorHighlight, - android.R.attr.colorForeground, - android.R.attr.colorBackground, - android.R.attr.panelColorForeground, - android.R.attr.panelColorBackground - }; - - int[] result = new int[attrsAppearance.length]; - - final ContextThemeWrapper contextThemeWrapper = - new ContextThemeWrapper(getApplicationContext(), android.R.style.TextAppearance); - - final TypedArray appearance = contextThemeWrapper.getTheme().obtainStyledAttributes(attrsAppearance); - - if (appearance != null) { - for (int i = 0; i < appearance.getIndexCount(); i++) { - int idx = appearance.getIndex(i); - int color = appearance.getColor(idx, 0); - result[idx] = color; - } - appearance.recycle(); - } - - return result; - } - - @WrapForJNI(calledFrom = "gecko") - public static void killAnyZombies() { - GeckoProcessesVisitor visitor = new GeckoProcessesVisitor() { - @Override - public boolean callback(int pid) { - if (pid != android.os.Process.myPid()) - android.os.Process.killProcess(pid); - return true; - } - }; - - EnumerateGeckoProcesses(visitor); - } - - interface GeckoProcessesVisitor { - boolean callback(int pid); - } - - private static void EnumerateGeckoProcesses(GeckoProcessesVisitor visiter) { - int pidColumn = -1; - int userColumn = -1; - - try { - // run ps and parse its output - java.lang.Process ps = Runtime.getRuntime().exec("ps"); - BufferedReader in = new BufferedReader(new InputStreamReader(ps.getInputStream()), - 2048); - - String headerOutput = in.readLine(); - - // figure out the column offsets. We only care about the pid and user fields - StringTokenizer st = new StringTokenizer(headerOutput); - - int tokenSoFar = 0; - while (st.hasMoreTokens()) { - String next = st.nextToken(); - if (next.equalsIgnoreCase("PID")) - pidColumn = tokenSoFar; - else if (next.equalsIgnoreCase("USER")) - userColumn = tokenSoFar; - tokenSoFar++; - } - - // alright, the rest are process entries. - String psOutput = null; - while ((psOutput = in.readLine()) != null) { - String[] split = psOutput.split("\\s+"); - if (split.length <= pidColumn || split.length <= userColumn) - continue; - int uid = android.os.Process.getUidForName(split[userColumn]); - if (uid == android.os.Process.myUid() && - !split[split.length - 1].equalsIgnoreCase("ps")) { - int pid = Integer.parseInt(split[pidColumn]); - boolean keepGoing = visiter.callback(pid); - if (keepGoing == false) - break; - } - } - in.close(); - } - catch (Exception e) { - Log.w(LOGTAG, "Failed to enumerate Gecko processes.", e); - } - } - - public static String getAppNameByPID(int pid) { - BufferedReader cmdlineReader = null; - String path = "/proc/" + pid + "/cmdline"; - try { - File cmdlineFile = new File(path); - if (!cmdlineFile.exists()) - return ""; - cmdlineReader = new BufferedReader(new FileReader(cmdlineFile)); - return cmdlineReader.readLine().trim(); - } catch (Exception ex) { - return ""; - } finally { - if (null != cmdlineReader) { - try { - cmdlineReader.close(); - } catch (Exception e) { } - } - } - } - - public static void listOfOpenFiles() { - int pidColumn = -1; - int nameColumn = -1; - - try { - String filter = GeckoProfile.get(getApplicationContext()).getDir().toString(); - Log.d(LOGTAG, "[OPENFILE] Filter: " + filter); - - // run lsof and parse its output - java.lang.Process lsof = Runtime.getRuntime().exec("lsof"); - BufferedReader in = new BufferedReader(new InputStreamReader(lsof.getInputStream()), 2048); - - String headerOutput = in.readLine(); - StringTokenizer st = new StringTokenizer(headerOutput); - int token = 0; - while (st.hasMoreTokens()) { - String next = st.nextToken(); - if (next.equalsIgnoreCase("PID")) - pidColumn = token; - else if (next.equalsIgnoreCase("NAME")) - nameColumn = token; - token++; - } - - // alright, the rest are open file entries. - Map<Integer, String> pidNameMap = new TreeMap<Integer, String>(); - String output = null; - while ((output = in.readLine()) != null) { - String[] split = output.split("\\s+"); - if (split.length <= pidColumn || split.length <= nameColumn) - continue; - final Integer pid = Integer.valueOf(split[pidColumn]); - String name = pidNameMap.get(pid); - if (name == null) { - name = getAppNameByPID(pid.intValue()); - pidNameMap.put(pid, name); - } - String file = split[nameColumn]; - if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(file) && file.startsWith(filter)) - Log.d(LOGTAG, "[OPENFILE] " + name + "(" + split[pidColumn] + ") : " + file); - } - in.close(); - } catch (Exception e) { } - } - - @WrapForJNI(calledFrom = "gecko") - private static byte[] getIconForExtension(String aExt, int iconSize) { - try { - if (iconSize <= 0) - iconSize = 16; - - if (aExt != null && aExt.length() > 1 && aExt.charAt(0) == '.') - aExt = aExt.substring(1); - - PackageManager pm = getApplicationContext().getPackageManager(); - Drawable icon = getDrawableForExtension(pm, aExt); - if (icon == null) { - // Use a generic icon - icon = pm.getDefaultActivityIcon(); - } - - Bitmap bitmap = ((BitmapDrawable)icon).getBitmap(); - if (bitmap.getWidth() != iconSize || bitmap.getHeight() != iconSize) - bitmap = Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, true); - - ByteBuffer buf = ByteBuffer.allocate(iconSize * iconSize * 4); - bitmap.copyPixelsToBuffer(buf); - - return buf.array(); - } - catch (Exception e) { - Log.w(LOGTAG, "getIconForExtension failed.", e); - return null; - } - } - - public static String getMimeTypeFromExtension(String ext) { - final MimeTypeMap mtm = MimeTypeMap.getSingleton(); - return mtm.getMimeTypeFromExtension(ext); - } - - private static Drawable getDrawableForExtension(PackageManager pm, String aExt) { - Intent intent = new Intent(Intent.ACTION_VIEW); - final String mimeType = getMimeTypeFromExtension(aExt); - if (mimeType != null && mimeType.length() > 0) - intent.setType(mimeType); - else - return null; - - List<ResolveInfo> list = pm.queryIntentActivities(intent, 0); - if (list.size() == 0) - return null; - - ResolveInfo resolveInfo = list.get(0); - - if (resolveInfo == null) - return null; - - ActivityInfo activityInfo = resolveInfo.activityInfo; - - return activityInfo.loadIcon(pm); - } - - @WrapForJNI(calledFrom = "gecko") - private static boolean getShowPasswordSetting() { - try { - int showPassword = - Settings.System.getInt(getApplicationContext().getContentResolver(), - Settings.System.TEXT_SHOW_PASSWORD, 1); - return (showPassword > 0); - } - catch (Exception e) { - return true; - } - } - - @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko") - public static native void onFullScreenPluginHidden(View view); - - @WrapForJNI(calledFrom = "gecko") - private static void addFullScreenPluginView(View view) { - if (getGeckoInterface() != null) - getGeckoInterface().addPluginView(view); - } - - @WrapForJNI(calledFrom = "gecko") - private static void removeFullScreenPluginView(View view) { - if (getGeckoInterface() != null) - getGeckoInterface().removePluginView(view); - } - - /** - * A plugin that wish to be loaded in the WebView must provide this permission - * in their AndroidManifest.xml. - */ - public static final String PLUGIN_ACTION = "android.webkit.PLUGIN"; - public static final String PLUGIN_PERMISSION = "android.webkit.permission.PLUGIN"; - - private static final String PLUGIN_SYSTEM_LIB = "/system/lib/plugins/"; - - private static final String PLUGIN_TYPE = "type"; - private static final String TYPE_NATIVE = "native"; - public static final ArrayList<PackageInfo> mPackageInfoCache = new ArrayList<>(); - - // Returns null if plugins are blocked on the device. - static String[] getPluginDirectories() { - - // Block on Pixel C. - if ((new File("/system/lib/hw/power.dragon.so")).exists()) { - Log.w(LOGTAG, "Blocking plugins because of Pixel C device (bug 1255122)"); - return null; - } - // An awful hack to detect Tegra devices. Easiest way to do it without spinning up a EGL context. - boolean isTegra = (new File("/system/lib/hw/gralloc.tegra.so")).exists() || - (new File("/system/lib/hw/gralloc.tegra3.so")).exists() || - (new File("/sys/class/nvidia-gpu")).exists(); - if (isTegra) { - // disable on KitKat (bug 957694) - if (Versions.feature19Plus) { - Log.w(LOGTAG, "Blocking plugins because of Tegra (bug 957694)"); - return null; - } - - // disable Flash on Tegra ICS with CM9 and other custom firmware (bug 736421) - final File vfile = new File("/proc/version"); - try { - if (vfile.canRead()) { - final BufferedReader reader = new BufferedReader(new FileReader(vfile)); - try { - final String version = reader.readLine(); - if (version.indexOf("CM9") != -1 || - version.indexOf("cyanogen") != -1 || - version.indexOf("Nova") != -1) { - Log.w(LOGTAG, "Blocking plugins because of Tegra 2 + unofficial ICS bug (bug 736421)"); - return null; - } - } finally { - reader.close(); - } - } - } catch (IOException ex) { - // Do nothing. - } - } - - ArrayList<String> directories = new ArrayList<String>(); - PackageManager pm = getApplicationContext().getPackageManager(); - List<ResolveInfo> plugins = pm.queryIntentServices(new Intent(PLUGIN_ACTION), - PackageManager.GET_META_DATA); - - synchronized (mPackageInfoCache) { - - // clear the list of existing packageInfo objects - mPackageInfoCache.clear(); - - - for (ResolveInfo info : plugins) { - - // retrieve the plugin's service information - ServiceInfo serviceInfo = info.serviceInfo; - if (serviceInfo == null) { - Log.w(LOGTAG, "Ignoring bad plugin."); - continue; - } - - // Blacklist HTC's flash lite. - // See bug #704516 - We're not quite sure what Flash Lite does, - // but loading it causes Flash to give errors and fail to draw. - if (serviceInfo.packageName.equals("com.htc.flashliteplugin")) { - Log.w(LOGTAG, "Skipping HTC's flash lite plugin"); - continue; - } - - - // Retrieve information from the plugin's manifest. - PackageInfo pkgInfo; - try { - pkgInfo = pm.getPackageInfo(serviceInfo.packageName, - PackageManager.GET_PERMISSIONS - | PackageManager.GET_SIGNATURES); - } catch (Exception e) { - Log.w(LOGTAG, "Can't find plugin: " + serviceInfo.packageName); - continue; - } - - if (pkgInfo == null) { - Log.w(LOGTAG, "Not loading plugin: " + serviceInfo.packageName + ". Could not load package information."); - continue; - } - - /* - * find the location of the plugin's shared library. The default - * is to assume the app is either a user installed app or an - * updated system app. In both of these cases the library is - * stored in the app's data directory. - */ - String directory = pkgInfo.applicationInfo.dataDir + "/lib"; - final int appFlags = pkgInfo.applicationInfo.flags; - final int updatedSystemFlags = ApplicationInfo.FLAG_SYSTEM | - ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; - - // preloaded system app with no user updates - if ((appFlags & updatedSystemFlags) == ApplicationInfo.FLAG_SYSTEM) { - directory = PLUGIN_SYSTEM_LIB + pkgInfo.packageName; - } - - // check if the plugin has the required permissions - String permissions[] = pkgInfo.requestedPermissions; - if (permissions == null) { - Log.w(LOGTAG, "Not loading plugin: " + serviceInfo.packageName + ". Does not have required permission."); - continue; - } - boolean permissionOk = false; - for (String permit : permissions) { - if (PLUGIN_PERMISSION.equals(permit)) { - permissionOk = true; - break; - } - } - if (!permissionOk) { - Log.w(LOGTAG, "Not loading plugin: " + serviceInfo.packageName + ". Does not have required permission (2)."); - continue; - } - - // check to ensure the plugin is properly signed - Signature signatures[] = pkgInfo.signatures; - if (signatures == null) { - Log.w(LOGTAG, "Not loading plugin: " + serviceInfo.packageName + ". Not signed."); - continue; - } - - // determine the type of plugin from the manifest - if (serviceInfo.metaData == null) { - Log.e(LOGTAG, "The plugin '" + serviceInfo.name + "' has no defined type."); - continue; - } - - String pluginType = serviceInfo.metaData.getString(PLUGIN_TYPE); - if (!TYPE_NATIVE.equals(pluginType)) { - Log.e(LOGTAG, "Unrecognized plugin type: " + pluginType); - continue; - } - - try { - Class<?> cls = getPluginClass(serviceInfo.packageName, serviceInfo.name); - - //TODO implement any requirements of the plugin class here! - boolean classFound = true; - - if (!classFound) { - Log.e(LOGTAG, "The plugin's class' " + serviceInfo.name + "' does not extend the appropriate class."); - continue; - } - - } catch (NameNotFoundException e) { - Log.e(LOGTAG, "Can't find plugin: " + serviceInfo.packageName); - continue; - } catch (ClassNotFoundException e) { - Log.e(LOGTAG, "Can't find plugin's class: " + serviceInfo.name); - continue; - } - - // if all checks have passed then make the plugin available - mPackageInfoCache.add(pkgInfo); - directories.add(directory); - } - } - - return directories.toArray(new String[directories.size()]); - } - - static String getPluginPackage(String pluginLib) { - - if (pluginLib == null || pluginLib.length() == 0) { - return null; - } - - synchronized (mPackageInfoCache) { - for (PackageInfo pkgInfo : mPackageInfoCache) { - if (pluginLib.contains(pkgInfo.packageName)) { - return pkgInfo.packageName; - } - } - } - - return null; - } - - static Class<?> getPluginClass(String packageName, String className) - throws NameNotFoundException, ClassNotFoundException { - Context pluginContext = getApplicationContext().createPackageContext(packageName, - Context.CONTEXT_INCLUDE_CODE | - Context.CONTEXT_IGNORE_SECURITY); - ClassLoader pluginCL = pluginContext.getClassLoader(); - return pluginCL.loadClass(className); - } - - @WrapForJNI - private static Class<?> loadPluginClass(String className, String libName) { - if (getGeckoInterface() == null) - return null; - try { - final String packageName = getPluginPackage(libName); - final int contextFlags = Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY; - final Context pluginContext = getApplicationContext().createPackageContext( - packageName, contextFlags); - return pluginContext.getClassLoader().loadClass(className); - } catch (java.lang.ClassNotFoundException cnfe) { - Log.w(LOGTAG, "Couldn't find plugin class " + className, cnfe); - return null; - } catch (android.content.pm.PackageManager.NameNotFoundException nnfe) { - Log.w(LOGTAG, "Couldn't find package.", nnfe); - return null; - } - } - - private static Context sApplicationContext; - private static ContextGetter sContextGetter; - - @Deprecated - @WrapForJNI - public static Context getContext() { - return sContextGetter.getContext(); - } - - public static void setContextGetter(ContextGetter cg) { - sContextGetter = cg; - } - - @WrapForJNI - public static Context getApplicationContext() { - return sApplicationContext; - } - - public static void setApplicationContext(final Context context) { - sApplicationContext = context; - } - - public static SharedPreferences getSharedPreferences() { - if (sContextGetter == null) { - throw new IllegalStateException("No ContextGetter; cannot fetch prefs."); - } - return sContextGetter.getSharedPreferences(); - } - - public interface AppStateListener { - public void onPause(); - public void onResume(); - public void onOrientationChanged(); - } - - public interface GeckoInterface { - public EventDispatcher getAppEventDispatcher(); - public GeckoProfile getProfile(); - public Activity getActivity(); - public String getDefaultUAString(); - public void doRestart(); - public void setFullScreen(boolean fullscreen); - public void addPluginView(View view); - public void removePluginView(final View view); - public void enableOrientationListener(); - public void disableOrientationListener(); - public void addAppStateListener(AppStateListener listener); - public void removeAppStateListener(AppStateListener listener); - public void notifyWakeLockChanged(String topic, String state); - public boolean areTabsShown(); - public AbsoluteLayout getPluginContainer(); - public void notifyCheckUpdateResult(String result); - public void invalidateOptionsMenu(); - - /** - * Create a shortcut -- generally a home-screen icon -- linking the given title to the given URI. - * <p> - * This method is always invoked on the Gecko thread. - * - * @param title of URI to link to. - * @param URI to link to. - */ - public void createShortcut(String title, String URI); - - /** - * Check if the given URI is visited. - * <p/> - * If it has been visited, call {@link GeckoAppShell#notifyUriVisited(String)}. (If it - * has not been visited, do nothing.) - * <p/> - * This method is always invoked on the Gecko thread. - * - * @param uri to check. - */ - public void checkUriVisited(String uri); - - /** - * Mark the given URI as visited in Gecko. - * <p/> - * Implementors may maintain some local store of visited URIs in order to be able to - * answer {@link #checkUriVisited(String)} requests affirmatively. - * <p/> - * This method is always invoked on the Gecko thread. - * - * @param uri to mark. - */ - public void markUriVisited(final String uri); - - /** - * Set the title of the given URI, as determined by Gecko. - * <p/> - * This method is always invoked on the Gecko thread. - * - * @param uri given. - * @param title to associate with the given URI. - */ - public void setUriTitle(final String uri, final String title); - - public void setAccessibilityEnabled(boolean enabled); - - public boolean openUriExternal(String targetURI, String mimeType, String packageName, String className, String action, String title); - - public String[] getHandlersForMimeType(String mimeType, String action); - public String[] getHandlersForURL(String url, String action); - - /** - * URI of the underlying chrome window to be opened, or null to use the default GeckoView - * XUL container <tt>chrome://browser/content/geckoview.xul</tt>. See - * <a href="https://developer.mozilla.org/en/docs/toolkit.defaultChromeURI">https://developer.mozilla.org/en/docs/toolkit.defaultChromeURI</a> - * - * @return URI or null. - */ - String getDefaultChromeURI(); - }; - - private static GeckoInterface sGeckoInterface; - - public static GeckoInterface getGeckoInterface() { - return sGeckoInterface; - } - - public static void setGeckoInterface(GeckoInterface aGeckoInterface) { - sGeckoInterface = aGeckoInterface; - } - - /* package */ static Camera sCamera; - - private static final int kPreferredFPS = 25; - private static byte[] sCameraBuffer; - - private static class CameraCallback implements Camera.PreviewCallback { - @WrapForJNI(calledFrom = "gecko") - private static native void onFrameData(int camera, byte[] data); - - private final int mCamera; - - public CameraCallback(int camera) { - mCamera = camera; - } - - @Override - public void onPreviewFrame(byte[] data, Camera camera) { - onFrameData(mCamera, data); - - if (sCamera != null) { - sCamera.addCallbackBuffer(sCameraBuffer); - } - } - } - - @WrapForJNI(calledFrom = "gecko") - private static int[] initCamera(String aContentType, int aCamera, int aWidth, int aHeight) { - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - try { - if (getGeckoInterface() != null) - getGeckoInterface().enableOrientationListener(); - } catch (Exception e) { } - } - }); - - // [0] = 0|1 (failure/success) - // [1] = width - // [2] = height - // [3] = fps - int[] result = new int[4]; - result[0] = 0; - - if (Camera.getNumberOfCameras() == 0) { - return result; - } - - try { - sCamera = Camera.open(aCamera); - - Camera.Parameters params = sCamera.getParameters(); - params.setPreviewFormat(ImageFormat.NV21); - - // use the preview fps closest to 25 fps. - int fpsDelta = 1000; - try { - Iterator<Integer> it = params.getSupportedPreviewFrameRates().iterator(); - while (it.hasNext()) { - int nFps = it.next(); - if (Math.abs(nFps - kPreferredFPS) < fpsDelta) { - fpsDelta = Math.abs(nFps - kPreferredFPS); - params.setPreviewFrameRate(nFps); - } - } - } catch (Exception e) { - params.setPreviewFrameRate(kPreferredFPS); - } - - // set up the closest preview size available - Iterator<Camera.Size> sit = params.getSupportedPreviewSizes().iterator(); - int sizeDelta = 10000000; - int bufferSize = 0; - while (sit.hasNext()) { - Camera.Size size = sit.next(); - if (Math.abs(size.width * size.height - aWidth * aHeight) < sizeDelta) { - sizeDelta = Math.abs(size.width * size.height - aWidth * aHeight); - params.setPreviewSize(size.width, size.height); - bufferSize = size.width * size.height; - } - } - - sCamera.setParameters(params); - sCameraBuffer = new byte[(bufferSize * 12) / 8]; - sCamera.addCallbackBuffer(sCameraBuffer); - sCamera.setPreviewCallbackWithBuffer(new CameraCallback(aCamera)); - sCamera.startPreview(); - params = sCamera.getParameters(); - result[0] = 1; - result[1] = params.getPreviewSize().width; - result[2] = params.getPreviewSize().height; - result[3] = params.getPreviewFrameRate(); - } catch (RuntimeException e) { - Log.w(LOGTAG, "initCamera RuntimeException.", e); - result[0] = result[1] = result[2] = result[3] = 0; - } - return result; - } - - @WrapForJNI(calledFrom = "gecko") - private static synchronized void closeCamera() { - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - try { - if (getGeckoInterface() != null) - getGeckoInterface().disableOrientationListener(); - } catch (Exception e) { } - } - }); - if (sCamera != null) { - sCamera.stopPreview(); - sCamera.release(); - sCamera = null; - sCameraBuffer = null; - } - } - - /* - * Battery API related methods. - */ - @WrapForJNI(calledFrom = "gecko") - private static void enableBatteryNotifications() { - GeckoBatteryManager.enableNotifications(); - } - - @WrapForJNI(calledFrom = "gecko") - private static void handleGeckoMessage(final NativeJSContainer message) { - boolean success = EventDispatcher.getInstance().dispatchEvent(message); - if (getGeckoInterface() != null && getGeckoInterface().getAppEventDispatcher() != null) { - success |= getGeckoInterface().getAppEventDispatcher().dispatchEvent(message); - } - - if (!success) { - final String type = message.optString("type", null); - final String guid = message.optString(EventDispatcher.GUID, null); - if (type != null && guid != null) { - (new EventDispatcher.GeckoEventCallback(guid, type)).sendError("No listeners for request"); - } - } - message.disposeNative(); - } - - @WrapForJNI(calledFrom = "gecko") - private static void disableBatteryNotifications() { - GeckoBatteryManager.disableNotifications(); - } - - @WrapForJNI(calledFrom = "gecko") - private static double[] getCurrentBatteryInformation() { - return GeckoBatteryManager.getCurrentInformation(); - } - - @WrapForJNI(stubName = "CheckURIVisited", calledFrom = "gecko") - private static void checkUriVisited(String uri) { - final GeckoInterface geckoInterface = getGeckoInterface(); - if (geckoInterface == null) { - return; - } - geckoInterface.checkUriVisited(uri); - } - - @WrapForJNI(stubName = "MarkURIVisited", calledFrom = "gecko") - private static void markUriVisited(final String uri) { - final GeckoInterface geckoInterface = getGeckoInterface(); - if (geckoInterface == null) { - return; - } - geckoInterface.markUriVisited(uri); - } - - @WrapForJNI(stubName = "SetURITitle", calledFrom = "gecko") - private static void setUriTitle(final String uri, final String title) { - final GeckoInterface geckoInterface = getGeckoInterface(); - if (geckoInterface == null) { - return; - } - geckoInterface.setUriTitle(uri, title); - } - - @WrapForJNI(calledFrom = "gecko") - private static void hideProgressDialog() { - // unused stub - } - - /* Called by JNI from AndroidBridge, and by reflection from tests/BaseTest.java.in */ - @WrapForJNI(calledFrom = "gecko") - @RobocopTarget - public static boolean isTablet() { - return HardwareUtils.isTablet(); - } - - private static boolean sImeWasEnabledOnLastResize = false; - public static void viewSizeChanged() { - GeckoView v = (GeckoView) getLayerView(); - if (v == null) { - return; - } - boolean imeIsEnabled = v.isIMEEnabled(); - if (imeIsEnabled && !sImeWasEnabledOnLastResize) { - // The IME just came up after not being up, so let's scroll - // to the focused input. - notifyObservers("ScrollTo:FocusedInput", ""); - } - sImeWasEnabledOnLastResize = imeIsEnabled; - } - - @WrapForJNI(calledFrom = "gecko") - private static double[] getCurrentNetworkInformation() { - return GeckoNetworkManager.getInstance().getCurrentInformation(); - } - - @WrapForJNI(calledFrom = "gecko") - private static void enableNetworkNotifications() { - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - GeckoNetworkManager.getInstance().enableNotifications(); - } - }); - } - - @WrapForJNI(calledFrom = "gecko") - private static void disableNetworkNotifications() { - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - GeckoNetworkManager.getInstance().disableNotifications(); - } - }); - } - - @WrapForJNI(calledFrom = "gecko") - private static short getScreenOrientation() { - return GeckoScreenOrientation.getInstance().getScreenOrientation().value; - } - - @WrapForJNI(calledFrom = "gecko") - private static int getScreenAngle() { - return GeckoScreenOrientation.getInstance().getAngle(); - } - - @WrapForJNI(calledFrom = "gecko") - private static void enableScreenOrientationNotifications() { - GeckoScreenOrientation.getInstance().enableNotifications(); - } - - @WrapForJNI(calledFrom = "gecko") - private static void disableScreenOrientationNotifications() { - GeckoScreenOrientation.getInstance().disableNotifications(); - } - - @WrapForJNI(calledFrom = "gecko") - private static void lockScreenOrientation(int aOrientation) { - GeckoScreenOrientation.getInstance().lock(aOrientation); - } - - @WrapForJNI(calledFrom = "gecko") - private static void unlockScreenOrientation() { - GeckoScreenOrientation.getInstance().unlock(); - } - - @WrapForJNI(calledFrom = "gecko") - private static void notifyWakeLockChanged(String topic, String state) { - if (getGeckoInterface() != null) - getGeckoInterface().notifyWakeLockChanged(topic, state); - } - - @WrapForJNI(calledFrom = "gecko") - private static boolean unlockProfile() { - // Try to kill any zombie Fennec's that might be running - GeckoAppShell.killAnyZombies(); - - // Then force unlock this profile - if (getGeckoInterface() != null) { - GeckoProfile profile = getGeckoInterface().getProfile(); - File lock = profile.getFile(".parentlock"); - return lock.exists() && lock.delete(); - } - return false; - } - - @WrapForJNI(calledFrom = "gecko") - private static String getProxyForURI(String spec, String scheme, String host, int port) { - final ProxySelector ps = new ProxySelector(); - - Proxy proxy = ps.select(scheme, host); - if (Proxy.NO_PROXY.equals(proxy)) { - return "DIRECT"; - } - - switch (proxy.type()) { - case HTTP: - return "PROXY " + proxy.address().toString(); - case SOCKS: - return "SOCKS " + proxy.address().toString(); - } - - return "DIRECT"; - } - - @WrapForJNI - private static InputStream createInputStream(URLConnection connection) throws IOException { - return connection.getInputStream(); - } - - private static class BitmapConnection extends URLConnection { - private Bitmap bitmap; - - BitmapConnection(Bitmap b) throws MalformedURLException, IOException { - super(null); - bitmap = b; - } - - @Override - public void connect() {} - - @Override - public InputStream getInputStream() throws IOException { - return new BitmapInputStream(); - } - - @Override - public String getContentType() { - return "image/png"; - } - - private final class BitmapInputStream extends PipedInputStream { - private boolean mHaveConnected = false; - - @Override - public synchronized int read(byte[] buffer, int byteOffset, int byteCount) - throws IOException { - if (mHaveConnected) { - return super.read(buffer, byteOffset, byteCount); - } - - final PipedOutputStream output = new PipedOutputStream(); - connect(output); - ThreadUtils.postToBackgroundThread( - new Runnable() { - @Override - public void run() { - try { - bitmap.compress(Bitmap.CompressFormat.PNG, 100, output); - output.close(); - } catch (IOException ioe) { } - } - }); - mHaveConnected = true; - return super.read(buffer, byteOffset, byteCount); - } - } - } - - @WrapForJNI - private static URLConnection getConnection(String url) { - try { - String spec; - if (url.startsWith("android://")) { - spec = url.substring(10); - } else { - spec = url.substring(8); - } - - // Check if we are loading a package icon. - try { - if (spec.startsWith("icon/")) { - String[] splits = spec.split("/"); - if (splits.length != 2) { - return null; - } - final String pkg = splits[1]; - final PackageManager pm = getApplicationContext().getPackageManager(); - final Drawable d = pm.getApplicationIcon(pkg); - final Bitmap bitmap = BitmapUtils.getBitmapFromDrawable(d); - return new BitmapConnection(bitmap); - } - } catch (Exception ex) { - Log.e(LOGTAG, "error", ex); - } - - // if the colon got stripped, put it back - int colon = spec.indexOf(':'); - if (colon == -1 || colon > spec.indexOf('/')) { - spec = spec.replaceFirst("/", ":/"); - } - } catch (Exception ex) { - return null; - } - return null; - } - - @WrapForJNI - private static String connectionGetMimeType(URLConnection connection) { - return connection.getContentType(); - } - - @WrapForJNI(calledFrom = "gecko") - private static int getMaxTouchPoints() { - PackageManager pm = getApplicationContext().getPackageManager(); - if (pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_JAZZHAND)) { - // at least, 5+ fingers. - return 5; - } else if (pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)) { - // at least, 2+ fingers. - return 2; - } else if (pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)) { - // 2 fingers - return 2; - } else if (pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)) { - // 1 finger - return 1; - } - return 0; - } - - public static synchronized void resetScreenSize() { - sScreenSize = null; - } - - @WrapForJNI(calledFrom = "gecko") - private static synchronized Rect getScreenSize() { - if (sScreenSize == null) { - final WindowManager wm = (WindowManager) - getApplicationContext().getSystemService(Context.WINDOW_SERVICE); - final Display disp = wm.getDefaultDisplay(); - sScreenSize = new Rect(0, 0, disp.getWidth(), disp.getHeight()); - } - return sScreenSize; - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoBatteryManager.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoBatteryManager.java deleted file mode 100644 index 1a41c390a..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoBatteryManager.java +++ /dev/null @@ -1,202 +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; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.BatteryManager; -import android.os.Build; -import android.os.SystemClock; -import android.util.Log; - -import org.mozilla.gecko.annotation.WrapForJNI; - -public class GeckoBatteryManager extends BroadcastReceiver { - private static final String LOGTAG = "GeckoBatteryManager"; - - // Those constants should be keep in sync with the ones in: - // dom/battery/Constants.h - private final static double kDefaultLevel = 1.0; - private final static boolean kDefaultCharging = true; - private final static double kDefaultRemainingTime = 0.0; - private final static double kUnknownRemainingTime = -1.0; - - private static long sLastLevelChange; - private static boolean sNotificationsEnabled; - private static double sLevel = kDefaultLevel; - private static boolean sCharging = kDefaultCharging; - private static double sRemainingTime = kDefaultRemainingTime; - - private static final GeckoBatteryManager sInstance = new GeckoBatteryManager(); - - private final IntentFilter mFilter; - private Context mApplicationContext; - private boolean mIsEnabled; - - public static GeckoBatteryManager getInstance() { - return sInstance; - } - - private GeckoBatteryManager() { - mFilter = new IntentFilter(); - mFilter.addAction(Intent.ACTION_BATTERY_CHANGED); - } - - public synchronized void start(final Context context) { - if (mIsEnabled) { - Log.w(LOGTAG, "Already started!"); - return; - } - - mApplicationContext = context.getApplicationContext(); - // registerReceiver will return null if registering fails. - if (mApplicationContext.registerReceiver(this, mFilter) == null) { - Log.e(LOGTAG, "Registering receiver failed"); - } else { - mIsEnabled = true; - } - } - - public synchronized void stop() { - if (!mIsEnabled) { - Log.w(LOGTAG, "Already stopped!"); - return; - } - - mApplicationContext.unregisterReceiver(this); - mApplicationContext = null; - mIsEnabled = false; - } - - @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko") - private static native void onBatteryChange(double level, boolean charging, - double remainingTime); - - @Override - public void onReceive(Context context, Intent intent) { - if (!intent.getAction().equals(Intent.ACTION_BATTERY_CHANGED)) { - Log.e(LOGTAG, "Got an unexpected intent!"); - return; - } - - boolean previousCharging = isCharging(); - double previousLevel = getLevel(); - - // NOTE: it might not be common (in 2012) but technically, Android can run - // on a device that has no battery so we want to make sure it's not the case - // before bothering checking for battery state. - // However, the Galaxy Nexus phone advertises itself as battery-less which - // force us to special-case the logic. - // See the Google bug: https://code.google.com/p/android/issues/detail?id=22035 - if (intent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, false) || - Build.MODEL.equals("Galaxy Nexus")) { - int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); - if (plugged == -1) { - sCharging = kDefaultCharging; - Log.e(LOGTAG, "Failed to get the plugged status!"); - } else { - // Likely, if plugged > 0, it's likely plugged and charging but the doc - // isn't clear about that. - sCharging = plugged != 0; - } - - if (sCharging != previousCharging) { - sRemainingTime = kUnknownRemainingTime; - // The new remaining time is going to take some time to show up but - // it's the best way to show a not too wrong value. - sLastLevelChange = 0; - } - - // We need two doubles because sLevel is a double. - double current = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); - double max = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1); - if (current == -1 || max == -1) { - Log.e(LOGTAG, "Failed to get battery level!"); - sLevel = kDefaultLevel; - } else { - sLevel = current / max; - } - - if (sLevel == 1.0 && sCharging) { - sRemainingTime = kDefaultRemainingTime; - } else if (sLevel != previousLevel) { - // Estimate remaining time. - if (sLastLevelChange != 0) { - // Use elapsedRealtime() because we want to track time across device sleeps. - long currentTime = SystemClock.elapsedRealtime(); - long dt = (currentTime - sLastLevelChange) / 1000; - double dLevel = sLevel - previousLevel; - - if (sCharging) { - if (dLevel < 0) { - sRemainingTime = kUnknownRemainingTime; - } else { - sRemainingTime = Math.round(dt / dLevel * (1.0 - sLevel)); - } - } else { - if (dLevel > 0) { - Log.w(LOGTAG, "When discharging, level should decrease!"); - sRemainingTime = kUnknownRemainingTime; - } else { - sRemainingTime = Math.round(dt / -dLevel * sLevel); - } - } - - sLastLevelChange = currentTime; - } else { - // That's the first time we got an update, we can't do anything. - sLastLevelChange = SystemClock.elapsedRealtime(); - } - } - } else { - sLevel = kDefaultLevel; - sCharging = kDefaultCharging; - sRemainingTime = kDefaultRemainingTime; - } - - /* - * We want to inform listeners if the following conditions are fulfilled: - * - we have at least one observer; - * - the charging state or the level has changed. - * - * Note: no need to check for a remaining time change given that it's only - * updated if there is a level change or a charging change. - * - * The idea is to prevent doing all the way to the DOM code in the child - * process to finally not send an event. - */ - if (sNotificationsEnabled && - (previousCharging != isCharging() || previousLevel != getLevel())) { - onBatteryChange(getLevel(), isCharging(), getRemainingTime()); - } - } - - public static boolean isCharging() { - return sCharging; - } - - public static double getLevel() { - return sLevel; - } - - public static double getRemainingTime() { - return sRemainingTime; - } - - public static void enableNotifications() { - sNotificationsEnabled = true; - } - - public static void disableNotifications() { - sNotificationsEnabled = false; - } - - public static double[] getCurrentInformation() { - return new double[] { getLevel(), isCharging() ? 1.0 : 0.0, getRemainingTime() }; - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditable.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditable.java deleted file mode 100644 index 695cff443..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditable.java +++ /dev/null @@ -1,1589 +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; - -import java.lang.reflect.Array; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.concurrent.ConcurrentLinkedQueue; - -import org.json.JSONObject; -import org.mozilla.gecko.AppConstants.Versions; -import org.mozilla.gecko.annotation.WrapForJNI; -import org.mozilla.gecko.gfx.LayerView; -import org.mozilla.gecko.mozglue.JNIObject; -import org.mozilla.gecko.util.ThreadUtils; -import org.mozilla.gecko.util.ThreadUtils.AssertBehavior; - -import android.graphics.RectF; -import android.os.Handler; -import android.os.Looper; -import android.text.Editable; -import android.text.InputFilter; -import android.text.NoCopySpan; -import android.text.Selection; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.TextPaint; -import android.text.TextUtils; -import android.text.style.CharacterStyle; -import android.util.Log; -import android.view.KeyCharacterMap; -import android.view.KeyEvent; - -/* - GeckoEditable implements only some functions of Editable - The field mText contains the actual underlying - SpannableStringBuilder/Editable that contains our text. -*/ -final class GeckoEditable extends JNIObject - implements InvocationHandler, Editable, GeckoEditableClient { - - private static final boolean DEBUG = false; - private static final String LOGTAG = "GeckoEditable"; - - // Filters to implement Editable's filtering functionality - private InputFilter[] mFilters; - - private final AsyncText mText; - private final Editable mProxy; - private final ConcurrentLinkedQueue<Action> mActions; - private KeyCharacterMap mKeyMap; - - // mIcRunHandler is the Handler that currently runs Gecko-to-IC Runnables - // mIcPostHandler is the Handler to post Gecko-to-IC Runnables to - // The two can be different when switching from one handler to another - private Handler mIcRunHandler; - private Handler mIcPostHandler; - - /* package */ GeckoEditableListener mListener; - /* package */ GeckoView mView; - - /* package */ boolean mInBatchMode; // Used by IC thread - /* package */ boolean mNeedSync; // Used by IC thread - // Gecko side needs an updated composition from Java; - private boolean mNeedUpdateComposition; // Used by IC thread - private boolean mSuppressKeyUp; // Used by IC thread - - private boolean mGeckoFocused; // Used by Gecko thread - private boolean mIgnoreSelectionChange; // Used by Gecko thread - - private static final int IME_RANGE_CARETPOSITION = 1; - private static final int IME_RANGE_RAWINPUT = 2; - private static final int IME_RANGE_SELECTEDRAWTEXT = 3; - private static final int IME_RANGE_CONVERTEDTEXT = 4; - private static final int IME_RANGE_SELECTEDCONVERTEDTEXT = 5; - - private static final int IME_RANGE_LINE_NONE = 0; - private static final int IME_RANGE_LINE_DOTTED = 1; - private static final int IME_RANGE_LINE_DASHED = 2; - private static final int IME_RANGE_LINE_SOLID = 3; - private static final int IME_RANGE_LINE_DOUBLE = 4; - private static final int IME_RANGE_LINE_WAVY = 5; - - private static final int IME_RANGE_UNDERLINE = 1; - private static final int IME_RANGE_FORECOLOR = 2; - private static final int IME_RANGE_BACKCOLOR = 4; - private static final int IME_RANGE_LINECOLOR = 8; - - @WrapForJNI(dispatchTo = "proxy") - private native void onKeyEvent(int action, int keyCode, int scanCode, int metaState, - long time, int unicodeChar, int baseUnicodeChar, - int domPrintableKeyValue, int repeatCount, int flags, - boolean isSynthesizedImeKey, KeyEvent event); - - private void onKeyEvent(KeyEvent event, int action, int savedMetaState, - boolean isSynthesizedImeKey) { - // Use a separate action argument so we can override the key's original action, - // e.g. change ACTION_MULTIPLE to ACTION_DOWN. That way we don't have to allocate - // a new key event just to change its action field. - // - // Normally we expect event.getMetaState() to reflect the current meta-state; however, - // some software-generated key events may not have event.getMetaState() set, e.g. key - // events from Swype. Therefore, it's necessary to combine the key's meta-states - // with the meta-states that we keep separately in KeyListener - final int metaState = event.getMetaState() | savedMetaState; - final int unmodifiedMetaState = metaState & - ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK | KeyEvent.META_META_MASK); - final int unicodeChar = event.getUnicodeChar(metaState); - final int domPrintableKeyValue = - unicodeChar >= ' ' ? unicodeChar : - unmodifiedMetaState != metaState ? event.getUnicodeChar(unmodifiedMetaState) : - 0; - onKeyEvent(action, event.getKeyCode(), event.getScanCode(), - metaState, event.getEventTime(), unicodeChar, - // e.g. for Ctrl+A, Android returns 0 for unicodeChar, - // but Gecko expects 'a', so we return that in baseUnicodeChar. - event.getUnicodeChar(0), domPrintableKeyValue, event.getRepeatCount(), - event.getFlags(), isSynthesizedImeKey, event); - } - - @WrapForJNI(dispatchTo = "proxy") - private native void onImeSynchronize(); - - @WrapForJNI(dispatchTo = "proxy") - private native void onImeReplaceText(int start, int end, String text); - - @WrapForJNI(dispatchTo = "proxy") - private native void onImeAddCompositionRange(int start, int end, int rangeType, - int rangeStyles, int rangeLineStyle, - boolean rangeBoldLine, int rangeForeColor, - int rangeBackColor, int rangeLineColor); - - @WrapForJNI(dispatchTo = "proxy") - private native void onImeUpdateComposition(int start, int end); - - @WrapForJNI(dispatchTo = "proxy") - private native void onImeRequestCursorUpdates(int requestMode); - - /** - * Class that encapsulates asynchronous text editing. There are two copies of the - * text, a current copy and a shadow copy. Both can be modified independently through - * the current*** and shadow*** methods, respectively. The current copy can only be - * modified on the Gecko side and reflects the authoritative version of the text. The - * shadow copy can only be modified on the IC side and reflects what we think the - * current text is. Periodically, the shadow copy can be synced to the current copy - * through syncShadowText, so the shadow copy once again refers to the same text as - * the current copy. - */ - private final class AsyncText { - // The current text is the update-to-date version of the text, and is only updated - // on the Gecko side. - private final SpannableStringBuilder mCurrentText = new SpannableStringBuilder(); - // Track changes on the current side for syncing purposes. - // Start of the changed range in current text since last sync. - private int mCurrentStart = Integer.MAX_VALUE; - // End of the changed range (before the change) in current text since last sync. - private int mCurrentOldEnd; - // End of the changed range (after the change) in current text since last sync. - private int mCurrentNewEnd; - // Track selection changes separately. - private boolean mCurrentSelectionChanged; - - // The shadow text is what we think the current text is on the Java side, and is - // periodically synced with the current text. - private final SpannableStringBuilder mShadowText = new SpannableStringBuilder(); - // Track changes on the shadow side for syncing purposes. - // Start of the changed range in shadow text since last sync. - private int mShadowStart = Integer.MAX_VALUE; - // End of the changed range (before the change) in shadow text since last sync. - private int mShadowOldEnd; - // End of the changed range (after the change) in shadow text since last sync. - private int mShadowNewEnd; - - private void addCurrentChangeLocked(final int start, final int oldEnd, final int newEnd) { - // Merge the new change into any existing change. - mCurrentStart = Math.min(mCurrentStart, start); - mCurrentOldEnd += Math.max(0, oldEnd - mCurrentNewEnd); - mCurrentNewEnd = newEnd + Math.max(0, mCurrentNewEnd - oldEnd); - } - - public synchronized void currentReplace(final int start, final int end, - final CharSequence newText) { - if (DEBUG) { - ThreadUtils.assertOnGeckoThread(); - } - mCurrentText.replace(start, end, newText); - addCurrentChangeLocked(start, end, start + newText.length()); - } - - public synchronized void currentSetSelection(final int start, final int end) { - if (DEBUG) { - ThreadUtils.assertOnGeckoThread(); - } - Selection.setSelection(mCurrentText, start, end); - mCurrentSelectionChanged = true; - } - - public synchronized void currentSetSpan(final Object obj, final int start, - final int end, final int flags) { - if (DEBUG) { - ThreadUtils.assertOnGeckoThread(); - } - mCurrentText.setSpan(obj, start, end, flags); - addCurrentChangeLocked(start, end, end); - } - - public synchronized void currentRemoveSpan(final Object obj) { - if (DEBUG) { - ThreadUtils.assertOnGeckoThread(); - } - if (obj == null) { - mCurrentText.clearSpans(); - addCurrentChangeLocked(0, mCurrentText.length(), mCurrentText.length()); - return; - } - final int start = mCurrentText.getSpanStart(obj); - final int end = mCurrentText.getSpanEnd(obj); - if (start < 0 || end < 0) { - return; - } - mCurrentText.removeSpan(obj); - addCurrentChangeLocked(start, end, end); - } - - // Return Spanned instead of Editable because the returned object is supposed to - // be read-only. Editing should be done through one of the current*** methods. - public Spanned getCurrentText() { - if (DEBUG) { - ThreadUtils.assertOnGeckoThread(); - } - return mCurrentText; - } - - private void addShadowChange(final int start, final int oldEnd, final int newEnd) { - // Merge the new change into any existing change. - mShadowStart = Math.min(mShadowStart, start); - mShadowOldEnd += Math.max(0, oldEnd - mShadowNewEnd); - mShadowNewEnd = newEnd + Math.max(0, mShadowNewEnd - oldEnd); - } - - public void shadowReplace(final int start, final int end, - final CharSequence newText) - { - if (DEBUG) { - assertOnIcThread(); - } - mShadowText.replace(start, end, newText); - addShadowChange(start, end, start + newText.length()); - } - - public void shadowSetSpan(final Object obj, final int start, - final int end, final int flags) { - if (DEBUG) { - assertOnIcThread(); - } - mShadowText.setSpan(obj, start, end, flags); - addShadowChange(start, end, end); - } - - public void shadowRemoveSpan(final Object obj) { - if (DEBUG) { - assertOnIcThread(); - } - if (obj == null) { - mShadowText.clearSpans(); - addShadowChange(0, mShadowText.length(), mShadowText.length()); - return; - } - final int start = mShadowText.getSpanStart(obj); - final int end = mShadowText.getSpanEnd(obj); - if (start < 0 || end < 0) { - return; - } - mShadowText.removeSpan(obj); - addShadowChange(start, end, end); - } - - // Return Spanned instead of Editable because the returned object is supposed to - // be read-only. Editing should be done through one of the shadow*** methods. - public Spanned getShadowText() { - if (DEBUG) { - assertOnIcThread(); - } - return mShadowText; - } - - public synchronized void syncShadowText(final GeckoEditableListener listener) { - if (DEBUG) { - assertOnIcThread(); - } - - if (mCurrentStart > mCurrentOldEnd && mShadowStart > mShadowOldEnd) { - // Still check selection changes. - if (!mCurrentSelectionChanged) { - return; - } - final int start = Selection.getSelectionStart(mCurrentText); - final int end = Selection.getSelectionEnd(mCurrentText); - Selection.setSelection(mShadowText, start, end); - mCurrentSelectionChanged = false; - - if (listener != null) { - listener.onSelectionChange(); - } - return; - } - - // Copy the portion of the current text that has changed over to the shadow - // text, with consideration for any concurrent changes in the shadow text. - final int start = Math.min(mShadowStart, mCurrentStart); - final int shadowEnd = mShadowNewEnd + Math.max(0, mCurrentOldEnd - mShadowOldEnd); - final int currentEnd = mCurrentNewEnd + Math.max(0, mShadowOldEnd - mCurrentOldEnd); - - // Perform replacement in two steps (delete and insert) so that old spans are - // properly deleted before identical new spans are inserted. Otherwise the new - // spans won't be inserted due to the text already having the old spans. - mShadowText.delete(start, shadowEnd); - mShadowText.insert(start, mCurrentText, start, currentEnd); - - // SpannableStringBuilder has some internal logic to fix up selections, but we - // don't want that, so we always fix up the selection a second time. - final int selStart = Selection.getSelectionStart(mCurrentText); - final int selEnd = Selection.getSelectionEnd(mCurrentText); - Selection.setSelection(mShadowText, selStart, selEnd); - - if (DEBUG && !mShadowText.equals(mCurrentText)) { - // Sanity check. - throw new IllegalStateException("Failed to sync: " + - mShadowStart + '-' + mShadowOldEnd + '-' + mShadowNewEnd + '/' + - mCurrentStart + '-' + mCurrentOldEnd + '-' + mCurrentNewEnd); - } - - if (listener != null) { - // Call onTextChange after selection fix-up but before we call - // onSelectionChange. - listener.onTextChange(); - - if (mCurrentSelectionChanged || (mCurrentOldEnd != mCurrentNewEnd && - (selStart >= mCurrentStart || selEnd >= mCurrentStart))) { - listener.onSelectionChange(); - } - } - - // These values ensure the first change is properly added. - mCurrentStart = mShadowStart = Integer.MAX_VALUE; - mCurrentOldEnd = mShadowOldEnd = 0; - mCurrentNewEnd = mShadowNewEnd = 0; - mCurrentSelectionChanged = false; - } - } - - /* An action that alters the Editable - - Each action corresponds to a Gecko event. While the Gecko event is being sent to the Gecko - thread, the action stays on top of mActions queue. After the Gecko event is processed and - replied, the action is removed from the queue - */ - private static final class Action { - // For input events (keypress, etc.); use with onImeSynchronize - static final int TYPE_EVENT = 0; - // For Editable.replace() call; use with onImeReplaceText - static final int TYPE_REPLACE_TEXT = 1; - // For Editable.setSpan() call; use with onImeSynchronize - static final int TYPE_SET_SPAN = 2; - // For Editable.removeSpan() call; use with onImeSynchronize - static final int TYPE_REMOVE_SPAN = 3; - // For switching handler; use with onImeSynchronize - static final int TYPE_SET_HANDLER = 4; - - final int mType; - int mStart; - int mEnd; - CharSequence mSequence; - Object mSpanObject; - int mSpanFlags; - Handler mHandler; - - Action(int type) { - mType = type; - } - - static Action newReplaceText(CharSequence text, int start, int end) { - if (start < 0 || start > end) { - Log.e(LOGTAG, "invalid replace text offsets: " + start + " to " + end); - throw new IllegalArgumentException("invalid replace text offsets"); - } - - final Action action = new Action(TYPE_REPLACE_TEXT); - action.mSequence = text; - action.mStart = start; - action.mEnd = end; - return action; - } - - static Action newSetSpan(Object object, int start, int end, int flags) { - if (start < 0 || start > end) { - Log.e(LOGTAG, "invalid span offsets: " + start + " to " + end); - throw new IllegalArgumentException("invalid span offsets"); - } - final Action action = new Action(TYPE_SET_SPAN); - action.mSpanObject = object; - action.mStart = start; - action.mEnd = end; - action.mSpanFlags = flags; - return action; - } - - static Action newRemoveSpan(Object object) { - final Action action = new Action(TYPE_REMOVE_SPAN); - action.mSpanObject = object; - return action; - } - - static Action newSetHandler(Handler handler) { - final Action action = new Action(TYPE_SET_HANDLER); - action.mHandler = handler; - return action; - } - } - - private void icOfferAction(final Action action) { - if (DEBUG) { - assertOnIcThread(); - Log.d(LOGTAG, "offer: Action(" + - getConstantName(Action.class, "TYPE_", action.mType) + ")"); - } - - if (mListener == null) { - // We haven't initialized or we've been destroyed. - return; - } - - mActions.offer(action); - - switch (action.mType) { - case Action.TYPE_EVENT: - case Action.TYPE_SET_HANDLER: - onImeSynchronize(); - break; - - case Action.TYPE_SET_SPAN: - mText.shadowSetSpan(action.mSpanObject, action.mStart, - action.mEnd, action.mSpanFlags); - action.mSequence = TextUtils.substring( - mText.getShadowText(), action.mStart, action.mEnd); - - mNeedUpdateComposition |= (action.mSpanFlags & Spanned.SPAN_INTERMEDIATE) == 0 && - ((action.mSpanFlags & Spanned.SPAN_COMPOSING) != 0 || - action.mSpanObject == Selection.SELECTION_START || - action.mSpanObject == Selection.SELECTION_END); - - onImeSynchronize(); - break; - - case Action.TYPE_REMOVE_SPAN: - final int flags = mText.getShadowText().getSpanFlags(action.mSpanObject); - mText.shadowRemoveSpan(action.mSpanObject); - - mNeedUpdateComposition |= (flags & Spanned.SPAN_INTERMEDIATE) == 0 && - (flags & Spanned.SPAN_COMPOSING) != 0; - - onImeSynchronize(); - break; - - case Action.TYPE_REPLACE_TEXT: - // Always sync text after a replace action, so that if the Gecko - // text is not changed, we will revert the shadow text to before. - mNeedSync = true; - - // Because we get composition styling here essentially for free, - // we don't need to check if we're in batch mode. - if (!icMaybeSendComposition( - action.mSequence, /* useEntireText */ true, /* notifyGecko */ false)) { - // Since we don't have a composition, we can try sending key events. - sendCharKeyEvents(action); - } - mText.shadowReplace(action.mStart, action.mEnd, action.mSequence); - onImeReplaceText(action.mStart, action.mEnd, action.mSequence.toString()); - break; - - default: - throw new IllegalStateException("Action not processed"); - } - } - - private KeyEvent [] synthesizeKeyEvents(CharSequence cs) { - try { - if (mKeyMap == null) { - mKeyMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); - } - } catch (Exception e) { - // KeyCharacterMap.UnavailableException is not found on Gingerbread; - // besides, it seems like HC and ICS will throw something other than - // KeyCharacterMap.UnavailableException; so use a generic Exception here - return null; - } - KeyEvent [] keyEvents = mKeyMap.getEvents(cs.toString().toCharArray()); - if (keyEvents == null || keyEvents.length == 0) { - return null; - } - return keyEvents; - } - - private void sendCharKeyEvents(Action action) { - if (action.mSequence.length() != 1 || - (action.mSequence instanceof Spannable && - ((Spannable)action.mSequence).nextSpanTransition( - -1, Integer.MAX_VALUE, null) < Integer.MAX_VALUE)) { - // Spans are not preserved when we use key events, - // so we need the sequence to not have any spans - return; - } - KeyEvent [] keyEvents = synthesizeKeyEvents(action.mSequence); - if (keyEvents == null) { - return; - } - for (KeyEvent event : keyEvents) { - if (KeyEvent.isModifierKey(event.getKeyCode())) { - continue; - } - if (event.getAction() == KeyEvent.ACTION_UP && mSuppressKeyUp) { - continue; - } - if (DEBUG) { - Log.d(LOGTAG, "sending: " + event); - } - onKeyEvent(event, event.getAction(), - /* metaState */ 0, /* isSynthesizedImeKey */ true); - } - } - - @WrapForJNI(calledFrom = "gecko") - GeckoEditable(final GeckoView v) { - if (DEBUG) { - // Called by nsWindow. - ThreadUtils.assertOnGeckoThread(); - } - - mText = new AsyncText(); - mActions = new ConcurrentLinkedQueue<Action>(); - - final Class<?>[] PROXY_INTERFACES = { Editable.class }; - mProxy = (Editable)Proxy.newProxyInstance( - Editable.class.getClassLoader(), - PROXY_INTERFACES, this); - - mIcRunHandler = mIcPostHandler = ThreadUtils.getUiHandler(); - - onViewChange(v); - } - - @WrapForJNI(dispatchTo = "proxy") @Override - protected native void disposeNative(); - - @WrapForJNI(calledFrom = "gecko") - private void onViewChange(final GeckoView v) { - if (DEBUG) { - // Called by nsWindow. - ThreadUtils.assertOnGeckoThread(); - Log.d(LOGTAG, "onViewChange(" + v + ")"); - } - - final GeckoEditableListener newListener = - v != null ? GeckoInputConnection.create(v, this) : null; - - final Runnable setListenerRunnable = new Runnable() { - @Override - public void run() { - if (DEBUG) { - Log.d(LOGTAG, "onViewChange (set listener)"); - } - - mListener = newListener; - - if (newListener == null) { - // We're being destroyed. By this point, we should have cleared all - // pending Runnables on the IC thread, so it's safe to call - // disposeNative here. - GeckoEditable.this.disposeNative(); - } - } - }; - - // Post to UI thread first to make sure any code that is using the old input - // connection has finished running, before we switch to a new input connection or - // before we clear the input connection on destruction. - final Handler icHandler = mIcPostHandler; - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - if (DEBUG) { - Log.d(LOGTAG, "onViewChange (set IC)"); - } - - if (mView != null) { - // Detach the previous view. - mView.setInputConnectionListener(null); - } - if (v != null) { - // And attach the new view. - v.setInputConnectionListener((InputConnectionListener) newListener); - } - - mView = v; - icHandler.post(setListenerRunnable); - } - }); - } - - private boolean onIcThread() { - return mIcRunHandler.getLooper() == Looper.myLooper(); - } - - private void assertOnIcThread() { - ThreadUtils.assertOnThread(mIcRunHandler.getLooper().getThread(), AssertBehavior.THROW); - } - - private void geckoPostToIc(Runnable runnable) { - if (DEBUG) { - ThreadUtils.assertOnGeckoThread(); - } - mIcPostHandler.post(runnable); - } - - private Object getField(Object obj, String field, Object def) { - try { - return obj.getClass().getField(field).get(obj); - } catch (Exception e) { - return def; - } - } - - /** - * Send composition ranges to Gecko for the entire shadow text. - */ - private void icMaybeSendComposition() { - if (!mNeedUpdateComposition) { - return; - } - - icMaybeSendComposition(mText.getShadowText(), - /* useEntireText */ false, /* notifyGecko */ true); - } - - /** - * Send composition ranges to Gecko if the text has composing spans. - * - * @param sequence Text with possible composing spans - * @param useEntireText If text has composing spans, treat the entire text as - * a Gecko composition, instead of just the spanned part. - * @param notifyGecko Notify Gecko of the new composition ranges; - * otherwise, the caller is responsible for notifying Gecko. - * @return Whether there was a composition - */ - private boolean icMaybeSendComposition(final CharSequence sequence, - final boolean useEntireText, - final boolean notifyGecko) { - mNeedUpdateComposition = false; - - int selStart = Selection.getSelectionStart(sequence); - int selEnd = Selection.getSelectionEnd(sequence); - - if (sequence instanceof Spanned) { - final Spanned text = (Spanned) sequence; - final Object[] spans = text.getSpans(0, text.length(), Object.class); - boolean found = false; - int composingStart = useEntireText ? 0 : Integer.MAX_VALUE; - int composingEnd = useEntireText ? text.length() : 0; - - // Find existence and range of any composing spans (spans with the - // SPAN_COMPOSING flag set). - for (Object span : spans) { - if ((text.getSpanFlags(span) & Spanned.SPAN_COMPOSING) == 0) { - continue; - } - found = true; - if (useEntireText) { - break; - } - composingStart = Math.min(composingStart, text.getSpanStart(span)); - composingEnd = Math.max(composingEnd, text.getSpanEnd(span)); - } - - if (useEntireText && (selStart < 0 || selEnd < 0)) { - selStart = composingEnd; - selEnd = composingEnd; - } - - if (found) { - icSendComposition(text, selStart, selEnd, composingStart, composingEnd); - if (notifyGecko) { - onImeUpdateComposition(composingStart, composingEnd); - } - return true; - } - } - - if (notifyGecko) { - // Set the selection by using a composition without ranges - onImeUpdateComposition(selStart, selEnd); - } - - if (DEBUG) { - Log.d(LOGTAG, "icSendComposition(): no composition"); - } - return false; - } - - private void icSendComposition(final Spanned text, - final int selStart, final int selEnd, - final int composingStart, final int composingEnd) { - if (DEBUG) { - assertOnIcThread(); - Log.d(LOGTAG, "icSendComposition(\"" + text + "\", " + - composingStart + ", " + composingEnd + ")"); - } - if (DEBUG) { - Log.d(LOGTAG, " range = " + composingStart + "-" + composingEnd); - Log.d(LOGTAG, " selection = " + selStart + "-" + selEnd); - } - - if (selEnd >= composingStart && selEnd <= composingEnd) { - onImeAddCompositionRange( - selEnd - composingStart, selEnd - composingStart, - IME_RANGE_CARETPOSITION, 0, 0, false, 0, 0, 0); - } - - int rangeStart = composingStart; - TextPaint tp = new TextPaint(); - TextPaint emptyTp = new TextPaint(); - // set initial foreground color to 0, because we check for tp.getColor() == 0 - // below to decide whether to pass a foreground color to Gecko - emptyTp.setColor(0); - do { - int rangeType, rangeStyles = 0, rangeLineStyle = IME_RANGE_LINE_NONE; - boolean rangeBoldLine = false; - int rangeForeColor = 0, rangeBackColor = 0, rangeLineColor = 0; - int rangeEnd = text.nextSpanTransition(rangeStart, composingEnd, Object.class); - - if (selStart > rangeStart && selStart < rangeEnd) { - rangeEnd = selStart; - } else if (selEnd > rangeStart && selEnd < rangeEnd) { - rangeEnd = selEnd; - } - CharacterStyle[] styleSpans = - text.getSpans(rangeStart, rangeEnd, CharacterStyle.class); - - if (DEBUG) { - Log.d(LOGTAG, " found " + styleSpans.length + " spans @ " + - rangeStart + "-" + rangeEnd); - } - - if (styleSpans.length == 0) { - rangeType = (selStart == rangeStart && selEnd == rangeEnd) - ? IME_RANGE_SELECTEDRAWTEXT - : IME_RANGE_RAWINPUT; - } else { - rangeType = (selStart == rangeStart && selEnd == rangeEnd) - ? IME_RANGE_SELECTEDCONVERTEDTEXT - : IME_RANGE_CONVERTEDTEXT; - tp.set(emptyTp); - for (CharacterStyle span : styleSpans) { - span.updateDrawState(tp); - } - int tpUnderlineColor = 0; - float tpUnderlineThickness = 0.0f; - - // These TextPaint fields only exist on Android ICS+ and are not in the SDK. - tpUnderlineColor = (Integer)getField(tp, "underlineColor", 0); - tpUnderlineThickness = (Float)getField(tp, "underlineThickness", 0.0f); - if (tpUnderlineColor != 0) { - rangeStyles |= IME_RANGE_UNDERLINE | IME_RANGE_LINECOLOR; - rangeLineColor = tpUnderlineColor; - // Approximately translate underline thickness to what Gecko understands - if (tpUnderlineThickness <= 0.5f) { - rangeLineStyle = IME_RANGE_LINE_DOTTED; - } else { - rangeLineStyle = IME_RANGE_LINE_SOLID; - if (tpUnderlineThickness >= 2.0f) { - rangeBoldLine = true; - } - } - } else if (tp.isUnderlineText()) { - rangeStyles |= IME_RANGE_UNDERLINE; - rangeLineStyle = IME_RANGE_LINE_SOLID; - } - if (tp.getColor() != 0) { - rangeStyles |= IME_RANGE_FORECOLOR; - rangeForeColor = tp.getColor(); - } - if (tp.bgColor != 0) { - rangeStyles |= IME_RANGE_BACKCOLOR; - rangeBackColor = tp.bgColor; - } - } - onImeAddCompositionRange( - rangeStart - composingStart, rangeEnd - composingStart, - rangeType, rangeStyles, rangeLineStyle, rangeBoldLine, - rangeForeColor, rangeBackColor, rangeLineColor); - rangeStart = rangeEnd; - - if (DEBUG) { - Log.d(LOGTAG, " added " + rangeType + - " : " + Integer.toHexString(rangeStyles) + - " : " + Integer.toHexString(rangeForeColor) + - " : " + Integer.toHexString(rangeBackColor)); - } - } while (rangeStart < composingEnd); - } - - // GeckoEditableClient interface - - @Override - public void sendKeyEvent(final KeyEvent event, int action, int metaState) { - if (DEBUG) { - assertOnIcThread(); - Log.d(LOGTAG, "sendKeyEvent(" + event + ", " + action + ", " + metaState + ")"); - } - /* - We are actually sending two events to Gecko here, - 1. Event from the event parameter (key event) - 2. Sync event from the icOfferAction call - The first event is a normal event that does not reply back to us, - the second sync event will have a reply, during which we see that there is a pending - event-type action, and update the shadow text accordingly. - */ - icMaybeSendComposition(); - onKeyEvent(event, action, metaState, /* isSynthesizedImeKey */ false); - icOfferAction(new Action(Action.TYPE_EVENT)); - } - - @Override - public Editable getEditable() { - if (!onIcThread()) { - // Android may be holding an old InputConnection; ignore - if (DEBUG) { - Log.i(LOGTAG, "getEditable() called on non-IC thread"); - } - return null; - } - if (mListener == null) { - // We haven't initialized or we've been destroyed. - return null; - } - return mProxy; - } - - @Override - public void setBatchMode(boolean inBatchMode) { - if (!onIcThread()) { - // Android may be holding an old InputConnection; ignore - if (DEBUG) { - Log.i(LOGTAG, "setBatchMode() called on non-IC thread"); - } - return; - } - - mInBatchMode = inBatchMode; - - if (!inBatchMode && mNeedSync) { - icSyncShadowText(); - } - } - - /* package */ void icSyncShadowText() { - if (mListener == null) { - // Not yet attached or already destroyed. - return; - } - - if (mInBatchMode || !mActions.isEmpty()) { - mNeedSync = true; - return; - } - - mNeedSync = false; - mText.syncShadowText(mListener); - } - - private void geckoScheduleSyncShadowText() { - if (DEBUG) { - ThreadUtils.assertOnGeckoThread(); - } - geckoPostToIc(new Runnable() { - @Override - public void run() { - icSyncShadowText(); - } - }); - } - - @Override - public void setSuppressKeyUp(boolean suppress) { - if (DEBUG) { - assertOnIcThread(); - } - // Suppress key up event generated as a result of - // translating characters to key events - mSuppressKeyUp = suppress; - } - - @Override // GeckoEditableClient - public Handler setInputConnectionHandler(final Handler handler) { - if (handler == mIcRunHandler) { - return mIcRunHandler; - } - if (DEBUG) { - assertOnIcThread(); - } - - // There are three threads at this point: Gecko thread, old IC thread, and new IC - // thread, and we want to safely switch from old IC thread to new IC thread. - // We first send a TYPE_SET_HANDLER action to the Gecko thread; this ensures that - // the Gecko thread is stopped at a known point. At the same time, the old IC - // thread blocks on the action; this ensures that the old IC thread is stopped at - // a known point. Finally, inside the Gecko thread, we post a Runnable to the old - // IC thread; this Runnable switches from old IC thread to new IC thread. We - // switch IC thread on the old IC thread to ensure any pending Runnables on the - // old IC thread are processed before we switch over. Inside the Gecko thread, we - // also post a Runnable to the new IC thread; this Runnable blocks until the - // switch is complete; this ensures that the new IC thread won't accept - // InputConnection calls until after the switch. - - handler.post(new Runnable() { // Make the new IC thread wait. - @Override - public void run() { - synchronized (handler) { - while (mIcRunHandler != handler) { - try { - handler.wait(); - } catch (final InterruptedException e) { - } - } - } - } - }); - - icOfferAction(Action.newSetHandler(handler)); - return handler; - } - - @Override // GeckoEditableClient - public void postToInputConnection(final Runnable runnable) { - mIcPostHandler.post(runnable); - } - - @Override // GeckoEditableClient - public void requestCursorUpdates(int requestMode) { - onImeRequestCursorUpdates(requestMode); - } - - private void geckoSetIcHandler(final Handler newHandler) { - geckoPostToIc(new Runnable() { // posting to old IC thread - @Override - public void run() { - synchronized (newHandler) { - mIcRunHandler = newHandler; - newHandler.notify(); - } - } - }); - - // At this point, all future Runnables should be posted to the new IC thread, but - // we don't switch mIcRunHandler yet because there may be pending Runnables on the - // old IC thread still waiting to run. - mIcPostHandler = newHandler; - } - - private void geckoActionReply(final Action action) { - if (!mGeckoFocused) { - if (DEBUG) { - Log.d(LOGTAG, "discarding stale reply"); - } - return; - } - - if (DEBUG) { - // GeckoEditableListener methods should all be called from the Gecko thread - ThreadUtils.assertOnGeckoThread(); - Log.d(LOGTAG, "reply: Action(" + - getConstantName(Action.class, "TYPE_", action.mType) + ")"); - } - switch (action.mType) { - case Action.TYPE_SET_SPAN: - final int len = mText.getCurrentText().length(); - if (action.mStart > len || action.mEnd > len || - !TextUtils.substring(mText.getCurrentText(), action.mStart, - action.mEnd).equals(action.mSequence)) { - if (DEBUG) { - Log.d(LOGTAG, "discarding stale set span call"); - } - break; - } - mText.currentSetSpan(action.mSpanObject, action.mStart, action.mEnd, action.mSpanFlags); - break; - - case Action.TYPE_REMOVE_SPAN: - mText.currentRemoveSpan(action.mSpanObject); - break; - - case Action.TYPE_SET_HANDLER: - geckoSetIcHandler(action.mHandler); - break; - } - } - - private void notifyCommitComposition() { - // Gecko already committed its composition. However, Android keyboards - // have trouble dealing with us removing the composition manually on - // the Java side. Therefore, we keep the composition intact on the Java - // side. The text content should still be in-sync on both sides. - } - - private void notifyCancelComposition() { - // Composition should have been canceled on our side - // through text update notifications; verify that here. - if (DEBUG) { - final Spanned text = mText.getCurrentText(); - final Object[] spans = text.getSpans(0, text.length(), Object.class); - for (Object span : spans) { - if ((text.getSpanFlags(span) & Spanned.SPAN_COMPOSING) != 0) { - throw new IllegalStateException("composition not cancelled"); - } - } - } - } - - @WrapForJNI(calledFrom = "gecko") - private void notifyIME(final int type) { - if (DEBUG) { - // GeckoEditableListener methods should all be called from the Gecko thread - ThreadUtils.assertOnGeckoThread(); - // NOTIFY_IME_REPLY_EVENT is logged separately, inside geckoActionReply() - if (type != GeckoEditableListener.NOTIFY_IME_REPLY_EVENT) { - Log.d(LOGTAG, "notifyIME(" + - getConstantName(GeckoEditableListener.class, "NOTIFY_IME_", type) + - ")"); - } - } - - if (type == GeckoEditableListener.NOTIFY_IME_REPLY_EVENT) { - geckoActionReply(mActions.poll()); - if (!mGeckoFocused || !mActions.isEmpty()) { - // Only post to IC thread below when the queue is empty. - return; - } - } else if (type == GeckoEditableListener.NOTIFY_IME_TO_COMMIT_COMPOSITION) { - notifyCommitComposition(); - return; - } else if (type == GeckoEditableListener.NOTIFY_IME_TO_CANCEL_COMPOSITION) { - notifyCancelComposition(); - return; - } - - geckoPostToIc(new Runnable() { - @Override - public void run() { - if (type == GeckoEditableListener.NOTIFY_IME_REPLY_EVENT) { - if (mNeedSync) { - icSyncShadowText(); - } - return; - } - - if (type == GeckoEditableListener.NOTIFY_IME_OF_FOCUS && mListener != null) { - mNeedSync = false; - mText.syncShadowText(/* listener */ null); - } - - if (mListener != null) { - mListener.notifyIME(type); - } - } - }); - - // Update the mGeckoFocused flag. - if (type == GeckoEditableListener.NOTIFY_IME_OF_BLUR) { - mGeckoFocused = false; - } else if (type == GeckoEditableListener.NOTIFY_IME_OF_FOCUS) { - mGeckoFocused = true; - } - } - - @WrapForJNI(calledFrom = "gecko") - private void notifyIMEContext(final int state, final String typeHint, - final String modeHint, final String actionHint) { - if (DEBUG) { - // GeckoEditableListener methods should all be called from the Gecko thread - ThreadUtils.assertOnGeckoThread(); - Log.d(LOGTAG, "notifyIMEContext(" + - getConstantName(GeckoEditableListener.class, "IME_STATE_", state) + - ", \"" + typeHint + "\", \"" + modeHint + "\", \"" + actionHint + "\")"); - } - geckoPostToIc(new Runnable() { - @Override - public void run() { - if (mListener == null) { - return; - } - mListener.notifyIMEContext(state, typeHint, modeHint, actionHint); - } - }); - } - - @WrapForJNI(calledFrom = "gecko", exceptionMode = "ignore") - private void onSelectionChange(final int start, final int end) { - if (DEBUG) { - // GeckoEditableListener methods should all be called from the Gecko thread - ThreadUtils.assertOnGeckoThread(); - Log.d(LOGTAG, "onSelectionChange(" + start + ", " + end + ")"); - } - - final int currentLength = mText.getCurrentText().length(); - if (start < 0 || start > currentLength || end < 0 || end > currentLength) { - Log.e(LOGTAG, "invalid selection notification range: " + - start + " to " + end + ", length: " + currentLength); - throw new IllegalArgumentException("invalid selection notification range"); - } - - if (mIgnoreSelectionChange) { - mIgnoreSelectionChange = false; - } else { - mText.currentSetSelection(start, end); - } - - geckoScheduleSyncShadowText(); - } - - private boolean geckoIsSameText(int start, int oldEnd, CharSequence newText) { - return oldEnd - start == newText.length() && - TextUtils.regionMatches(mText.getCurrentText(), start, newText, 0, oldEnd - start); - } - - @WrapForJNI(calledFrom = "gecko", exceptionMode = "ignore") - private void onTextChange(final CharSequence text, final int start, - final int unboundedOldEnd, final int unboundedNewEnd) { - if (DEBUG) { - // GeckoEditableListener methods should all be called from the Gecko thread - ThreadUtils.assertOnGeckoThread(); - StringBuilder sb = new StringBuilder("onTextChange("); - debugAppend(sb, text); - sb.append(", ").append(start).append(", ") - .append(unboundedOldEnd).append(", ") - .append(unboundedNewEnd).append(")"); - Log.d(LOGTAG, sb.toString()); - } - if (start < 0 || start > unboundedOldEnd) { - Log.e(LOGTAG, "invalid text notification range: " + - start + " to " + unboundedOldEnd); - throw new IllegalArgumentException("invalid text notification range"); - } - - final int currentLength = mText.getCurrentText().length(); - - /* For the "end" parameters, Gecko can pass in a large - number to denote "end of the text". Fix that here */ - final int oldEnd = unboundedOldEnd > currentLength ? currentLength : unboundedOldEnd; - // new end should always match text - if (unboundedOldEnd <= currentLength && unboundedNewEnd != (start + text.length())) { - Log.e(LOGTAG, "newEnd does not match text: " + unboundedNewEnd + " vs " + - (start + text.length())); - throw new IllegalArgumentException("newEnd does not match text"); - } - - final int newEnd = start + text.length(); - final Action action = mActions.peek(); - - if (start == 0 && unboundedOldEnd > currentLength) { - // Simply replace the text for newly-focused editors. Replace in two steps to - // properly clear composing spans that span the whole range. - mText.currentReplace(0, currentLength, ""); - mText.currentReplace(0, 0, text); - - // Don't ignore the next selection change because we are re-syncing with Gecko - mIgnoreSelectionChange = false; - - } else if (action != null && - action.mType == Action.TYPE_REPLACE_TEXT && - start <= action.mStart && - oldEnd >= action.mEnd && - newEnd >= action.mStart + action.mSequence.length()) { - - // Try to preserve both old spans and new spans in action.mSequence. - // indexInText is where we can find waction.mSequence within the passed in text. - final int startWithinText = action.mStart - start; - int indexInText = TextUtils.indexOf(text, action.mSequence, startWithinText); - if (indexInText < 0 && startWithinText >= action.mSequence.length()) { - indexInText = text.toString().lastIndexOf(action.mSequence.toString(), - startWithinText); - } - - if (indexInText < 0) { - // Text was changed from under us. We are forced to discard any new spans. - mText.currentReplace(start, oldEnd, text); - - // Don't ignore the next selection change because we are forced to re-sync - // with Gecko here. - mIgnoreSelectionChange = false; - - } else if (indexInText == 0 && text.length() == action.mSequence.length() && - oldEnd - start == action.mEnd - action.mStart) { - // The new change exactly matches our saved change, so do a direct replace. - mText.currentReplace(start, oldEnd, action.mSequence); - - // Ignore the next selection change because the selection change is a - // side-effect of the replace-text event we sent. - mIgnoreSelectionChange = true; - - } else { - // The sequence is embedded within the changed text, so we have to perform - // replacement in parts. First replace part of text before the sequence. - mText.currentReplace(start, action.mStart, text.subSequence(0, indexInText)); - - // Then replace part of the text after the sequence. - final int actionStart = indexInText + start; - final int delta = actionStart - action.mStart; - final int actionEnd = delta + action.mEnd; - - final Spanned currentText = mText.getCurrentText(); - final boolean resetSelStart = Selection.getSelectionStart(currentText) == actionEnd; - final boolean resetSelEnd = Selection.getSelectionEnd(currentText) == actionEnd; - - mText.currentReplace(actionEnd, delta + oldEnd, text.subSequence( - indexInText + action.mSequence.length(), text.length())); - - // The replacement above may have shifted our selection, if the selection - // was at the start of the replacement range. If so, we need to reset - // our selection to the previous position. - if (resetSelStart || resetSelEnd) { - mText.currentSetSelection( - resetSelStart ? actionEnd : Selection.getSelectionStart(currentText), - resetSelEnd ? actionEnd : Selection.getSelectionEnd(currentText)); - } - - // Finally replace the sequence itself to preserve new spans. - mText.currentReplace(actionStart, actionEnd, action.mSequence); - - // Ignore the next selection change because the selection change is a - // side-effect of the replace-text event we sent. - mIgnoreSelectionChange = true; - } - - } else if (geckoIsSameText(start, oldEnd, text)) { - // Nothing to do because the text is the same. This could happen when - // the composition is updated for example, in which case we want to keep the - // Java selection. - mIgnoreSelectionChange = mIgnoreSelectionChange || - (action != null && action.mType == Action.TYPE_REPLACE_TEXT); - return; - - } else { - // Gecko side initiated the text change. Replace in two steps to properly - // clear composing spans that span the whole range. - mText.currentReplace(start, oldEnd, ""); - mText.currentReplace(start, start, text); - - // Don't ignore the next selection change because we are forced to re-sync - // with Gecko here. - mIgnoreSelectionChange = false; - } - - // onTextChange is always followed by onSelectionChange, so we let - // onSelectionChange schedule a shadow text sync. - } - - @WrapForJNI(calledFrom = "gecko") - private void onDefaultKeyEvent(final KeyEvent event) { - if (DEBUG) { - // GeckoEditableListener methods should all be called from the Gecko thread - ThreadUtils.assertOnGeckoThread(); - StringBuilder sb = new StringBuilder("onDefaultKeyEvent("); - sb.append("action=").append(event.getAction()).append(", ") - .append("keyCode=").append(event.getKeyCode()).append(", ") - .append("metaState=").append(event.getMetaState()).append(", ") - .append("time=").append(event.getEventTime()).append(", ") - .append("repeatCount=").append(event.getRepeatCount()).append(")"); - Log.d(LOGTAG, sb.toString()); - } - - geckoPostToIc(new Runnable() { - @Override - public void run() { - if (mListener == null) { - return; - } - mListener.onDefaultKeyEvent(event); - } - }); - } - - @WrapForJNI(calledFrom = "gecko") - private void updateCompositionRects(final RectF[] aRects) { - if (DEBUG) { - // GeckoEditableListener methods should all be called from the Gecko thread - ThreadUtils.assertOnGeckoThread(); - Log.d(LOGTAG, "updateCompositionRects(aRects.length = " + aRects.length + ")"); - } - geckoPostToIc(new Runnable() { - @Override - public void run() { - if (mListener == null) { - return; - } - mListener.updateCompositionRects(aRects); - } - }); - } - - // InvocationHandler interface - - static String getConstantName(Class<?> cls, String prefix, Object value) { - for (Field fld : cls.getDeclaredFields()) { - try { - if (fld.getName().startsWith(prefix) && - fld.get(null).equals(value)) { - return fld.getName(); - } - } catch (IllegalAccessException e) { - } - } - return String.valueOf(value); - } - - static StringBuilder debugAppend(StringBuilder sb, Object obj) { - if (obj == null) { - sb.append("null"); - } else if (obj instanceof GeckoEditable) { - sb.append("GeckoEditable"); - } else if (Proxy.isProxyClass(obj.getClass())) { - debugAppend(sb, Proxy.getInvocationHandler(obj)); - } else if (obj instanceof CharSequence) { - sb.append('"').append(obj.toString().replace('\n', '\u21b2')).append('"'); - } else if (obj.getClass().isArray()) { - sb.append(obj.getClass().getComponentType().getSimpleName()).append('[') - .append(Array.getLength(obj)).append(']'); - } else { - sb.append(obj); - } - return sb; - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) - throws Throwable { - Object target; - final Class<?> methodInterface = method.getDeclaringClass(); - if (DEBUG) { - // Editable methods should all be called from the IC thread - assertOnIcThread(); - } - if (methodInterface == Editable.class || - methodInterface == Appendable.class || - methodInterface == Spannable.class) { - // Method alters the Editable; route calls to our implementation - target = this; - } else { - target = mText.getShadowText(); - } - Object ret; - try { - ret = method.invoke(target, args); - } catch (InvocationTargetException e) { - // Bug 817386 - // Most likely Gecko has changed the text while GeckoInputConnection is - // trying to access the text. If we pass through the exception here, Fennec - // will crash due to a lack of exception handler. Log the exception and - // return an empty value instead. - if (!(e.getCause() instanceof IndexOutOfBoundsException)) { - // Only handle IndexOutOfBoundsException for now, - // as other exceptions might signal other bugs - throw e; - } - Log.w(LOGTAG, "Exception in GeckoEditable." + method.getName(), e.getCause()); - Class<?> retClass = method.getReturnType(); - if (retClass == Character.TYPE) { - ret = '\0'; - } else if (retClass == Integer.TYPE) { - ret = 0; - } else if (retClass == String.class) { - ret = ""; - } else { - ret = null; - } - } - if (DEBUG) { - StringBuilder log = new StringBuilder(method.getName()); - log.append("("); - if (args != null) { - for (Object arg : args) { - debugAppend(log, arg).append(", "); - } - if (args.length > 0) { - log.setLength(log.length() - 2); - } - } - if (method.getReturnType().equals(Void.TYPE)) { - log.append(")"); - } else { - debugAppend(log.append(") = "), ret); - } - Log.d(LOGTAG, log.toString()); - } - return ret; - } - - // Spannable interface - - @Override - public void removeSpan(Object what) { - if (what == null) { - return; - } - - if (what == Selection.SELECTION_START || - what == Selection.SELECTION_END) { - Log.w(LOGTAG, "selection removed with removeSpan()"); - } - - icOfferAction(Action.newRemoveSpan(what)); - } - - @Override - public void setSpan(Object what, int start, int end, int flags) { - icOfferAction(Action.newSetSpan(what, start, end, flags)); - } - - // Appendable interface - - @Override - public Editable append(CharSequence text) { - return replace(mProxy.length(), mProxy.length(), text, 0, text.length()); - } - - @Override - public Editable append(CharSequence text, int start, int end) { - return replace(mProxy.length(), mProxy.length(), text, start, end); - } - - @Override - public Editable append(char text) { - return replace(mProxy.length(), mProxy.length(), String.valueOf(text), 0, 1); - } - - // Editable interface - - @Override - public InputFilter[] getFilters() { - return mFilters; - } - - @Override - public void setFilters(InputFilter[] filters) { - mFilters = filters; - } - - @Override - public void clearSpans() { - /* XXX this clears the selection spans too, - but there is no way to clear the corresponding selection in Gecko */ - Log.w(LOGTAG, "selection cleared with clearSpans()"); - icOfferAction(Action.newRemoveSpan(/* what */ null)); - } - - @Override - public Editable replace(int st, int en, - CharSequence source, int start, int end) { - - CharSequence text = source; - if (start < 0 || start > end || end > text.length()) { - Log.e(LOGTAG, "invalid replace offsets: " + - start + " to " + end + ", length: " + text.length()); - throw new IllegalArgumentException("invalid replace offsets"); - } - if (start != 0 || end != text.length()) { - text = text.subSequence(start, end); - } - if (mFilters != null) { - // Filter text before sending the request to Gecko - for (int i = 0; i < mFilters.length; ++i) { - final CharSequence cs = mFilters[i].filter( - text, 0, text.length(), mProxy, st, en); - if (cs != null) { - text = cs; - } - } - } - if (text == source) { - // Always create a copy - text = new SpannableString(source); - } - icOfferAction(Action.newReplaceText(text, Math.min(st, en), Math.max(st, en))); - return mProxy; - } - - @Override - public void clear() { - replace(0, mProxy.length(), "", 0, 0); - } - - @Override - public Editable delete(int st, int en) { - return replace(st, en, "", 0, 0); - } - - @Override - public Editable insert(int where, CharSequence text, - int start, int end) { - return replace(where, where, text, start, end); - } - - @Override - public Editable insert(int where, CharSequence text) { - return replace(where, where, text, 0, text.length()); - } - - @Override - public Editable replace(int st, int en, CharSequence text) { - return replace(st, en, text, 0, text.length()); - } - - /* GetChars interface */ - - @Override - public void getChars(int start, int end, char[] dest, int destoff) { - /* overridden Editable interface methods in GeckoEditable must not be called directly - outside of GeckoEditable. Instead, the call must go through mProxy, which ensures - that Java is properly synchronized with Gecko */ - throw new UnsupportedOperationException("method must be called through mProxy"); - } - - /* Spanned interface */ - - @Override - public int getSpanEnd(Object tag) { - throw new UnsupportedOperationException("method must be called through mProxy"); - } - - @Override - public int getSpanFlags(Object tag) { - throw new UnsupportedOperationException("method must be called through mProxy"); - } - - @Override - public int getSpanStart(Object tag) { - throw new UnsupportedOperationException("method must be called through mProxy"); - } - - @Override - public <T> T[] getSpans(int start, int end, Class<T> type) { - throw new UnsupportedOperationException("method must be called through mProxy"); - } - - @Override - @SuppressWarnings("rawtypes") // nextSpanTransition uses raw Class in its Android declaration - public int nextSpanTransition(int start, int limit, Class type) { - throw new UnsupportedOperationException("method must be called through mProxy"); - } - - /* CharSequence interface */ - - @Override - public char charAt(int index) { - throw new UnsupportedOperationException("method must be called through mProxy"); - } - - @Override - public int length() { - throw new UnsupportedOperationException("method must be called through mProxy"); - } - - @Override - public CharSequence subSequence(int start, int end) { - throw new UnsupportedOperationException("method must be called through mProxy"); - } - - @Override - public String toString() { - throw new UnsupportedOperationException("method must be called through mProxy"); - } -} - diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditableClient.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditableClient.java deleted file mode 100644 index 5e721b3af..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditableClient.java +++ /dev/null @@ -1,33 +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; - -import android.os.Handler; -import android.text.Editable; -import android.view.KeyEvent; - -/** - * Interface for the IC thread. - */ -interface GeckoEditableClient { - void sendKeyEvent(KeyEvent event, int action, int metaState); - Editable getEditable(); - void setBatchMode(boolean isBatchMode); - void setSuppressKeyUp(boolean suppress); - Handler setInputConnectionHandler(Handler handler); - void postToInputConnection(Runnable runnable); - - // The following value is used by requestCursorUpdates - - // ONE_SHOT calls updateCompositionRects() after getting current composing character rects. - public static final int ONE_SHOT = 1; - // START_MONITOR start the monitor for composing character rects. If is is updaed, call updateCompositionRects() - public static final int START_MONITOR = 2; - // ENDT_MONITOR stops the monitor for composing character rects. - public static final int END_MONITOR = 3; - - void requestCursorUpdates(int requestMode); -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditableListener.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditableListener.java deleted file mode 100644 index db594aaf7..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditableListener.java +++ /dev/null @@ -1,43 +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; - -import org.mozilla.gecko.annotation.WrapForJNI; - -import android.graphics.RectF; -import android.view.KeyEvent; - -/** - * Interface for the Editable to listen on the Gecko thread, as well as for the IC thread to listen - * to the Editable. - */ -interface GeckoEditableListener { - // IME notification type for notifyIME(), corresponding to NotificationToIME enum in Gecko - @WrapForJNI - int NOTIFY_IME_OPEN_VKB = -2; - @WrapForJNI - int NOTIFY_IME_REPLY_EVENT = -1; - @WrapForJNI - int NOTIFY_IME_OF_FOCUS = 1; - @WrapForJNI - int NOTIFY_IME_OF_BLUR = 2; - @WrapForJNI - int NOTIFY_IME_TO_COMMIT_COMPOSITION = 8; - @WrapForJNI - int NOTIFY_IME_TO_CANCEL_COMPOSITION = 9; - // IME enabled state for notifyIMEContext() - int IME_STATE_DISABLED = 0; - int IME_STATE_ENABLED = 1; - int IME_STATE_PASSWORD = 2; - int IME_STATE_PLUGIN = 3; - - void notifyIME(int type); - void notifyIMEContext(int state, String typeHint, String modeHint, String actionHint); - void onSelectionChange(); - void onTextChange(); - void onDefaultKeyEvent(KeyEvent event); - void updateCompositionRects(final RectF[] aRects); -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoHalDefines.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoHalDefines.java deleted file mode 100644 index 3d9b97427..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoHalDefines.java +++ /dev/null @@ -1,27 +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; - -public class GeckoHalDefines -{ - /* - * Keep these values consistent with |SensorType| in HalSensor.h - */ - public static final int SENSOR_ORIENTATION = 0; - public static final int SENSOR_ACCELERATION = 1; - public static final int SENSOR_PROXIMITY = 2; - public static final int SENSOR_LINEAR_ACCELERATION = 3; - public static final int SENSOR_GYROSCOPE = 4; - public static final int SENSOR_LIGHT = 5; - public static final int SENSOR_ROTATION_VECTOR = 6; - public static final int SENSOR_GAME_ROTATION_VECTOR = 7; - - public static final int SENSOR_ACCURACY_UNKNOWN = -1; - public static final int SENSOR_ACCURACY_UNRELIABLE = 0; - public static final int SENSOR_ACCURACY_LOW = 1; - public static final int SENSOR_ACCURACY_MED = 2; - public static final int SENSOR_ACCURACY_HIGH = 3; -}; diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoInputConnection.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoInputConnection.java deleted file mode 100644 index a80be0bce..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoInputConnection.java +++ /dev/null @@ -1,1060 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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; - -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.concurrent.SynchronousQueue; - -import org.mozilla.gecko.AppConstants.Versions; -import org.mozilla.gecko.gfx.DynamicToolbarAnimator; -import org.mozilla.gecko.util.Clipboard; -import org.mozilla.gecko.util.GamepadUtils; -import org.mozilla.gecko.util.ThreadUtils; -import org.mozilla.gecko.util.ThreadUtils.AssertBehavior; - -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.content.Context; -import android.content.res.Configuration; -import android.graphics.Matrix; -import android.graphics.RectF; -import android.media.AudioManager; -import android.os.Handler; -import android.os.Looper; -import android.os.SystemClock; -import android.text.Editable; -import android.text.InputType; -import android.text.Selection; -import android.text.SpannableString; -import android.text.method.KeyListener; -import android.text.method.TextKeyListener; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.KeyEvent; -import android.view.View; -import android.view.inputmethod.BaseInputConnection; -import android.view.inputmethod.CursorAnchorInfo; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.ExtractedText; -import android.view.inputmethod.ExtractedTextRequest; -import android.view.inputmethod.InputConnection; -import android.view.inputmethod.InputMethodManager; - -class GeckoInputConnection - extends BaseInputConnection - implements InputConnectionListener, GeckoEditableListener { - - private static final boolean DEBUG = false; - protected static final String LOGTAG = "GeckoInputConnection"; - - private static final String CUSTOM_HANDLER_TEST_METHOD = "testInputConnection"; - private static final String CUSTOM_HANDLER_TEST_CLASS = - "org.mozilla.gecko.tests.components.GeckoViewComponent$TextInput"; - - private static final int INLINE_IME_MIN_DISPLAY_SIZE = 480; - - private static Handler sBackgroundHandler; - - // Managed only by notifyIMEContext; see comments in notifyIMEContext - private int mIMEState; - private String mIMETypeHint = ""; - private String mIMEModeHint = ""; - private String mIMEActionHint = ""; - private boolean mFocused; - - private String mCurrentInputMethod = ""; - - private final View mView; - private final GeckoEditableClient mEditableClient; - protected int mBatchEditCount; - private ExtractedTextRequest mUpdateRequest; - private final ExtractedText mUpdateExtract = new ExtractedText(); - private final InputConnection mKeyInputConnection; - private CursorAnchorInfo.Builder mCursorAnchorInfoBuilder; - - // Prevent showSoftInput and hideSoftInput from causing reentrant calls on some devices. - private volatile boolean mSoftInputReentrancyGuard; - - public static GeckoEditableListener create(View targetView, - GeckoEditableClient editable) { - if (DEBUG) - return DebugGeckoInputConnection.create(targetView, editable); - else - return new GeckoInputConnection(targetView, editable); - } - - protected GeckoInputConnection(View targetView, - GeckoEditableClient editable) { - super(targetView, true); - mView = targetView; - mEditableClient = editable; - mIMEState = IME_STATE_DISABLED; - // InputConnection that sends keys for plugins, which don't have full editors - mKeyInputConnection = new BaseInputConnection(targetView, false); - } - - @Override - public synchronized boolean beginBatchEdit() { - mBatchEditCount++; - if (mBatchEditCount == 1) { - mEditableClient.setBatchMode(true); - } - return true; - } - - @Override - public synchronized boolean endBatchEdit() { - if (mBatchEditCount <= 0) { - Log.w(LOGTAG, "endBatchEdit() called, but mBatchEditCount <= 0?!"); - return true; - } - - mBatchEditCount--; - if (mBatchEditCount != 0) { - return true; - } - - // setBatchMode will call onTextChange and/or onSelectionChange for us. - mEditableClient.setBatchMode(false); - return true; - } - - @Override - public Editable getEditable() { - return mEditableClient.getEditable(); - } - - @Override - public boolean performContextMenuAction(int id) { - Editable editable = getEditable(); - if (editable == null) { - return false; - } - int selStart = Selection.getSelectionStart(editable); - int selEnd = Selection.getSelectionEnd(editable); - - switch (id) { - case android.R.id.selectAll: - setSelection(0, editable.length()); - break; - case android.R.id.cut: - // If selection is empty, we'll select everything - if (selStart == selEnd) { - // Fill the clipboard - Clipboard.setText(editable); - editable.clear(); - } else { - Clipboard.setText( - editable.toString().substring( - Math.min(selStart, selEnd), - Math.max(selStart, selEnd))); - editable.delete(selStart, selEnd); - } - break; - case android.R.id.paste: - commitText(Clipboard.getText(), 1); - break; - case android.R.id.copy: - // Copy the current selection or the empty string if nothing is selected. - String copiedText = selStart == selEnd ? "" : - editable.toString().substring( - Math.min(selStart, selEnd), - Math.max(selStart, selEnd)); - Clipboard.setText(copiedText); - break; - } - return true; - } - - @Override - public ExtractedText getExtractedText(ExtractedTextRequest req, int flags) { - if (req == null) - return null; - - if ((flags & GET_EXTRACTED_TEXT_MONITOR) != 0) - mUpdateRequest = req; - - Editable editable = getEditable(); - if (editable == null) { - return null; - } - int selStart = Selection.getSelectionStart(editable); - int selEnd = Selection.getSelectionEnd(editable); - - ExtractedText extract = new ExtractedText(); - extract.flags = 0; - extract.partialStartOffset = -1; - extract.partialEndOffset = -1; - extract.selectionStart = selStart; - extract.selectionEnd = selEnd; - extract.startOffset = 0; - if ((req.flags & GET_TEXT_WITH_STYLES) != 0) { - extract.text = new SpannableString(editable); - } else { - extract.text = editable.toString(); - } - return extract; - } - - private View getView() { - return mView; - } - - private InputMethodManager getInputMethodManager() { - View view = getView(); - if (view == null) { - return null; - } - Context context = view.getContext(); - return InputMethods.getInputMethodManager(context); - } - - private void showSoftInput() { - if (mSoftInputReentrancyGuard) { - return; - } - final View v = getView(); - final InputMethodManager imm = getInputMethodManager(); - if (v == null || imm == null) { - return; - } - - v.post(new Runnable() { - @Override - public void run() { - if (v.hasFocus() && !imm.isActive(v)) { - // Marshmallow workaround: The view has focus but it is not the active - // view for the input method. (Bug 1211848) - v.clearFocus(); - v.requestFocus(); - } - GeckoAppShell.getLayerView().getDynamicToolbarAnimator().showToolbar(/*immediately*/true); - mSoftInputReentrancyGuard = true; - imm.showSoftInput(v, 0); - mSoftInputReentrancyGuard = false; - } - }); - } - - private void hideSoftInput() { - if (mSoftInputReentrancyGuard) { - return; - } - final InputMethodManager imm = getInputMethodManager(); - if (imm != null) { - final View v = getView(); - mSoftInputReentrancyGuard = true; - imm.hideSoftInputFromWindow(v.getWindowToken(), 0); - mSoftInputReentrancyGuard = false; - } - } - - private void restartInput() { - - final InputMethodManager imm = getInputMethodManager(); - if (imm == null) { - return; - } - final View v = getView(); - // InputMethodManager has internal logic to detect if we are restarting input - // in an already focused View, which is the case here because all content text - // fields are inside one LayerView. When this happens, InputMethodManager will - // tell the input method to soft reset instead of hard reset. Stock latin IME - // on Android 4.2+ has a quirk that when it soft resets, it does not clear the - // composition. The following workaround tricks the IME into clearing the - // composition when soft resetting. - if (InputMethods.needsSoftResetWorkaround(mCurrentInputMethod)) { - // Fake a selection change, because the IME clears the composition when - // the selection changes, even if soft-resetting. Offsets here must be - // different from the previous selection offsets, and -1 seems to be a - // reasonable, deterministic value - notifySelectionChange(-1, -1); - } - try { - imm.restartInput(v); - } catch (RuntimeException e) { - Log.e(LOGTAG, "Error restarting input", e); - } - } - - private void resetInputConnection() { - if (mBatchEditCount != 0) { - Log.w(LOGTAG, "resetting with mBatchEditCount = " + mBatchEditCount); - mBatchEditCount = 0; - } - - // Do not reset mIMEState here; see comments in notifyIMEContext - - restartInput(); - } - - @Override // GeckoEditableListener - public void onTextChange() { - - if (mUpdateRequest == null) { - return; - } - - final InputMethodManager imm = getInputMethodManager(); - final View v = getView(); - final Editable editable = getEditable(); - if (imm == null || v == null || editable == null) { - return; - } - mUpdateExtract.flags = 0; - // Update the entire Editable range - mUpdateExtract.partialStartOffset = -1; - mUpdateExtract.partialEndOffset = -1; - mUpdateExtract.selectionStart = Selection.getSelectionStart(editable); - mUpdateExtract.selectionEnd = Selection.getSelectionEnd(editable); - mUpdateExtract.startOffset = 0; - if ((mUpdateRequest.flags & GET_TEXT_WITH_STYLES) != 0) { - mUpdateExtract.text = new SpannableString(editable); - } else { - mUpdateExtract.text = editable.toString(); - } - imm.updateExtractedText(v, mUpdateRequest.token, mUpdateExtract); - } - - @Override // GeckoEditableListener - public void onSelectionChange() { - - final Editable editable = getEditable(); - if (editable != null) { - notifySelectionChange(Selection.getSelectionStart(editable), - Selection.getSelectionEnd(editable)); - } - } - - private void notifySelectionChange(int start, int end) { - - final InputMethodManager imm = getInputMethodManager(); - final View v = getView(); - final Editable editable = getEditable(); - if (imm == null || v == null || editable == null) { - return; - } - imm.updateSelection(v, start, end, getComposingSpanStart(editable), - getComposingSpanEnd(editable)); - } - - @Override - public void updateCompositionRects(final RectF[] aRects) { - if (!Versions.feature21Plus) { - return; - } - - if (mCursorAnchorInfoBuilder == null) { - mCursorAnchorInfoBuilder = new CursorAnchorInfo.Builder(); - } - mCursorAnchorInfoBuilder.reset(); - - // Calculate Gecko logical coords to screen coords - final View v = getView(); - if (v == null) { - return; - } - - int[] viewCoords = new int[2]; - v.getLocationOnScreen(viewCoords); - - DynamicToolbarAnimator animator = GeckoAppShell.getLayerView().getDynamicToolbarAnimator(); - float toolbarHeight = animator.getMaxTranslation() - animator.getToolbarTranslation(); - - Matrix matrix = GeckoAppShell.getLayerView().getMatrixForLayerRectToViewRect(); - if (matrix == null) { - if (DEBUG) { - Log.d(LOGTAG, "Cannot get Matrix to convert from Gecko coords to layer view coords"); - } - return; - } - matrix.postTranslate(viewCoords[0], viewCoords[1] + toolbarHeight); - mCursorAnchorInfoBuilder.setMatrix(matrix); - - final Editable content = getEditable(); - if (content == null) { - return; - } - int composingStart = getComposingSpanStart(content); - int composingEnd = getComposingSpanEnd(content); - if (composingStart < 0 || composingEnd < 0) { - if (DEBUG) { - Log.d(LOGTAG, "No composition for updates"); - } - return; - } - - for (int i = 0; i < aRects.length; i++) { - mCursorAnchorInfoBuilder.addCharacterBounds(i, - aRects[i].left, - aRects[i].top, - aRects[i].right, - aRects[i].bottom, - CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION); - } - - mCursorAnchorInfoBuilder.setComposingText(0, content.subSequence(composingStart, composingEnd)); - - updateCursor(); - } - - @TargetApi(21) - private void updateCursor() { - if (mCursorAnchorInfoBuilder == null) { - return; - } - - final InputMethodManager imm = getInputMethodManager(); - final View v = getView(); - if (imm == null || v == null) { - return; - } - - imm.updateCursorAnchorInfo(v, mCursorAnchorInfoBuilder.build()); - } - - @Override - public boolean requestCursorUpdates(int cursorUpdateMode) { - - if ((cursorUpdateMode & InputConnection.CURSOR_UPDATE_IMMEDIATE) != 0) { - mEditableClient.requestCursorUpdates(GeckoEditableClient.ONE_SHOT); - } - - if ((cursorUpdateMode & InputConnection.CURSOR_UPDATE_MONITOR) != 0) { - mEditableClient.requestCursorUpdates(GeckoEditableClient.START_MONITOR); - } else { - mEditableClient.requestCursorUpdates(GeckoEditableClient.END_MONITOR); - } - return true; - } - - @Override - public void onDefaultKeyEvent(final KeyEvent event) { - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - GeckoInputConnection.this.performDefaultKeyAction(event); - } - }); - } - - private static synchronized Handler getBackgroundHandler() { - if (sBackgroundHandler != null) { - return sBackgroundHandler; - } - // Don't use GeckoBackgroundThread because Gecko thread may block waiting on - // GeckoBackgroundThread. If we were to use GeckoBackgroundThread, due to IME, - // GeckoBackgroundThread may end up also block waiting on Gecko thread and a - // deadlock occurs - Thread backgroundThread = new Thread(new Runnable() { - @Override - public void run() { - Looper.prepare(); - synchronized (GeckoInputConnection.class) { - sBackgroundHandler = new Handler(); - GeckoInputConnection.class.notify(); - } - Looper.loop(); - // We should never be exiting the thread loop. - throw new IllegalThreadStateException("unreachable code"); - } - }, LOGTAG); - backgroundThread.setDaemon(true); - backgroundThread.start(); - while (sBackgroundHandler == null) { - try { - // wait for new thread to set sBackgroundHandler - GeckoInputConnection.class.wait(); - } catch (InterruptedException e) { - } - } - return sBackgroundHandler; - } - - private boolean canReturnCustomHandler() { - if (mIMEState == IME_STATE_DISABLED) { - return false; - } - for (StackTraceElement frame : Thread.currentThread().getStackTrace()) { - // We only return our custom Handler to InputMethodManager's InputConnection - // proxy. For all other purposes, we return the regular Handler. - // InputMethodManager retrieves the Handler for its InputConnection proxy - // inside its method startInputInner(), so we check for that here. This is - // valid from Android 2.2 to at least Android 4.2. If this situation ever - // changes, we gracefully fall back to using the regular Handler. - if ("startInputInner".equals(frame.getMethodName()) && - "android.view.inputmethod.InputMethodManager".equals(frame.getClassName())) { - // only return our own Handler to InputMethodManager - return true; - } - if (CUSTOM_HANDLER_TEST_METHOD.equals(frame.getMethodName()) && - CUSTOM_HANDLER_TEST_CLASS.equals(frame.getClassName())) { - // InputConnection tests should also run on the custom handler - return true; - } - } - return false; - } - - private boolean isPhysicalKeyboardPresent() { - final View v = getView(); - if (v == null) { - return false; - } - final Configuration config = v.getContext().getResources().getConfiguration(); - return config.keyboard != Configuration.KEYBOARD_NOKEYS; - } - - // Android N: @Override // InputConnection - // We need to suppress lint complaining about the lack override here in the meantime: it wants us to build - // against sdk 24, even though we're using 23, and therefore complains about the lack of override. - // Once we update to 24, we can use the actual override annotation and remove the lint suppression. - @SuppressLint("Override") - public Handler getHandler() { - if (isPhysicalKeyboardPresent()) { - return ThreadUtils.getUiHandler(); - } - - return getBackgroundHandler(); - } - - @Override // InputConnectionListener - public Handler getHandler(Handler defHandler) { - if (!canReturnCustomHandler()) { - return defHandler; - } - - return mEditableClient.setInputConnectionHandler(getHandler()); - } - - @Override - public InputConnection onCreateInputConnection(EditorInfo outAttrs) { - // Some keyboards require us to fill out outAttrs even if we return null. - outAttrs.inputType = InputType.TYPE_CLASS_TEXT; - outAttrs.imeOptions = EditorInfo.IME_ACTION_NONE; - outAttrs.actionLabel = null; - - if (mIMEState == IME_STATE_DISABLED) { - hideSoftInput(); - return null; - } - - if (mIMEState == IME_STATE_PASSWORD || - "password".equalsIgnoreCase(mIMETypeHint)) - outAttrs.inputType |= InputType.TYPE_TEXT_VARIATION_PASSWORD; - else if (mIMEState == IME_STATE_PLUGIN) - outAttrs.inputType = InputType.TYPE_NULL; // "send key events" mode - else if (mIMETypeHint.equalsIgnoreCase("url")) - outAttrs.inputType |= InputType.TYPE_TEXT_VARIATION_URI; - else if (mIMETypeHint.equalsIgnoreCase("email")) - outAttrs.inputType |= InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS; - else if (mIMETypeHint.equalsIgnoreCase("tel")) - outAttrs.inputType = InputType.TYPE_CLASS_PHONE; - else if (mIMETypeHint.equalsIgnoreCase("number") || - mIMETypeHint.equalsIgnoreCase("range")) - outAttrs.inputType = InputType.TYPE_CLASS_NUMBER - | InputType.TYPE_NUMBER_FLAG_SIGNED - | InputType.TYPE_NUMBER_FLAG_DECIMAL; - else if (mIMETypeHint.equalsIgnoreCase("week") || - mIMETypeHint.equalsIgnoreCase("month")) - outAttrs.inputType = InputType.TYPE_CLASS_DATETIME - | InputType.TYPE_DATETIME_VARIATION_DATE; - else if (mIMEModeHint.equalsIgnoreCase("numeric")) - outAttrs.inputType = InputType.TYPE_CLASS_NUMBER | - InputType.TYPE_NUMBER_FLAG_SIGNED | - InputType.TYPE_NUMBER_FLAG_DECIMAL; - else if (mIMEModeHint.equalsIgnoreCase("digit")) - outAttrs.inputType = InputType.TYPE_CLASS_NUMBER; - else { - // TYPE_TEXT_FLAG_IME_MULTI_LINE flag makes the fullscreen IME line wrap - outAttrs.inputType |= InputType.TYPE_TEXT_FLAG_AUTO_CORRECT | - InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE; - if (mIMETypeHint.equalsIgnoreCase("textarea") || - mIMETypeHint.length() == 0) { - // empty mIMETypeHint indicates contentEditable/designMode documents - outAttrs.inputType |= InputType.TYPE_TEXT_FLAG_MULTI_LINE; - } - if (mIMEModeHint.equalsIgnoreCase("uppercase")) - outAttrs.inputType |= InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS; - else if (mIMEModeHint.equalsIgnoreCase("titlecase")) - outAttrs.inputType |= InputType.TYPE_TEXT_FLAG_CAP_WORDS; - else if (mIMETypeHint.equalsIgnoreCase("text") && - !mIMEModeHint.equalsIgnoreCase("autocapitalized")) - outAttrs.inputType |= InputType.TYPE_TEXT_VARIATION_NORMAL; - else if (!mIMEModeHint.equalsIgnoreCase("lowercase")) - outAttrs.inputType |= InputType.TYPE_TEXT_FLAG_CAP_SENTENCES; - // auto-capitalized mode is the default for types other than text - } - - if (mIMEActionHint.equalsIgnoreCase("go")) - outAttrs.imeOptions = EditorInfo.IME_ACTION_GO; - else if (mIMEActionHint.equalsIgnoreCase("done")) - outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE; - else if (mIMEActionHint.equalsIgnoreCase("next")) - outAttrs.imeOptions = EditorInfo.IME_ACTION_NEXT; - else if (mIMEActionHint.equalsIgnoreCase("search") || - mIMETypeHint.equalsIgnoreCase("search")) - outAttrs.imeOptions = EditorInfo.IME_ACTION_SEARCH; - else if (mIMEActionHint.equalsIgnoreCase("send")) - outAttrs.imeOptions = EditorInfo.IME_ACTION_SEND; - else if (mIMEActionHint.length() > 0) { - if (DEBUG) - Log.w(LOGTAG, "Unexpected mIMEActionHint=\"" + mIMEActionHint + "\""); - outAttrs.actionLabel = mIMEActionHint; - } - - Context context = getView().getContext(); - DisplayMetrics metrics = context.getResources().getDisplayMetrics(); - if (Math.min(metrics.widthPixels, metrics.heightPixels) > INLINE_IME_MIN_DISPLAY_SIZE) { - // prevent showing full-screen keyboard only when the screen is tall enough - // to show some reasonable amount of the page (see bug 752709) - outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_EXTRACT_UI - | EditorInfo.IME_FLAG_NO_FULLSCREEN; - } - - if (DEBUG) { - Log.d(LOGTAG, "mapped IME states to: inputType = " + - Integer.toHexString(outAttrs.inputType) + ", imeOptions = " + - Integer.toHexString(outAttrs.imeOptions)); - } - - String prevInputMethod = mCurrentInputMethod; - mCurrentInputMethod = InputMethods.getCurrentInputMethod(context); - if (DEBUG) { - Log.d(LOGTAG, "IME: CurrentInputMethod=" + mCurrentInputMethod); - } - - if (mIMEState == IME_STATE_PLUGIN) { - // Since we are using a temporary string as the editable, the selection is at 0 - outAttrs.initialSelStart = 0; - outAttrs.initialSelEnd = 0; - return mKeyInputConnection; - } - Editable editable = getEditable(); - outAttrs.initialSelStart = Selection.getSelectionStart(editable); - outAttrs.initialSelEnd = Selection.getSelectionEnd(editable); - - showSoftInput(); - return this; - } - - private boolean replaceComposingSpanWithSelection() { - final Editable content = getEditable(); - if (content == null) { - return false; - } - int a = getComposingSpanStart(content), - b = getComposingSpanEnd(content); - if (a != -1 && b != -1) { - if (DEBUG) { - Log.d(LOGTAG, "removing composition at " + a + "-" + b); - } - removeComposingSpans(content); - Selection.setSelection(content, a, b); - } - return true; - } - - @Override - public boolean commitText(CharSequence text, int newCursorPosition) { - if (InputMethods.shouldCommitCharAsKey(mCurrentInputMethod) && - text.length() == 1 && newCursorPosition > 0) { - if (DEBUG) { - Log.d(LOGTAG, "committing \"" + text + "\" as key"); - } - // mKeyInputConnection is a BaseInputConnection that commits text as keys; - // but we first need to replace any composing span with a selection, - // so that the new key events will generate characters to replace - // text from the old composing span - return replaceComposingSpanWithSelection() && - mKeyInputConnection.commitText(text, newCursorPosition); - } - return super.commitText(text, newCursorPosition); - } - - @Override - public boolean setSelection(int start, int end) { - if (start < 0 || end < 0) { - // Some keyboards (e.g. Samsung) can call setSelection with - // negative offsets. In that case we ignore the call, similar to how - // BaseInputConnection.setSelection ignores offsets that go past the length. - return true; - } - return super.setSelection(start, end); - } - - /* package */ void sendKeyEvent(final int action, KeyEvent event) { - final Editable editable = getEditable(); - if (editable == null) { - return; - } - - final KeyListener keyListener = TextKeyListener.getInstance(); - event = translateKey(event.getKeyCode(), event); - - // We only let TextKeyListener do UI things on the UI thread. - final View v = ThreadUtils.isOnUiThread() ? getView() : null; - final int keyCode = event.getKeyCode(); - final boolean handled; - - if (shouldSkipKeyListener(keyCode, event)) { - handled = false; - } else if (action == KeyEvent.ACTION_DOWN) { - mEditableClient.setSuppressKeyUp(true); - handled = keyListener.onKeyDown(v, editable, keyCode, event); - } else if (action == KeyEvent.ACTION_UP) { - handled = keyListener.onKeyUp(v, editable, keyCode, event); - } else { - handled = keyListener.onKeyOther(v, editable, event); - } - - if (!handled) { - mEditableClient.sendKeyEvent(event, action, TextKeyListener.getMetaState(editable)); - } - - if (action == KeyEvent.ACTION_DOWN) { - if (!handled) { - // Usually, the down key listener call above adjusts meta states for us. - // However, if the call didn't handle the event, we have to manually - // adjust meta states so the meta states remain consistent. - TextKeyListener.adjustMetaAfterKeypress(editable); - } - mEditableClient.setSuppressKeyUp(false); - } - } - - @Override - public boolean sendKeyEvent(KeyEvent event) { - sendKeyEvent(event.getAction(), event); - return false; // seems to always return false - } - - @Override - public boolean onKeyPreIme(int keyCode, KeyEvent event) { - return false; - } - - private boolean shouldProcessKey(int keyCode, KeyEvent event) { - switch (keyCode) { - case KeyEvent.KEYCODE_MENU: - case KeyEvent.KEYCODE_BACK: - case KeyEvent.KEYCODE_VOLUME_UP: - case KeyEvent.KEYCODE_VOLUME_DOWN: - case KeyEvent.KEYCODE_SEARCH: - // ignore HEADSETHOOK to allow hold-for-voice-search to work - case KeyEvent.KEYCODE_HEADSETHOOK: - return false; - } - return true; - } - - private boolean shouldSkipKeyListener(int keyCode, KeyEvent event) { - if (mIMEState == IME_STATE_DISABLED || - mIMEState == IME_STATE_PLUGIN) { - return true; - } - // Preserve enter and tab keys for the browser - if (keyCode == KeyEvent.KEYCODE_ENTER || - keyCode == KeyEvent.KEYCODE_TAB) { - return true; - } - // BaseKeyListener returns false even if it handled these keys for us, - // so we skip the key listener entirely and handle these ourselves - if (keyCode == KeyEvent.KEYCODE_DEL || - keyCode == KeyEvent.KEYCODE_FORWARD_DEL) { - return true; - } - return false; - } - - private KeyEvent translateKey(int keyCode, KeyEvent event) { - switch (keyCode) { - case KeyEvent.KEYCODE_ENTER: - if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 && - mIMEActionHint.equalsIgnoreCase("next")) { - return new KeyEvent(event.getAction(), KeyEvent.KEYCODE_TAB); - } - break; - } - - if (GamepadUtils.isSonyXperiaGamepadKeyEvent(event)) { - return GamepadUtils.translateSonyXperiaGamepadKeys(keyCode, event); - } - - return event; - } - - // Called by OnDefaultKeyEvent handler, up from Gecko - /* package */ void performDefaultKeyAction(KeyEvent event) { - switch (event.getKeyCode()) { - case KeyEvent.KEYCODE_MUTE: - case KeyEvent.KEYCODE_HEADSETHOOK: - case KeyEvent.KEYCODE_MEDIA_PLAY: - case KeyEvent.KEYCODE_MEDIA_PAUSE: - case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: - case KeyEvent.KEYCODE_MEDIA_STOP: - case KeyEvent.KEYCODE_MEDIA_NEXT: - case KeyEvent.KEYCODE_MEDIA_PREVIOUS: - case KeyEvent.KEYCODE_MEDIA_REWIND: - case KeyEvent.KEYCODE_MEDIA_RECORD: - case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: - case KeyEvent.KEYCODE_MEDIA_CLOSE: - case KeyEvent.KEYCODE_MEDIA_EJECT: - case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: - // Forward media keypresses to the registered handler so headset controls work - // Does the same thing as Chromium - // https://chromium.googlesource.com/chromium/src/+/49.0.2623.67/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsDelegateAndroid.java#445 - // These are all the keys dispatchMediaKeyEvent supports. - if (AppConstants.Versions.feature19Plus) { - // dispatchMediaKeyEvent is only available on Android 4.4+ - Context viewContext = getView().getContext(); - AudioManager am = (AudioManager)viewContext.getSystemService(Context.AUDIO_SERVICE); - am.dispatchMediaKeyEvent(event); - } - break; - } - } - - private boolean processKey(final int action, final int keyCode, final KeyEvent event) { - - if (keyCode > KeyEvent.getMaxKeyCode() || !shouldProcessKey(keyCode, event)) { - return false; - } - - mEditableClient.postToInputConnection(new Runnable() { - @Override - public void run() { - sendKeyEvent(action, event); - } - }); - return true; - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - return processKey(KeyEvent.ACTION_DOWN, keyCode, event); - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - return processKey(KeyEvent.ACTION_UP, keyCode, event); - } - - /** - * Get a key that represents a given character. - */ - private KeyEvent getCharKeyEvent(final char c) { - final long time = SystemClock.uptimeMillis(); - return new KeyEvent(time, time, KeyEvent.ACTION_MULTIPLE, - KeyEvent.KEYCODE_UNKNOWN, /* repeat */ 0) { - @Override - public int getUnicodeChar() { - return c; - } - - @Override - public int getUnicodeChar(int metaState) { - return c; - } - }; - } - - @Override - public boolean onKeyMultiple(int keyCode, int repeatCount, final KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_UNKNOWN) { - // KEYCODE_UNKNOWN means the characters are in KeyEvent.getCharacters() - final String str = event.getCharacters(); - for (int i = 0; i < str.length(); i++) { - final KeyEvent charEvent = getCharKeyEvent(str.charAt(i)); - if (!processKey(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_UNKNOWN, charEvent) || - !processKey(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_UNKNOWN, charEvent)) { - return false; - } - } - return true; - } - - while ((repeatCount--) != 0) { - if (!processKey(KeyEvent.ACTION_DOWN, keyCode, event) || - !processKey(KeyEvent.ACTION_UP, keyCode, event)) { - return false; - } - } - return true; - } - - @Override - public boolean onKeyLongPress(int keyCode, KeyEvent event) { - View v = getView(); - switch (keyCode) { - case KeyEvent.KEYCODE_MENU: - InputMethodManager imm = getInputMethodManager(); - imm.toggleSoftInputFromWindow(v.getWindowToken(), - InputMethodManager.SHOW_FORCED, 0); - return true; - default: - break; - } - return false; - } - - @Override - public boolean isIMEEnabled() { - // make sure this picks up PASSWORD and PLUGIN states as well - return mIMEState != IME_STATE_DISABLED; - } - - @Override - public void notifyIME(int type) { - switch (type) { - - case NOTIFY_IME_OF_FOCUS: - // Showing/hiding vkb is done in notifyIMEContext - mFocused = true; - resetInputConnection(); - break; - - case NOTIFY_IME_OF_BLUR: - // Showing/hiding vkb is done in notifyIMEContext - mFocused = false; - break; - - case NOTIFY_IME_OPEN_VKB: - showSoftInput(); - break; - - default: - if (DEBUG) { - throw new IllegalArgumentException("Unexpected NOTIFY_IME=" + type); - } - break; - } - } - - @Override - public void notifyIMEContext(int state, String typeHint, String modeHint, String actionHint) { - // For some input type we will use a widget to display the ui, for those we must not - // display the ime. We can display a widget for date and time types and, if the sdk version - // is 11 or greater, for datetime/month/week as well. - if (typeHint != null && - (typeHint.equalsIgnoreCase("date") || - typeHint.equalsIgnoreCase("time") || - typeHint.equalsIgnoreCase("datetime") || - typeHint.equalsIgnoreCase("month") || - typeHint.equalsIgnoreCase("week") || - typeHint.equalsIgnoreCase("datetime-local"))) { - state = IME_STATE_DISABLED; - } - - // mIMEState and the mIME*Hint fields should only be changed by notifyIMEContext, - // and not reset anywhere else. Usually, notifyIMEContext is called right after a - // focus or blur, so resetting mIMEState during the focus or blur seems harmless. - // However, this behavior is not guaranteed. Gecko may call notifyIMEContext - // independent of focus change; that is, a focus change may not be accompanied by - // a notifyIMEContext call. So if we reset mIMEState inside focus, there may not - // be another notifyIMEContext call to set mIMEState to a proper value (bug 829318) - /* When IME is 'disabled', IME processing is disabled. - In addition, the IME UI is hidden */ - mIMEState = state; - mIMETypeHint = (typeHint == null) ? "" : typeHint; - mIMEModeHint = (modeHint == null) ? "" : modeHint; - mIMEActionHint = (actionHint == null) ? "" : actionHint; - - // These fields are reset here and will be updated when restartInput is called below - mUpdateRequest = null; - mCurrentInputMethod = ""; - - View v = getView(); - if (v == null || !v.hasFocus()) { - // When using Find In Page, we can still receive notifyIMEContext calls due to the - // selection changing when highlighting. However in this case we don't want to reset/ - // show/hide the keyboard because the find box has the focus and is taking input from - // the keyboard. - return; - } - - // On focus, the notifyIMEContext call comes *before* the - // notifyIME(NOTIFY_IME_OF_FOCUS) call, but we need to call restartInput during - // notifyIME, so we skip restartInput here. On blur, the notifyIMEContext call - // comes *after* the notifyIME(NOTIFY_IME_OF_BLUR) call, and we need to call - // restartInput here. - if (mIMEState == IME_STATE_DISABLED || mFocused) { - restartInput(); - } - } -} - -final class DebugGeckoInputConnection - extends GeckoInputConnection - implements InvocationHandler { - - private InputConnection mProxy; - private final StringBuilder mCallLevel; - - private DebugGeckoInputConnection(View targetView, - GeckoEditableClient editable) { - super(targetView, editable); - mCallLevel = new StringBuilder(); - } - - public static GeckoEditableListener create(View targetView, - GeckoEditableClient editable) { - final Class<?>[] PROXY_INTERFACES = { InputConnection.class, - InputConnectionListener.class, - GeckoEditableListener.class }; - DebugGeckoInputConnection dgic = - new DebugGeckoInputConnection(targetView, editable); - dgic.mProxy = (InputConnection)Proxy.newProxyInstance( - GeckoInputConnection.class.getClassLoader(), - PROXY_INTERFACES, dgic); - return (GeckoEditableListener)dgic.mProxy; - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) - throws Throwable { - - StringBuilder log = new StringBuilder(mCallLevel); - log.append("> ").append(method.getName()).append("("); - if (args != null) { - for (Object arg : args) { - // translate argument values to constant names - if ("notifyIME".equals(method.getName()) && arg == args[0]) { - log.append(GeckoEditable.getConstantName( - GeckoEditableListener.class, "NOTIFY_IME_", arg)); - } else if ("notifyIMEContext".equals(method.getName()) && arg == args[0]) { - log.append(GeckoEditable.getConstantName( - GeckoEditableListener.class, "IME_STATE_", arg)); - } else { - GeckoEditable.debugAppend(log, arg); - } - log.append(", "); - } - if (args.length > 0) { - log.setLength(log.length() - 2); - } - } - log.append(")"); - Log.d(LOGTAG, log.toString()); - - mCallLevel.append(' '); - Object ret = method.invoke(this, args); - if (ret == this) { - ret = mProxy; - } - mCallLevel.setLength(Math.max(0, mCallLevel.length() - 1)); - - log.setLength(mCallLevel.length()); - log.append("< ").append(method.getName()); - if (!method.getReturnType().equals(Void.TYPE)) { - GeckoEditable.debugAppend(log.append(": "), ret); - } - Log.d(LOGTAG, log.toString()); - return ret; - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoNetworkManager.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoNetworkManager.java deleted file mode 100644 index 0cb56a7d2..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoNetworkManager.java +++ /dev/null @@ -1,491 +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; - -import org.mozilla.gecko.annotation.JNITarget; -import org.mozilla.gecko.annotation.WrapForJNI; -import org.mozilla.gecko.util.NativeEventListener; -import org.mozilla.gecko.util.NativeJSObject; -import org.mozilla.gecko.util.EventCallback; -import org.mozilla.gecko.util.NetworkUtils; -import org.mozilla.gecko.util.NetworkUtils.ConnectionSubType; -import org.mozilla.gecko.util.NetworkUtils.ConnectionType; -import org.mozilla.gecko.util.NetworkUtils.NetworkStatus; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.ConnectivityManager; -import android.net.DhcpInfo; -import android.net.wifi.WifiInfo; -import android.net.wifi.WifiManager; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.telephony.TelephonyManager; -import android.text.format.Formatter; -import android.util.Log; - -/** - * Provides connection type, subtype and general network status (up/down). - * - * According to spec of Network Information API version 3, connection types include: - * bluetooth, cellular, ethernet, none, wifi and other. The objective of providing such general - * connection is due to some security concerns. In short, we don't want to expose exact network type, - * especially the cellular network type. - * - * Specific mobile subtypes are mapped to general 2G, 3G and 4G buckets. - * - * Logic is implemented as a state machine, so see the transition matrix to figure out what happens when. - * This class depends on access to the context, so only use after GeckoAppShell has been initialized. - */ -public class GeckoNetworkManager extends BroadcastReceiver implements NativeEventListener { - private static final String LOGTAG = "GeckoNetworkManager"; - - private static final String LINK_DATA_CHANGED = "changed"; - - private static GeckoNetworkManager instance; - - // We hackishly (yet harmlessly, in this case) keep a Context reference passed in via the start method. - // See context handling notes in handleManagerEvent, and Bug 1277333. - private Context context; - - public static void destroy() { - if (instance != null) { - instance.onDestroy(); - instance = null; - } - } - - public enum ManagerState { - OffNoListeners, - OffWithListeners, - OnNoListeners, - OnWithListeners - } - - public enum ManagerEvent { - start, - stop, - enableNotifications, - disableNotifications, - receivedUpdate - } - - private ManagerState currentState = ManagerState.OffNoListeners; - private ConnectionType currentConnectionType = ConnectionType.NONE; - private ConnectionType previousConnectionType = ConnectionType.NONE; - private ConnectionSubType currentConnectionSubtype = ConnectionSubType.UNKNOWN; - private ConnectionSubType previousConnectionSubtype = ConnectionSubType.UNKNOWN; - private NetworkStatus currentNetworkStatus = NetworkStatus.UNKNOWN; - private NetworkStatus previousNetworkStatus = NetworkStatus.UNKNOWN; - - private enum InfoType { - MCC, - MNC - } - - private GeckoNetworkManager() { - EventDispatcher.getInstance().registerGeckoThreadListener(this, - "Wifi:Enable", - "Wifi:GetIPAddress"); - } - - private void onDestroy() { - handleManagerEvent(ManagerEvent.stop); - EventDispatcher.getInstance().unregisterGeckoThreadListener(this, - "Wifi:Enable", - "Wifi:GetIPAddress"); - } - - public static GeckoNetworkManager getInstance() { - if (instance == null) { - instance = new GeckoNetworkManager(); - } - - return instance; - } - - public double[] getCurrentInformation() { - final Context applicationContext = GeckoAppShell.getApplicationContext(); - final ConnectionType connectionType = currentConnectionType; - return new double[] { - connectionType.value, - connectionType == ConnectionType.WIFI ? 1.0 : 0.0, - connectionType == ConnectionType.WIFI ? wifiDhcpGatewayAddress(applicationContext) : 0.0 - }; - } - - @Override - public void onReceive(Context aContext, Intent aIntent) { - handleManagerEvent(ManagerEvent.receivedUpdate); - } - - public void start(final Context context) { - this.context = context; - handleManagerEvent(ManagerEvent.start); - } - - public void stop() { - handleManagerEvent(ManagerEvent.stop); - } - - public void enableNotifications() { - handleManagerEvent(ManagerEvent.enableNotifications); - } - - public void disableNotifications() { - handleManagerEvent(ManagerEvent.disableNotifications); - } - - /** - * For a given event, figure out the next state, run any transition by-product actions, and switch - * current state to the next state. If event is invalid for the current state, this is a no-op. - * - * @param event Incoming event - * @return Boolean indicating if transition was performed. - */ - private synchronized boolean handleManagerEvent(ManagerEvent event) { - final ManagerState nextState = getNextState(currentState, event); - - Log.d(LOGTAG, "Incoming event " + event + " for state " + currentState + " -> " + nextState); - if (nextState == null) { - Log.w(LOGTAG, "Invalid event " + event + " for state " + currentState); - return false; - } - - // We're being deliberately careful about handling context here; it's possible that in some - // rare cases and possibly related to timing of when this is called (seems to be early in the startup phase), - // GeckoAppShell.getApplicationContext() will be null, and .start() wasn't called yet, - // so we don't have a local Context reference either. If both of these are true, we have to drop the event. - // NB: this is hacky (and these checks attempt to isolate the hackiness), and root cause - // seems to be how this class fits into the larger ecosystem and general flow of events. - // See Bug 1277333. - final Context contextForAction; - if (context != null) { - contextForAction = context; - } else { - contextForAction = GeckoAppShell.getApplicationContext(); - } - - if (contextForAction == null) { - Log.w(LOGTAG, "Context is not available while processing event " + event + " for state " + currentState); - return false; - } - - performActionsForStateEvent(contextForAction, currentState, event); - currentState = nextState; - - return true; - } - - /** - * Defines a transition matrix for our state machine. For a given state/event pair, returns nextState. - * - * @param currentState Current state against which we have an incoming event - * @param event Incoming event for which we'd like to figure out the next state - * @return State into which we should transition as result of given event - */ - @Nullable - public static ManagerState getNextState(@NonNull ManagerState currentState, @NonNull ManagerEvent event) { - switch (currentState) { - case OffNoListeners: - switch (event) { - case start: - return ManagerState.OnNoListeners; - case enableNotifications: - return ManagerState.OffWithListeners; - default: - return null; - } - case OnNoListeners: - switch (event) { - case stop: - return ManagerState.OffNoListeners; - case enableNotifications: - return ManagerState.OnWithListeners; - case receivedUpdate: - return ManagerState.OnNoListeners; - default: - return null; - } - case OnWithListeners: - switch (event) { - case stop: - return ManagerState.OffWithListeners; - case disableNotifications: - return ManagerState.OnNoListeners; - case receivedUpdate: - return ManagerState.OnWithListeners; - default: - return null; - } - case OffWithListeners: - switch (event) { - case start: - return ManagerState.OnWithListeners; - case disableNotifications: - return ManagerState.OffNoListeners; - default: - return null; - } - default: - throw new IllegalStateException("Unknown current state: " + currentState.name()); - } - } - - /** - * For a given state/event combination, run any actions which are by-products of leaving the state - * because of a given event. Since this is a deterministic state machine, we can easily do that - * without any additional information. - * - * @param currentState State which we are leaving - * @param event Event which is causing us to leave the state - */ - private void performActionsForStateEvent(final Context context, final ManagerState currentState, final ManagerEvent event) { - // NB: network state might be queried via getCurrentInformation at any time; pre-rewrite behaviour was - // that network state was updated whenever enableNotifications was called. To avoid deviating - // from previous behaviour and causing weird side-effects, we call updateNetworkStateAndConnectionType - // whenever notifications are enabled. - switch (currentState) { - case OffNoListeners: - if (event == ManagerEvent.start) { - updateNetworkStateAndConnectionType(context); - registerBroadcastReceiver(context, this); - } - if (event == ManagerEvent.enableNotifications) { - updateNetworkStateAndConnectionType(context); - } - break; - case OnNoListeners: - if (event == ManagerEvent.receivedUpdate) { - updateNetworkStateAndConnectionType(context); - sendNetworkStateToListeners(context); - } - if (event == ManagerEvent.enableNotifications) { - updateNetworkStateAndConnectionType(context); - registerBroadcastReceiver(context, this); - } - if (event == ManagerEvent.stop) { - unregisterBroadcastReceiver(context, this); - } - break; - case OnWithListeners: - if (event == ManagerEvent.receivedUpdate) { - updateNetworkStateAndConnectionType(context); - sendNetworkStateToListeners(context); - } - if (event == ManagerEvent.stop) { - unregisterBroadcastReceiver(context, this); - } - /* no-op event: ManagerEvent.disableNotifications */ - break; - case OffWithListeners: - if (event == ManagerEvent.start) { - registerBroadcastReceiver(context, this); - } - /* no-op event: ManagerEvent.disableNotifications */ - break; - default: - throw new IllegalStateException("Unknown current state: " + currentState.name()); - } - } - - /** - * Update current network state and connection types. - */ - private void updateNetworkStateAndConnectionType(final Context context) { - final ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService( - Context.CONNECTIVITY_SERVICE); - // Type/status getters below all have a defined behaviour for when connectivityManager == null - if (connectivityManager == null) { - Log.e(LOGTAG, "ConnectivityManager does not exist."); - } - currentConnectionType = NetworkUtils.getConnectionType(connectivityManager); - currentNetworkStatus = NetworkUtils.getNetworkStatus(connectivityManager); - currentConnectionSubtype = NetworkUtils.getConnectionSubType(connectivityManager); - Log.d(LOGTAG, "New network state: " + currentNetworkStatus + ", " + currentConnectionType + ", " + currentConnectionSubtype); - } - - @WrapForJNI(dispatchTo = "gecko") - private static native void onConnectionChanged(int type, String subType, - boolean isWifi, int DHCPGateway); - - @WrapForJNI(dispatchTo = "gecko") - private static native void onStatusChanged(String status); - - /** - * Send current network state and connection type to whomever is listening. - */ - private void sendNetworkStateToListeners(final Context context) { - if (currentConnectionType != previousConnectionType || - currentConnectionSubtype != previousConnectionSubtype) { - previousConnectionType = currentConnectionType; - previousConnectionSubtype = currentConnectionSubtype; - - final boolean isWifi = currentConnectionType == ConnectionType.WIFI; - final int gateway = !isWifi ? 0 : - wifiDhcpGatewayAddress(context); - - if (GeckoThread.isRunning()) { - onConnectionChanged(currentConnectionType.value, - currentConnectionSubtype.value, isWifi, gateway); - } else { - GeckoThread.queueNativeCall(GeckoNetworkManager.class, "onConnectionChanged", - currentConnectionType.value, - String.class, currentConnectionSubtype.value, - isWifi, gateway); - } - } - - final String status; - - if (currentNetworkStatus != previousNetworkStatus) { - previousNetworkStatus = currentNetworkStatus; - status = currentNetworkStatus.value; - } else { - status = LINK_DATA_CHANGED; - } - - if (GeckoThread.isRunning()) { - onStatusChanged(status); - } else { - GeckoThread.queueNativeCall(GeckoNetworkManager.class, "onStatusChanged", - String.class, status); - } - } - - /** - * Stop listening for network state updates. - */ - private static void unregisterBroadcastReceiver(final Context context, final BroadcastReceiver receiver) { - context.unregisterReceiver(receiver); - } - - /** - * Start listening for network state updates. - */ - private static void registerBroadcastReceiver(final Context context, final BroadcastReceiver receiver) { - final IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); - context.registerReceiver(receiver, filter); - } - - private static int wifiDhcpGatewayAddress(final Context context) { - if (context == null) { - return 0; - } - - try { - WifiManager mgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); - DhcpInfo d = mgr.getDhcpInfo(); - if (d == null) { - return 0; - } - - return d.gateway; - - } catch (Exception ex) { - // getDhcpInfo() is not documented to require any permissions, but on some devices - // requires android.permission.ACCESS_WIFI_STATE. Just catch the generic exception - // here and returning 0. Not logging because this could be noisy. - return 0; - } - } - - @Override - /** - * Handles native messages, not part of the state machine flow. - */ - public void handleMessage(final String event, final NativeJSObject message, - final EventCallback callback) { - final Context applicationContext = GeckoAppShell.getApplicationContext(); - switch (event) { - case "Wifi:Enable": - final WifiManager mgr = (WifiManager) applicationContext.getSystemService(Context.WIFI_SERVICE); - - if (!mgr.isWifiEnabled()) { - mgr.setWifiEnabled(true); - } else { - // If Wifi is enabled, maybe you need to select a network - Intent intent = new Intent(android.provider.Settings.ACTION_WIFI_SETTINGS); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - applicationContext.startActivity(intent); - } - break; - case "Wifi:GetIPAddress": - getWifiIPAddress(callback); - break; - } - } - - // This function only works for IPv4; not part of the state machine flow. - private void getWifiIPAddress(final EventCallback callback) { - final WifiManager mgr = (WifiManager) GeckoAppShell.getApplicationContext().getSystemService(Context.WIFI_SERVICE); - - if (mgr == null) { - callback.sendError("Cannot get WifiManager"); - return; - } - - final WifiInfo info = mgr.getConnectionInfo(); - if (info == null) { - callback.sendError("Cannot get connection info"); - return; - } - - int ip = info.getIpAddress(); - if (ip == 0) { - callback.sendError("Cannot get IPv4 address"); - return; - } - callback.sendSuccess(Formatter.formatIpAddress(ip)); - } - - private static int getNetworkOperator(InfoType type, Context context) { - if (null == context) { - return -1; - } - - TelephonyManager tel = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - if (tel == null) { - Log.e(LOGTAG, "Telephony service does not exist"); - return -1; - } - - String networkOperator = tel.getNetworkOperator(); - if (networkOperator == null || networkOperator.length() <= 3) { - return -1; - } - - if (type == InfoType.MNC) { - return Integer.parseInt(networkOperator.substring(3)); - } - - if (type == InfoType.MCC) { - return Integer.parseInt(networkOperator.substring(0, 3)); - } - - return -1; - } - - /** - * These are called from JavaScript ctypes. Avoid letting ProGuard delete them. - * - * Note that these methods must only be called after GeckoAppShell has been - * initialized: they depend on access to the context. - * - * Not part of the state machine flow. - */ - @JNITarget - public static int getMCC() { - return getNetworkOperator(InfoType.MCC, GeckoAppShell.getApplicationContext()); - } - - @JNITarget - public static int getMNC() { - return getNetworkOperator(InfoType.MNC, GeckoAppShell.getApplicationContext()); - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoProfile.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoProfile.java deleted file mode 100644 index 27ec4f1dd..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoProfile.java +++ /dev/null @@ -1,1002 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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; - -import android.app.Activity; -import android.content.Context; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.support.annotation.WorkerThread; -import android.text.TextUtils; -import android.util.Log; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException; -import org.mozilla.gecko.GeckoProfileDirectories.NoSuchProfileException; -import org.mozilla.gecko.annotation.RobocopTarget; -import org.mozilla.gecko.util.FileUtils; -import org.mozilla.gecko.util.INIParser; -import org.mozilla.gecko.util.INISection; -import org.mozilla.gecko.util.IntentUtils; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.nio.charset.Charset; -import java.util.Enumeration; -import java.util.Hashtable; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public final class GeckoProfile { - private static final String LOGTAG = "GeckoProfile"; - - // The path in the profile to the file containing the client ID. - private static final String CLIENT_ID_FILE_PATH = "datareporting/state.json"; - private static final String FHR_CLIENT_ID_FILE_PATH = "healthreport/state.json"; - // In the client ID file, the attribute title in the JSON object containing the client ID value. - private static final String CLIENT_ID_JSON_ATTR = "clientID"; - - private static final String TIMES_PATH = "times.json"; - private static final String PROFILE_CREATION_DATE_JSON_ATTR = "created"; - - // Only tests should need to do this. - // We can default this to AppConstants.RELEASE_OR_BETA once we fix Bug 1069687. - private static volatile boolean sAcceptDirectoryChanges = true; - - @RobocopTarget - public static void enableDirectoryChanges() { - Log.w(LOGTAG, "Directory changes should only be enabled for tests. And even then it's a bad idea."); - sAcceptDirectoryChanges = true; - } - - public static final String DEFAULT_PROFILE = "default"; - // Profile is using a custom directory outside of the Mozilla directory. - public static final String CUSTOM_PROFILE = ""; - - public static final String GUEST_PROFILE_DIR = "guest"; - public static final String GUEST_MODE_PREF = "guestMode"; - - // Session store - private static final String SESSION_FILE = "sessionstore.js"; - private static final String SESSION_FILE_BACKUP = "sessionstore.bak"; - private static final String SESSION_FILE_PREVIOUS = "sessionstore.old"; - private static final long MAX_PREVIOUS_FILE_AGE = 1000 * 3600 * 24; // 24 hours - - private boolean mOldSessionDataProcessed = false; - - private static final ConcurrentHashMap<String, GeckoProfile> sProfileCache = - new ConcurrentHashMap<String, GeckoProfile>( - /* capacity */ 4, /* load factor */ 0.75f, /* concurrency */ 2); - private static String sDefaultProfileName; - - private final String mName; - private final File mMozillaDir; - private final Context mApplicationContext; - - private Object mData; - - /** - * Access to this member should be synchronized to avoid - * races during creation -- particularly between getDir and GeckoView#init. - * - * Not final because this is lazily computed. - */ - private File mProfileDir; - - private Boolean mInGuestMode; - - public static boolean shouldUseGuestMode(final Context context) { - return GeckoSharedPrefs.forApp(context).getBoolean(GUEST_MODE_PREF, false); - } - - public static void enterGuestMode(final Context context) { - GeckoSharedPrefs.forApp(context).edit().putBoolean(GUEST_MODE_PREF, true).commit(); - } - - public static void leaveGuestMode(final Context context) { - GeckoSharedPrefs.forApp(context).edit().putBoolean(GUEST_MODE_PREF, false).commit(); - } - - public static GeckoProfile initFromArgs(final Context context, final String args) { - if (shouldUseGuestMode(context)) { - final GeckoProfile guestProfile = getGuestProfile(context); - if (guestProfile != null) { - return guestProfile; - } - // Failed to create guest profile; leave guest mode. - leaveGuestMode(context); - } - - // We never want to use the guest mode profile concurrently with a normal profile - // -- no syncing to it, no dual-profile usage, nothing. GeckoThread startup with - // a conventional GeckoProfile will cause the guest profile to be deleted and - // guest mode to reset. - if (getGuestDir(context).isDirectory()) { - final GeckoProfile guestProfile = getGuestProfile(context); - if (guestProfile != null) { - removeProfile(context, guestProfile); - } - } - - String profileName = null; - String profilePath = null; - - if (args != null && args.contains("-P")) { - final Pattern p = Pattern.compile("(?:-P\\s*)(\\w*)(\\s*)"); - final Matcher m = p.matcher(args); - if (m.find()) { - profileName = m.group(1); - } - } - - if (args != null && args.contains("-profile")) { - final Pattern p = Pattern.compile("(?:-profile\\s*)(\\S*)(\\s*)"); - final Matcher m = p.matcher(args); - if (m.find()) { - profilePath = m.group(1); - } - } - - if (profileName == null && profilePath == null) { - // Get the default profile for the Activity. - return getDefaultProfile(context); - } - - return GeckoProfile.get(context, profileName, profilePath); - } - - private static GeckoProfile getDefaultProfile(Context context) { - try { - return get(context, getDefaultProfileName(context)); - - } catch (final NoMozillaDirectoryException e) { - // If this failed, we're screwed. - Log.wtf(LOGTAG, "Unable to get default profile name.", e); - throw new RuntimeException(e); - } - } - - public static GeckoProfile get(Context context) { - return get(context, null, (File) null); - } - - public static GeckoProfile get(Context context, String profileName) { - if (profileName != null) { - GeckoProfile profile = sProfileCache.get(profileName); - if (profile != null) - return profile; - } - return get(context, profileName, (File)null); - } - - @RobocopTarget - public static GeckoProfile get(Context context, String profileName, String profilePath) { - File dir = null; - if (!TextUtils.isEmpty(profilePath)) { - dir = new File(profilePath); - if (!dir.exists() || !dir.isDirectory()) { - Log.w(LOGTAG, "requested profile directory missing: " + profilePath); - } - } - return get(context, profileName, dir); - } - - // Note that the profile cache respects only the profile name! - // If the directory changes, the returned GeckoProfile instance will be mutated. - @RobocopTarget - public static GeckoProfile get(Context context, String profileName, File profileDir) { - if (context == null) { - throw new IllegalArgumentException("context must be non-null"); - } - - // Null name? | Null dir? | Returned profile - // ------------------------------------------ - // Yes | Yes | Active profile or default profile. - // No | Yes | Profile with specified name at default dir. - // Yes | No | Custom (anonymous) profile with specified dir. - // No | No | Profile with specified name at specified dir. - - if (profileName == null && profileDir == null) { - // If no profile info was passed in, look for the active profile or a default profile. - final GeckoProfile profile = GeckoThread.getActiveProfile(); - if (profile != null) { - return profile; - } - - final String args; - if (context instanceof Activity) { - args = IntentUtils.getStringExtraSafe(((Activity) context).getIntent(), "args"); - } else { - args = null; - } - - return GeckoProfile.initFromArgs(context, args); - - } else if (profileName == null) { - // If only profile dir was passed in, use custom (anonymous) profile. - profileName = CUSTOM_PROFILE; - - } else if (AppConstants.DEBUG_BUILD) { - Log.v(LOGTAG, "Fetching profile: '" + profileName + "', '" + profileDir + "'"); - } - - // We require the profile dir to exist if specified, so create it here if needed. - final boolean init = profileDir != null && profileDir.mkdirs(); - - // Actually try to look up the profile. - GeckoProfile profile = sProfileCache.get(profileName); - GeckoProfile newProfile = null; - - if (profile == null) { - try { - newProfile = new GeckoProfile(context, profileName, profileDir); - } catch (NoMozillaDirectoryException e) { - // We're unable to do anything sane here. - throw new RuntimeException(e); - } - - profile = sProfileCache.putIfAbsent(profileName, newProfile); - } - - if (profile == null) { - profile = newProfile; - - } else if (profileDir != null) { - // We have an existing profile but was given an alternate directory. - boolean consistent = false; - try { - consistent = profile.mProfileDir != null && - profile.mProfileDir.getCanonicalPath().equals(profileDir.getCanonicalPath()); - } catch (final IOException e) { - } - - if (!consistent) { - if (!sAcceptDirectoryChanges || !profileDir.isDirectory()) { - throw new IllegalStateException( - "Refusing to reuse profile with a different directory."); - } - - if (AppConstants.RELEASE_OR_BETA) { - Log.e(LOGTAG, "Release build trying to switch out profile dir. " + - "This is an error, but let's do what we can."); - } - profile.setDir(profileDir); - } - } - - if (init) { - // Initialize the profile directory if we had to create it. - profile.enqueueInitialization(profileDir); - } - - return profile; - } - - // Currently unused outside of testing. - @RobocopTarget - public static boolean removeProfile(final Context context, final GeckoProfile profile) { - final boolean success = profile.remove(); - - if (success) { - // Clear all shared prefs for the given profile. - GeckoSharedPrefs.forProfileName(context, profile.getName()) - .edit().clear().apply(); - } - - return success; - } - - private static File getGuestDir(final Context context) { - return context.getFileStreamPath(GUEST_PROFILE_DIR); - } - - @RobocopTarget - public static GeckoProfile getGuestProfile(final Context context) { - return get(context, CUSTOM_PROFILE, getGuestDir(context)); - } - - public static boolean isGuestProfile(final Context context, final String profileName, - final File profileDir) { - // Guest profile is just a custom profile with a special path. - if (profileDir == null || !CUSTOM_PROFILE.equals(profileName)) { - return false; - } - - try { - return profileDir.getCanonicalPath().equals(getGuestDir(context).getCanonicalPath()); - } catch (final IOException e) { - return false; - } - } - - private GeckoProfile(Context context, String profileName, File profileDir) throws NoMozillaDirectoryException { - if (profileName == null) { - throw new IllegalArgumentException("Unable to create GeckoProfile for empty profile name."); - } else if (CUSTOM_PROFILE.equals(profileName) && profileDir == null) { - throw new IllegalArgumentException("Custom profile must have a directory"); - } - - mApplicationContext = context.getApplicationContext(); - mName = profileName; - mMozillaDir = GeckoProfileDirectories.getMozillaDirectory(context); - - mProfileDir = profileDir; - if (profileDir != null && !profileDir.isDirectory()) { - throw new IllegalArgumentException("Profile directory must exist if specified."); - } - } - - /** - * Return the custom data object associated with this profile, which was set by the - * previous {@link #setData(Object)} call. This association is valid for the duration - * of the process lifetime. The caller must ensure proper synchronization, typically - * by synchronizing on the object returned by {@link #getLock()}. - * - * The data object is usually a database object that stores per-profile data such as - * page history. However, it can be any other object that needs to maintain - * profile-specific state. - * - * @return Associated data object - */ - public Object getData() { - return mData; - } - - /** - * Associate this profile with a custom data object, which can be retrieved by - * subsequent {@link #getData()} calls. The caller must ensure proper - * synchronization, typically by synchronizing on the object returned by {@link - * #getLock()}. - * - * @param data Custom data object - */ - public void setData(final Object data) { - mData = data; - } - - private void setDir(File dir) { - if (dir != null && dir.exists() && dir.isDirectory()) { - synchronized (this) { - mProfileDir = dir; - mInGuestMode = null; - } - } - } - - @RobocopTarget - public String getName() { - return mName; - } - - public boolean isCustomProfile() { - return CUSTOM_PROFILE.equals(mName); - } - - @RobocopTarget - public boolean inGuestMode() { - if (mInGuestMode == null) { - mInGuestMode = isGuestProfile(GeckoAppShell.getApplicationContext(), - mName, mProfileDir); - } - return mInGuestMode; - } - - /** - * Return an Object that can be used with a synchronized statement to allow - * exclusive access to the profile. - */ - public Object getLock() { - return this; - } - - /** - * Retrieves the directory backing the profile. This method acts - * as a lazy initializer for the GeckoProfile instance. - */ - @RobocopTarget - public synchronized File getDir() { - forceCreateLocked(); - return mProfileDir; - } - - /** - * Forces profile creation. Consider using {@link #getDir()} to initialize the profile instead - it is the - * lazy initializer and, for our code reasoning abilities, we should initialize the profile in one place. - */ - private void forceCreateLocked() { - if (mProfileDir != null) { - return; - } - - try { - // Check if a profile with this name already exists. - try { - mProfileDir = findProfileDir(); - Log.d(LOGTAG, "Found profile dir."); - } catch (NoSuchProfileException noSuchProfile) { - // If it doesn't exist, create it. - mProfileDir = createProfileDir(); - } - } catch (IOException ioe) { - Log.e(LOGTAG, "Error getting profile dir", ioe); - } - } - - public File getFile(String aFile) { - File f = getDir(); - if (f == null) - return null; - - return new File(f, aFile); - } - - /** - * Retrieves the Gecko client ID from the filesystem. If the client ID does not exist, we attempt to migrate and - * persist it from FHR and, if that fails, we attempt to create a new one ourselves. - * - * This method assumes the client ID is located in a file at a hard-coded path within the profile. The format of - * this file is a JSONObject which at the bottom level contains a String -> String mapping containing the client ID. - * - * WARNING: the platform provides a JSM to retrieve the client ID [1] and this would be a - * robust way to access it. However, we don't want to rely on Gecko running in order to get - * the client ID so instead we access the file this module accesses directly. However, it's - * possible the format of this file (and the access calls in the jsm) will change, leaving - * this code to fail. There are tests in TestGeckoProfile to verify the file format but be - * warned: THIS IS NOT FOOLPROOF. - * - * [1]: https://dxr.mozilla.org/mozilla-central/source/toolkit/modules/ClientID.jsm - * - * @throws IOException if the client ID could not be retrieved. - */ - // Mimics ClientID.jsm – _doLoadClientID. - @WorkerThread - public String getClientId() throws IOException { - try { - return getValidClientIdFromDisk(CLIENT_ID_FILE_PATH); - } catch (final IOException e) { - // Avoid log spam: don't log the full Exception w/ the stack trace. - Log.d(LOGTAG, "Could not get client ID - attempting to migrate ID from FHR: " + e.getLocalizedMessage()); - } - - String clientIdToWrite; - try { - clientIdToWrite = getValidClientIdFromDisk(FHR_CLIENT_ID_FILE_PATH); - } catch (final IOException e) { - // Avoid log spam: don't log the full Exception w/ the stack trace. - Log.d(LOGTAG, "Could not migrate client ID from FHR – creating a new one: " + e.getLocalizedMessage()); - clientIdToWrite = generateNewClientId(); - } - - // There is a possibility Gecko is running and the Gecko telemetry implementation decided it's time to generate - // the client ID, writing client ID underneath us. Since it's highly unlikely (e.g. we run in onStart before - // Gecko is started), we don't handle that possibility besides writing the ID and then reading from the file - // again (rather than just returning the value we generated before writing). - // - // In the event it does happen, any discrepancy will be resolved after a restart. In the mean time, both this - // implementation and the Gecko implementation could upload documents with inconsistent IDs. - // - // In any case, if we get an exception, intentionally throw - there's nothing more to do here. - persistClientId(clientIdToWrite); - return getValidClientIdFromDisk(CLIENT_ID_FILE_PATH); - } - - protected static String generateNewClientId() { - return UUID.randomUUID().toString(); - } - - /** - * @return a valid client ID - * @throws IOException if a valid client ID could not be retrieved - */ - @WorkerThread - private String getValidClientIdFromDisk(final String filePath) throws IOException { - final JSONObject obj = readJSONObjectFromFile(filePath); - final String clientId = obj.optString(CLIENT_ID_JSON_ATTR); - if (isClientIdValid(clientId)) { - return clientId; - } - throw new IOException("Received client ID is invalid: " + clientId); - } - - /** - * Persists the given client ID to disk. This will overwrite any existing files. - */ - @WorkerThread - private void persistClientId(final String clientId) throws IOException { - if (!ensureParentDirs(CLIENT_ID_FILE_PATH)) { - throw new IOException("Could not create client ID parent directories"); - } - - final JSONObject obj = new JSONObject(); - try { - obj.put(CLIENT_ID_JSON_ATTR, clientId); - } catch (final JSONException e) { - throw new IOException("Could not create client ID JSON object", e); - } - - // ClientID.jsm overwrites the file to store the client ID so it's okay if we do it too. - Log.d(LOGTAG, "Attempting to write new client ID"); - writeFile(CLIENT_ID_FILE_PATH, obj.toString()); // Logs errors within function: ideally we'd throw. - } - - // From ClientID.jsm - isValidClientID. - public static boolean isClientIdValid(final String clientId) { - // We could use UUID.fromString but, for consistency, we take the implementation from ClientID.jsm. - if (TextUtils.isEmpty(clientId)) { - return false; - } - return clientId.matches("(?i:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})"); - } - - /** - * Gets the profile creation date and persists it if it had to be generated. - * - * To get this value, we first look in times.json. If that could not be accessed, we - * return the package's first install date. This is not a perfect solution because a - * user may have large gap between install time and first use. - * - * A more correct algorithm could be the one performed by the JS code in ProfileAge.jsm - * getOldestProfileTimestamp: walk the tree and return the oldest timestamp on the files - * within the profile. However, since times.json will only not exist for the small - * number of really old profiles, we're okay with the package install date compromise for - * simplicity. - * - * @return the profile creation date in the format returned by {@link System#currentTimeMillis()} - * or -1 if the value could not be persisted. - */ - @WorkerThread - public long getAndPersistProfileCreationDate(final Context context) { - try { - return getProfileCreationDateFromTimesFile(); - } catch (final IOException e) { - Log.d(LOGTAG, "Unable to retrieve profile creation date from times.json. Getting from system..."); - final long packageInstallMillis = org.mozilla.gecko.util.ContextUtils.getCurrentPackageInfo(context).firstInstallTime; - try { - persistProfileCreationDateToTimesFile(packageInstallMillis); - } catch (final IOException ioEx) { - // We return -1 to ensure the profileCreationDate - // will either be an error (-1) or a consistent value. - Log.w(LOGTAG, "Unable to persist profile creation date - returning -1"); - return -1; - } - - return packageInstallMillis; - } - } - - @WorkerThread - private long getProfileCreationDateFromTimesFile() throws IOException { - final JSONObject obj = readJSONObjectFromFile(TIMES_PATH); - try { - return obj.getLong(PROFILE_CREATION_DATE_JSON_ATTR); - } catch (final JSONException e) { - // Don't log to avoid leaking data in JSONObject. - throw new IOException("Profile creation does not exist in JSONObject"); - } - } - - @WorkerThread - private void persistProfileCreationDateToTimesFile(final long profileCreationMillis) throws IOException { - final JSONObject obj = new JSONObject(); - try { - obj.put(PROFILE_CREATION_DATE_JSON_ATTR, profileCreationMillis); - } catch (final JSONException e) { - // Don't log to avoid leaking data in JSONObject. - throw new IOException("Unable to persist profile creation date to times file"); - } - Log.d(LOGTAG, "Attempting to write new profile creation date"); - writeFile(TIMES_PATH, obj.toString()); // Ideally we'd throw here too. - } - - /** - * Updates the state of the old session data file. - * - * sessionstore.js should hold the current session, and sessionstore.old should - * hold the previous session (where it is used to read the "tabs from last time"). - * If we're not restoring tabs automatically, sessionstore.js needs to be moved to - * sessionstore.old, so we can display the correct "tabs from last time". - * If we *are* restoring tabs, we need to delete outdated copies of sessionstore.old, - * so we don't continue showing stale "tabs from last time" indefinitely. - * - * @param shouldRestore Pass true if we are automatically restoring last session's tabs. - */ - public void updateSessionFile(boolean shouldRestore) { - File sessionFilePrevious = getFile(SESSION_FILE_PREVIOUS); - if (!shouldRestore) { - File sessionFile = getFile(SESSION_FILE); - if (sessionFile != null && sessionFile.exists()) { - sessionFile.renameTo(sessionFilePrevious); - } - } else { - if (sessionFilePrevious != null && sessionFilePrevious.exists() && - System.currentTimeMillis() - sessionFilePrevious.lastModified() > MAX_PREVIOUS_FILE_AGE) { - sessionFilePrevious.delete(); - } - } - synchronized (this) { - mOldSessionDataProcessed = true; - notifyAll(); - } - } - - public void waitForOldSessionDataProcessing() { - synchronized (this) { - while (!mOldSessionDataProcessed) { - try { - wait(); - } catch (final InterruptedException e) { - // Ignore and wait again. - } - } - } - } - - /** - * Get the string from a session file. - * - * The session can either be read from sessionstore.js or sessionstore.bak. - * In general, sessionstore.js holds the current session, and - * sessionstore.bak holds a backup copy in case of interrupted writes. - * - * @param readBackup if true, the session is read from sessionstore.bak; - * otherwise, the session is read from sessionstore.js - * - * @return the session string - */ - public String readSessionFile(boolean readBackup) { - return readSessionFile(readBackup ? SESSION_FILE_BACKUP : SESSION_FILE); - } - - /** - * Get the string from last session's session file. - * - * If we are not restoring tabs automatically, sessionstore.old will contain - * the previous session. - * - * @return the session string - */ - public String readPreviousSessionFile() { - return readSessionFile(SESSION_FILE_PREVIOUS); - } - - private String readSessionFile(String fileName) { - File sessionFile = getFile(fileName); - - try { - if (sessionFile != null && sessionFile.exists()) { - return readFile(sessionFile); - } - } catch (IOException ioe) { - Log.e(LOGTAG, "Unable to read session file", ioe); - } - return null; - } - - /** - * Checks whether the session store file exists. - */ - public boolean sessionFileExists() { - File sessionFile = getFile(SESSION_FILE); - - return sessionFile != null && sessionFile.exists(); - } - - /** - * Ensures the parent director(y|ies) of the given filename exist by making them - * if they don't already exist.. - * - * @param filename The path to the file whose parents should be made directories - * @return true if the parent directory exists, false otherwise - */ - @WorkerThread - protected boolean ensureParentDirs(final String filename) { - final File file = new File(getDir(), filename); - final File parentFile = file.getParentFile(); - return parentFile.mkdirs() || parentFile.isDirectory(); - } - - public void writeFile(final String filename, final String data) { - File file = new File(getDir(), filename); - BufferedWriter bufferedWriter = null; - try { - bufferedWriter = new BufferedWriter(new FileWriter(file, false)); - bufferedWriter.write(data); - } catch (IOException e) { - Log.e(LOGTAG, "Unable to write to file", e); - } finally { - try { - if (bufferedWriter != null) { - bufferedWriter.close(); - } - } catch (IOException e) { - Log.e(LOGTAG, "Error closing writer while writing to file", e); - } - } - } - - @WorkerThread - public JSONObject readJSONObjectFromFile(final String filename) throws IOException { - final String fileContents; - try { - fileContents = readFile(filename); - } catch (final IOException e) { - // Don't log exception to avoid leaking profile path. - throw new IOException("Could not access given file to retrieve JSONObject"); - } - - try { - return new JSONObject(fileContents); - } catch (final JSONException e) { - // Don't log exception to avoid leaking profile path. - throw new IOException("Could not parse JSON to retrieve JSONObject"); - } - } - - public JSONArray readJSONArrayFromFile(final String filename) { - String fileContent; - try { - fileContent = readFile(filename); - } catch (IOException expected) { - return new JSONArray(); - } - - JSONArray jsonArray; - try { - jsonArray = new JSONArray(fileContent); - } catch (JSONException e) { - jsonArray = new JSONArray(); - } - return jsonArray; - } - - public String readFile(String filename) throws IOException { - File dir = getDir(); - if (dir == null) { - throw new IOException("No profile directory found"); - } - File target = new File(dir, filename); - return readFile(target); - } - - private String readFile(File target) throws IOException { - FileReader fr = new FileReader(target); - try { - StringBuilder sb = new StringBuilder(); - char[] buf = new char[8192]; - int read = fr.read(buf); - while (read >= 0) { - sb.append(buf, 0, read); - read = fr.read(buf); - } - return sb.toString(); - } finally { - fr.close(); - } - } - - public boolean deleteFileFromProfileDir(String fileName) throws IllegalArgumentException { - if (TextUtils.isEmpty(fileName)) { - throw new IllegalArgumentException("Filename cannot be empty."); - } - File file = new File(getDir(), fileName); - return file.delete(); - } - - private boolean remove() { - try { - synchronized (this) { - if (mProfileDir != null && mProfileDir.exists()) { - FileUtils.delete(mProfileDir); - } - - if (isCustomProfile()) { - // Custom profiles don't have profile.ini sections that we need to remove. - return true; - } - - try { - // If findProfileDir() succeeds, it means the profile was created - // through forceCreate(), so we set mProfileDir to null to enable - // forceCreate() to create the profile again. - findProfileDir(); - mProfileDir = null; - - } catch (final NoSuchProfileException e) { - // If findProfileDir() throws, it means the profile was not created - // through forceCreate(), and we have to preserve mProfileDir because - // it was given to us. In that case, there's nothing left to do here. - return true; - } - } - - final INIParser parser = GeckoProfileDirectories.getProfilesINI(mMozillaDir); - final Hashtable<String, INISection> sections = parser.getSections(); - for (Enumeration<INISection> e = sections.elements(); e.hasMoreElements();) { - final INISection section = e.nextElement(); - String name = section.getStringProperty("Name"); - - if (name == null || !name.equals(mName)) { - continue; - } - - if (section.getName().startsWith("Profile")) { - // ok, we have stupid Profile#-named things. Rename backwards. - try { - int sectionNumber = Integer.parseInt(section.getName().substring("Profile".length())); - String curSection = "Profile" + sectionNumber; - String nextSection = "Profile" + (sectionNumber + 1); - - sections.remove(curSection); - - while (sections.containsKey(nextSection)) { - parser.renameSection(nextSection, curSection); - sectionNumber++; - - curSection = nextSection; - nextSection = "Profile" + (sectionNumber + 1); - } - } catch (NumberFormatException nex) { - // uhm, malformed Profile thing; we can't do much. - Log.e(LOGTAG, "Malformed section name in profiles.ini: " + section.getName()); - return false; - } - } else { - // this really shouldn't be the case, but handle it anyway - parser.removeSection(mName); - } - - break; - } - - parser.write(); - return true; - } catch (IOException ex) { - Log.w(LOGTAG, "Failed to remove profile.", ex); - return false; - } - } - - /** - * @return the default profile name for this application, or - * {@link GeckoProfile#DEFAULT_PROFILE} if none could be found. - * - * @throws NoMozillaDirectoryException - * if the Mozilla directory did not exist and could not be - * created. - */ - public static String getDefaultProfileName(final Context context) throws NoMozillaDirectoryException { - // Have we read the default profile from the INI already? - // Changing the default profile requires a restart, so we don't - // need to worry about runtime changes. - if (sDefaultProfileName != null) { - return sDefaultProfileName; - } - - final String profileName = GeckoProfileDirectories.findDefaultProfileName(context); - if (profileName == null) { - // Note that we don't persist this back to profiles.ini. - sDefaultProfileName = DEFAULT_PROFILE; - return DEFAULT_PROFILE; - } - - sDefaultProfileName = profileName; - return sDefaultProfileName; - } - - private File findProfileDir() throws NoSuchProfileException { - if (isCustomProfile()) { - return mProfileDir; - } - return GeckoProfileDirectories.findProfileDir(mMozillaDir, mName); - } - - @WorkerThread - private File createProfileDir() throws IOException { - if (isCustomProfile()) { - // Custom profiles must already exist. - return mProfileDir; - } - - INIParser parser = GeckoProfileDirectories.getProfilesINI(mMozillaDir); - - // Salt the name of our requested profile - String saltedName; - File profileDir; - do { - saltedName = GeckoProfileDirectories.saltProfileName(mName); - profileDir = new File(mMozillaDir, saltedName); - } while (profileDir.exists()); - - // Attempt to create the salted profile dir - if (!profileDir.mkdirs()) { - throw new IOException("Unable to create profile."); - } - Log.d(LOGTAG, "Created new profile dir."); - - // Now update profiles.ini - // If this is the first time its created, we also add a General section - // look for the first profile number that isn't taken yet - int profileNum = 0; - boolean isDefaultSet = false; - INISection profileSection; - while ((profileSection = parser.getSection("Profile" + profileNum)) != null) { - profileNum++; - if (profileSection.getProperty("Default") != null) { - isDefaultSet = true; - } - } - - profileSection = new INISection("Profile" + profileNum); - profileSection.setProperty("Name", mName); - profileSection.setProperty("IsRelative", 1); - profileSection.setProperty("Path", saltedName); - - if (parser.getSection("General") == null) { - INISection generalSection = new INISection("General"); - generalSection.setProperty("StartWithLastProfile", 1); - parser.addSection(generalSection); - } - - if (!isDefaultSet) { - // only set as default if this is the first profile we're creating - profileSection.setProperty("Default", 1); - } - - parser.addSection(profileSection); - parser.write(); - - enqueueInitialization(profileDir); - - // Write out profile creation time, mirroring the logic in nsToolkitProfileService. - try { - FileOutputStream stream = new FileOutputStream(profileDir.getAbsolutePath() + File.separator + TIMES_PATH); - OutputStreamWriter writer = new OutputStreamWriter(stream, Charset.forName("UTF-8")); - try { - writer.append("{\"created\": " + System.currentTimeMillis() + "}\n"); - } finally { - writer.close(); - } - } catch (Exception e) { - // Best-effort. - Log.w(LOGTAG, "Couldn't write " + TIMES_PATH, e); - } - - // Create the client ID file before Gecko starts (we assume this method - // is called before Gecko starts). If we let Gecko start, the JS telemetry - // code may try to write to the file at the same time Java does. - persistClientId(generateNewClientId()); - - return profileDir; - } - - /** - * This method is called once, immediately before creation of the profile - * directory completes. - * - * It queues up work to be done in the background to prepare the profile, - * such as adding default bookmarks. - * - * This is public for use *from tests only*! - */ - @RobocopTarget - public void enqueueInitialization(final File profileDir) { - Log.i(LOGTAG, "Enqueuing profile init."); - - final Bundle message = new Bundle(2); - message.putCharSequence("name", getName()); - message.putCharSequence("path", profileDir.getAbsolutePath()); - EventDispatcher.getInstance().dispatch("Profile:Create", message); - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoProfileDirectories.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoProfileDirectories.java deleted file mode 100644 index 2afb54bc4..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoProfileDirectories.java +++ /dev/null @@ -1,230 +0,0 @@ -/* 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; - -import java.io.File; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Map; - -import org.mozilla.gecko.annotation.RobocopTarget; -import org.mozilla.gecko.util.INIParser; -import org.mozilla.gecko.util.INISection; - -import android.content.Context; - -/** - * <code>GeckoProfileDirectories</code> manages access to mappings from profile - * names to salted profile directory paths, as well as the default profile name. - * - * This class will eventually come to encapsulate the remaining logic embedded - * in profiles.ini; for now it's a read-only wrapper. - */ -public class GeckoProfileDirectories { - @SuppressWarnings("serial") - public static class NoMozillaDirectoryException extends Exception { - public NoMozillaDirectoryException(Throwable cause) { - super(cause); - } - - public NoMozillaDirectoryException(String reason) { - super(reason); - } - - public NoMozillaDirectoryException(String reason, Throwable cause) { - super(reason, cause); - } - } - - @SuppressWarnings("serial") - public static class NoSuchProfileException extends Exception { - public NoSuchProfileException(String detailMessage, Throwable cause) { - super(detailMessage, cause); - } - - public NoSuchProfileException(String detailMessage) { - super(detailMessage); - } - } - - private interface INISectionPredicate { - public boolean matches(INISection section); - } - - private static final String MOZILLA_DIR_NAME = "mozilla"; - - /** - * Returns true if the supplied profile entry represents the default profile. - */ - private static final INISectionPredicate sectionIsDefault = new INISectionPredicate() { - @Override - public boolean matches(INISection section) { - return section.getIntProperty("Default") == 1; - } - }; - - /** - * Returns true if the supplied profile entry has a 'Name' field. - */ - private static final INISectionPredicate sectionHasName = new INISectionPredicate() { - @Override - public boolean matches(INISection section) { - final String name = section.getStringProperty("Name"); - return name != null; - } - }; - - @RobocopTarget - public static INIParser getProfilesINI(File mozillaDir) { - return new INIParser(new File(mozillaDir, "profiles.ini")); - } - - /** - * Utility method to compute a salted profile name: eight random alphanumeric - * characters, followed by a period, followed by the profile name. - */ - public static String saltProfileName(final String name) { - if (name == null) { - throw new IllegalArgumentException("Cannot salt null profile name."); - } - - final String allowedChars = "abcdefghijklmnopqrstuvwxyz0123456789"; - final int scale = allowedChars.length(); - final int saltSize = 8; - - final StringBuilder saltBuilder = new StringBuilder(saltSize + 1 + name.length()); - for (int i = 0; i < saltSize; i++) { - saltBuilder.append(allowedChars.charAt((int)(Math.random() * scale))); - } - saltBuilder.append('.'); - saltBuilder.append(name); - return saltBuilder.toString(); - } - - /** - * Return the Mozilla directory within the files directory of the provided - * context. This should always be the same within a running application. - * - * This method is package-scoped so that new {@link GeckoProfile} instances can - * contextualize themselves. - * - * @return a new File object for the Mozilla directory. - * @throws NoMozillaDirectoryException - * if the directory did not exist and could not be created. - */ - @RobocopTarget - public static File getMozillaDirectory(Context context) throws NoMozillaDirectoryException { - final File mozillaDir = new File(context.getFilesDir(), MOZILLA_DIR_NAME); - if (mozillaDir.mkdirs() || mozillaDir.isDirectory()) { - return mozillaDir; - } - - // Although this leaks a path to the system log, the path is - // predictable (unlike a profile directory), so this is fine. - throw new NoMozillaDirectoryException("Unable to create mozilla directory at " + mozillaDir.getAbsolutePath()); - } - - /** - * Discover the default profile name by examining profiles.ini. - * - * Package-scoped because {@link GeckoProfile} needs access to it. - * - * @return null if there is no "Default" entry in profiles.ini, or the profile - * name if there is. - * @throws NoMozillaDirectoryException - * if the Mozilla directory did not exist and could not be created. - */ - static String findDefaultProfileName(final Context context) throws NoMozillaDirectoryException { - final INIParser parser = GeckoProfileDirectories.getProfilesINI(getMozillaDirectory(context)); - if (parser.getSections() != null) { - for (Enumeration<INISection> e = parser.getSections().elements(); e.hasMoreElements(); ) { - final INISection section = e.nextElement(); - if (section.getIntProperty("Default") == 1) { - return section.getStringProperty("Name"); - } - } - } - return null; - } - - static Map<String, String> getDefaultProfile(final File mozillaDir) { - return getMatchingProfiles(mozillaDir, sectionIsDefault, true); - } - - static Map<String, String> getProfilesNamed(final File mozillaDir, final String name) { - final INISectionPredicate predicate = new INISectionPredicate() { - @Override - public boolean matches(final INISection section) { - return name.equals(section.getStringProperty("Name")); - } - }; - return getMatchingProfiles(mozillaDir, predicate, true); - } - - /** - * Calls {@link GeckoProfileDirectories#getMatchingProfiles(File, INISectionPredicate, boolean)} - * with a filter to ensure that all profiles are named. - */ - static Map<String, String> getAllProfiles(final File mozillaDir) { - return getMatchingProfiles(mozillaDir, sectionHasName, false); - } - - /** - * Return a mapping from the names of all matching profiles (that is, - * profiles appearing in profiles.ini that match the supplied predicate) to - * their absolute paths on disk. - * - * @param mozillaDir - * a directory containing profiles.ini. - * @param predicate - * a predicate to use when evaluating whether to include a - * particular INI section. - * @param stopOnSuccess - * if true, this method will return with the first result that - * matches the predicate; if false, all matching results are - * included. - * @return a {@link Map} from name to path. - */ - public static Map<String, String> getMatchingProfiles(final File mozillaDir, INISectionPredicate predicate, boolean stopOnSuccess) { - final HashMap<String, String> result = new HashMap<String, String>(); - final INIParser parser = GeckoProfileDirectories.getProfilesINI(mozillaDir); - - if (parser.getSections() != null) { - for (Enumeration<INISection> e = parser.getSections().elements(); e.hasMoreElements(); ) { - final INISection section = e.nextElement(); - if (predicate == null || predicate.matches(section)) { - final String name = section.getStringProperty("Name"); - final String pathString = section.getStringProperty("Path"); - final boolean isRelative = section.getIntProperty("IsRelative") == 1; - final File path = isRelative ? new File(mozillaDir, pathString) : new File(pathString); - result.put(name, path.getAbsolutePath()); - - if (stopOnSuccess) { - return result; - } - } - } - } - return result; - } - - public static File findProfileDir(final File mozillaDir, final String profileName) throws NoSuchProfileException { - // Open profiles.ini to find the correct path. - final INIParser parser = GeckoProfileDirectories.getProfilesINI(mozillaDir); - if (parser.getSections() != null) { - for (Enumeration<INISection> e = parser.getSections().elements(); e.hasMoreElements(); ) { - final INISection section = e.nextElement(); - final String name = section.getStringProperty("Name"); - if (name != null && name.equals(profileName)) { - if (section.getIntProperty("IsRelative") == 1) { - return new File(mozillaDir, section.getStringProperty("Path")); - } - return new File(section.getStringProperty("Path")); - } - } - } - throw new NoSuchProfileException("No profile " + profileName); - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoScreenOrientation.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoScreenOrientation.java deleted file mode 100644 index 23f84f52a..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoScreenOrientation.java +++ /dev/null @@ -1,423 +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; - -import org.mozilla.gecko.annotation.WrapForJNI; - -import android.content.pm.ActivityInfo; -import android.content.res.Configuration; -import android.util.Log; -import android.view.Surface; -import android.app.Activity; - -import java.util.Arrays; -import java.util.List; - -/* - * Updates, locks and unlocks the screen orientation. - * - * Note: Replaces the OnOrientationChangeListener to avoid redundant rotation - * event handling. - */ -public class GeckoScreenOrientation { - private static final String LOGTAG = "GeckoScreenOrientation"; - - // Make sure that any change in dom/base/ScreenOrientation.h happens here too. - public enum ScreenOrientation { - NONE(0), - PORTRAIT_PRIMARY(1 << 0), - PORTRAIT_SECONDARY(1 << 1), - PORTRAIT(PORTRAIT_PRIMARY.value | PORTRAIT_SECONDARY.value), - LANDSCAPE_PRIMARY(1 << 2), - LANDSCAPE_SECONDARY(1 << 3), - LANDSCAPE(LANDSCAPE_PRIMARY.value | LANDSCAPE_SECONDARY.value), - DEFAULT(1 << 4); - - public final short value; - - private ScreenOrientation(int value) { - this.value = (short)value; - } - - private final static ScreenOrientation[] sValues = ScreenOrientation.values(); - - public static ScreenOrientation get(int value) { - for (ScreenOrientation orient: sValues) { - if (orient.value == value) { - return orient; - } - } - return NONE; - } - } - - // Singleton instance. - private static GeckoScreenOrientation sInstance; - // Default screen orientation, used for initialization and unlocking. - private static final ScreenOrientation DEFAULT_SCREEN_ORIENTATION = ScreenOrientation.DEFAULT; - // Default rotation, used when device rotation is unknown. - private static final int DEFAULT_ROTATION = Surface.ROTATION_0; - // Default orientation, used if screen orientation is unspecified. - private ScreenOrientation mDefaultScreenOrientation; - // Last updated screen orientation. - private ScreenOrientation mScreenOrientation; - // Whether the update should notify Gecko about screen orientation changes. - private boolean mShouldNotify = true; - // Configuration screen orientation preference path. - private static final String DEFAULT_SCREEN_ORIENTATION_PREF = "app.orientation.default"; - - public GeckoScreenOrientation() { - PrefsHelper.getPref(DEFAULT_SCREEN_ORIENTATION_PREF, new PrefsHelper.PrefHandlerBase() { - @Override public void prefValue(String pref, String value) { - // Read and update the configuration default preference. - mDefaultScreenOrientation = screenOrientationFromArrayString(value); - setRequestedOrientation(mDefaultScreenOrientation); - } - }); - - mDefaultScreenOrientation = DEFAULT_SCREEN_ORIENTATION; - update(); - } - - public static GeckoScreenOrientation getInstance() { - if (sInstance == null) { - sInstance = new GeckoScreenOrientation(); - } - return sInstance; - } - - /* - * Enable Gecko screen orientation events on update. - */ - public void enableNotifications() { - update(); - mShouldNotify = true; - } - - /* - * Disable Gecko screen orientation events on update. - */ - public void disableNotifications() { - mShouldNotify = false; - } - - /* - * Update screen orientation. - * Retrieve orientation and rotation via GeckoAppShell. - * - * @return Whether the screen orientation has changed. - */ - public boolean update() { - Activity activity = getActivity(); - if (activity == null) { - return false; - } - Configuration config = activity.getResources().getConfiguration(); - return update(config.orientation); - } - - /* - * Update screen orientation given the android orientation. - * Retrieve rotation via GeckoAppShell. - * - * @param aAndroidOrientation - * Android screen orientation from Configuration.orientation. - * - * @return Whether the screen orientation has changed. - */ - public boolean update(int aAndroidOrientation) { - return update(getScreenOrientation(aAndroidOrientation, getRotation())); - } - - @WrapForJNI(dispatchTo = "gecko") - private static native void onOrientationChange(short screenOrientation, short angle); - - /* - * Update screen orientation given the screen orientation. - * - * @param aScreenOrientation - * Gecko screen orientation based on android orientation and rotation. - * - * @return Whether the screen orientation has changed. - */ - public boolean update(ScreenOrientation aScreenOrientation) { - if (mScreenOrientation == aScreenOrientation) { - return false; - } - mScreenOrientation = aScreenOrientation; - Log.d(LOGTAG, "updating to new orientation " + mScreenOrientation); - if (mShouldNotify) { - // Gecko expects a definite screen orientation, so we default to the - // primary orientations. - if (aScreenOrientation == ScreenOrientation.PORTRAIT) { - aScreenOrientation = ScreenOrientation.PORTRAIT_PRIMARY; - } else if (aScreenOrientation == ScreenOrientation.LANDSCAPE) { - aScreenOrientation = ScreenOrientation.LANDSCAPE_PRIMARY; - } - - if (GeckoThread.isRunning()) { - onOrientationChange(aScreenOrientation.value, getAngle()); - } else { - GeckoThread.queueNativeCall(GeckoScreenOrientation.class, "onOrientationChange", - aScreenOrientation.value, getAngle()); - } - } - GeckoAppShell.resetScreenSize(); - return true; - } - - /* - * @return The Android orientation (Configuration.orientation). - */ - public int getAndroidOrientation() { - return screenOrientationToAndroidOrientation(getScreenOrientation()); - } - - /* - * @return The Gecko screen orientation derived from Android orientation and - * rotation. - */ - public ScreenOrientation getScreenOrientation() { - return mScreenOrientation; - } - - /* - * Lock screen orientation given the Gecko screen orientation. - * - * @param aGeckoOrientation - * The Gecko orientation provided. - */ - public void lock(int aGeckoOrientation) { - lock(ScreenOrientation.get(aGeckoOrientation)); - } - - /* - * Lock screen orientation given the Gecko screen orientation. - * Retrieve rotation via GeckoAppShell. - * - * @param aScreenOrientation - * Gecko screen orientation derived from Android orientation and - * rotation. - * - * @return Whether the locking was successful. - */ - public boolean lock(ScreenOrientation aScreenOrientation) { - Log.d(LOGTAG, "locking to " + aScreenOrientation); - update(aScreenOrientation); - return setRequestedOrientation(aScreenOrientation); - } - - /* - * Unlock and update screen orientation. - * - * @return Whether the unlocking was successful. - */ - public boolean unlock() { - Log.d(LOGTAG, "unlocking"); - setRequestedOrientation(mDefaultScreenOrientation); - return update(); - } - - private Activity getActivity() { - if (GeckoAppShell.getGeckoInterface() == null) { - return null; - } - return GeckoAppShell.getGeckoInterface().getActivity(); - } - - /* - * Set the given requested orientation for the current activity. - * This is essentially an unlock without an update. - * - * @param aScreenOrientation - * Gecko screen orientation. - * - * @return Whether the requested orientation was set. This can only fail if - * the current activity cannot be retrieved via GeckoAppShell. - * - */ - private boolean setRequestedOrientation(ScreenOrientation aScreenOrientation) { - int activityOrientation = screenOrientationToActivityInfoOrientation(aScreenOrientation); - Activity activity = getActivity(); - if (activity == null) { - Log.w(LOGTAG, "setRequestOrientation: failed to get activity"); - return false; - } - if (activity.getRequestedOrientation() == activityOrientation) { - return false; - } - activity.setRequestedOrientation(activityOrientation); - return true; - } - - /* - * Combine the Android orientation and rotation to the Gecko orientation. - * - * @param aAndroidOrientation - * Android orientation from Configuration.orientation. - * @param aRotation - * Device rotation from Display.getRotation(). - * - * @return Gecko screen orientation. - */ - private ScreenOrientation getScreenOrientation(int aAndroidOrientation, int aRotation) { - boolean isPrimary = aRotation == Surface.ROTATION_0 || aRotation == Surface.ROTATION_90; - if (aAndroidOrientation == Configuration.ORIENTATION_PORTRAIT) { - if (isPrimary) { - // Non-rotated portrait device or landscape device rotated - // to primary portrait mode counter-clockwise. - return ScreenOrientation.PORTRAIT_PRIMARY; - } - return ScreenOrientation.PORTRAIT_SECONDARY; - } - if (aAndroidOrientation == Configuration.ORIENTATION_LANDSCAPE) { - if (isPrimary) { - // Non-rotated landscape device or portrait device rotated - // to primary landscape mode counter-clockwise. - return ScreenOrientation.LANDSCAPE_PRIMARY; - } - return ScreenOrientation.LANDSCAPE_SECONDARY; - } - return ScreenOrientation.NONE; - } - - /* - * @return Device rotation converted to an angle. - */ - public short getAngle() { - switch (getRotation()) { - case Surface.ROTATION_0: - return 0; - case Surface.ROTATION_90: - return 90; - case Surface.ROTATION_180: - return 180; - case Surface.ROTATION_270: - return 270; - default: - Log.w(LOGTAG, "getAngle: unexpected rotation value"); - return 0; - } - } - - /* - * @return Device rotation from Display.getRotation(). - */ - private int getRotation() { - Activity activity = getActivity(); - if (activity == null) { - Log.w(LOGTAG, "getRotation: failed to get activity"); - return DEFAULT_ROTATION; - } - return activity.getWindowManager().getDefaultDisplay().getRotation(); - } - - /* - * Retrieve the screen orientation from an array string. - * - * @param aArray - * String containing comma-delimited strings. - * - * @return First parsed Gecko screen orientation. - */ - public static ScreenOrientation screenOrientationFromArrayString(String aArray) { - List<String> orientations = Arrays.asList(aArray.split(",")); - if (orientations.size() == 0) { - // If nothing is listed, return default. - Log.w(LOGTAG, "screenOrientationFromArrayString: no orientation in string"); - return DEFAULT_SCREEN_ORIENTATION; - } - - // We don't support multiple orientations yet. To avoid developer - // confusion, just take the first one listed. - return screenOrientationFromString(orientations.get(0)); - } - - /* - * Retrieve the screen orientation from a string. - * - * @param aStr - * String hopefully containing a screen orientation name. - * @return Gecko screen orientation if matched, DEFAULT_SCREEN_ORIENTATION - * otherwise. - */ - public static ScreenOrientation screenOrientationFromString(String aStr) { - switch (aStr) { - case "portrait": - return ScreenOrientation.PORTRAIT; - case "landscape": - return ScreenOrientation.LANDSCAPE; - case "portrait-primary": - return ScreenOrientation.PORTRAIT_PRIMARY; - case "portrait-secondary": - return ScreenOrientation.PORTRAIT_SECONDARY; - case "landscape-primary": - return ScreenOrientation.LANDSCAPE_PRIMARY; - case "landscape-secondary": - return ScreenOrientation.LANDSCAPE_SECONDARY; - } - - Log.w(LOGTAG, "screenOrientationFromString: unknown orientation string: " + aStr); - return DEFAULT_SCREEN_ORIENTATION; - } - - /* - * Convert Gecko screen orientation to Android orientation. - * - * @param aScreenOrientation - * Gecko screen orientation. - * @return Android orientation. This conversion is lossy, the Android - * orientation does not differentiate between primary and secondary - * orientations. - */ - public static int screenOrientationToAndroidOrientation(ScreenOrientation aScreenOrientation) { - switch (aScreenOrientation) { - case PORTRAIT: - case PORTRAIT_PRIMARY: - case PORTRAIT_SECONDARY: - return Configuration.ORIENTATION_PORTRAIT; - case LANDSCAPE: - case LANDSCAPE_PRIMARY: - case LANDSCAPE_SECONDARY: - return Configuration.ORIENTATION_LANDSCAPE; - case NONE: - case DEFAULT: - default: - return Configuration.ORIENTATION_UNDEFINED; - } - } - - - /* - * Convert Gecko screen orientation to Android ActivityInfo orientation. - * This is yet another orientation used by Android, but it's more detailed - * than the Android orientation. - * It is required for screen orientation locking and unlocking. - * - * @param aScreenOrientation - * Gecko screen orientation. - * @return Android ActivityInfo orientation. - */ - public static int screenOrientationToActivityInfoOrientation(ScreenOrientation aScreenOrientation) { - switch (aScreenOrientation) { - case PORTRAIT: - case PORTRAIT_PRIMARY: - return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; - case PORTRAIT_SECONDARY: - return ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; - case LANDSCAPE: - case LANDSCAPE_PRIMARY: - return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; - case LANDSCAPE_SECONDARY: - return ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; - case DEFAULT: - case NONE: - return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; - default: - return ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; - } - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoSharedPrefs.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoSharedPrefs.java deleted file mode 100644 index ec928dd86..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoSharedPrefs.java +++ /dev/null @@ -1,318 +0,0 @@ -/* 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; - -import java.util.Arrays; -import java.util.EnumSet; -import java.util.List; -import java.util.Map; - -import org.mozilla.gecko.annotation.RobocopTarget; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.SharedPreferences.Editor; -import android.os.StrictMode; -import android.preference.PreferenceManager; -import android.util.Log; - -/** - * {@code GeckoSharedPrefs} provides scoped SharedPreferences instances. - * You should use this API instead of using Context.getSharedPreferences() - * directly. There are four methods to get scoped SharedPreferences instances: - * - * forApp() - * Use it for app-wide, cross-profile pref keys. - * forCrashReporter() - * For the crash reporter, which runs in its own process. - * forProfile() - * Use it to fetch and store keys for the current profile. - * forProfileName() - * Use it to fetch and store keys from/for a specific profile. - * - * {@code GeckoSharedPrefs} has a notion of migrations. Migrations can used to - * migrate keys from one scope to another. You can trigger a new migration by - * incrementing PREFS_VERSION and updating migrateIfNecessary() accordingly. - * - * Migration history: - * 1: Move all PreferenceManager keys to app/profile scopes - * 2: Move the crash reporter's private preferences into their own scope - */ -@RobocopTarget -public final class GeckoSharedPrefs { - private static final String LOGTAG = "GeckoSharedPrefs"; - - // Increment it to trigger a new migration - public static final int PREFS_VERSION = 2; - - // Name for app-scoped prefs - public static final String APP_PREFS_NAME = "GeckoApp"; - - // Name for crash reporter prefs - public static final String CRASH_PREFS_NAME = "CrashReporter"; - - // Used when fetching profile-scoped prefs. - public static final String PROFILE_PREFS_NAME_PREFIX = "GeckoProfile-"; - - // The prefs key that holds the current migration - private static final String PREFS_VERSION_KEY = "gecko_shared_prefs_migration"; - - // For disabling migration when getting a SharedPreferences instance - private static final EnumSet<Flags> disableMigrations = EnumSet.of(Flags.DISABLE_MIGRATIONS); - - // The keys that have to be moved from ProfileManager's default - // shared prefs to the profile from version 0 to 1. - private static final String[] PROFILE_MIGRATIONS_0_TO_1 = { - "home_panels", - "home_locale" - }; - - // The keys that have to be moved from the app prefs - // into the crash reporter's own prefs. - private static final String[] PROFILE_MIGRATIONS_1_TO_2 = { - "sendReport", - "includeUrl", - "allowContact", - "contactEmail" - }; - - // For optimizing the migration check in subsequent get() calls - private static volatile boolean migrationDone; - - public enum Flags { - DISABLE_MIGRATIONS - } - - public static SharedPreferences forApp(Context context) { - return forApp(context, EnumSet.noneOf(Flags.class)); - } - - /** - * Returns an app-scoped SharedPreferences instance. You can disable - * migrations by using the DISABLE_MIGRATIONS flag. - */ - public static SharedPreferences forApp(Context context, EnumSet<Flags> flags) { - if (flags != null && !flags.contains(Flags.DISABLE_MIGRATIONS)) { - migrateIfNecessary(context); - } - - return context.getSharedPreferences(APP_PREFS_NAME, 0); - } - - public static SharedPreferences forCrashReporter(Context context) { - return forCrashReporter(context, EnumSet.noneOf(Flags.class)); - } - - /** - * Returns a crash-reporter-scoped SharedPreferences instance. You can disable - * migrations by using the DISABLE_MIGRATIONS flag. - */ - public static SharedPreferences forCrashReporter(Context context, EnumSet<Flags> flags) { - if (flags != null && !flags.contains(Flags.DISABLE_MIGRATIONS)) { - migrateIfNecessary(context); - } - - return context.getSharedPreferences(CRASH_PREFS_NAME, 0); - } - - public static SharedPreferences forProfile(Context context) { - return forProfile(context, EnumSet.noneOf(Flags.class)); - } - - /** - * Returns a SharedPreferences instance scoped to the current profile - * in the app. You can disable migrations by using the DISABLE_MIGRATIONS - * flag. - */ - public static SharedPreferences forProfile(Context context, EnumSet<Flags> flags) { - String profileName = GeckoProfile.get(context).getName(); - if (profileName == null) { - throw new IllegalStateException("Could not get current profile name"); - } - - return forProfileName(context, profileName, flags); - } - - public static SharedPreferences forProfileName(Context context, String profileName) { - return forProfileName(context, profileName, EnumSet.noneOf(Flags.class)); - } - - /** - * Returns an SharedPreferences instance scoped to the given profile name. - * You can disable migrations by using the DISABLE_MIGRATION flag. - */ - public static SharedPreferences forProfileName(Context context, String profileName, - EnumSet<Flags> flags) { - if (flags != null && !flags.contains(Flags.DISABLE_MIGRATIONS)) { - migrateIfNecessary(context); - } - - final String prefsName = PROFILE_PREFS_NAME_PREFIX + profileName; - return context.getSharedPreferences(prefsName, 0); - } - - /** - * Returns the current version of the prefs. - */ - public static int getVersion(Context context) { - return forApp(context, disableMigrations).getInt(PREFS_VERSION_KEY, 0); - } - - /** - * Resets migration flag. Should only be used in tests. - */ - public static synchronized void reset() { - migrationDone = false; - } - - /** - * Performs all prefs migrations in the background thread to avoid StrictMode - * exceptions from reading/writing in the UI thread. This method will block - * the current thread until the migration is finished. - */ - private static synchronized void migrateIfNecessary(final Context context) { - if (migrationDone) { - return; - } - - // We deliberately perform the migration in the current thread (which - // is likely the UI thread) as this is actually cheaper than enforcing a - // context switch to another thread (see bug 940575). - // Avoid strict mode warnings when doing so. - final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads(); - StrictMode.allowThreadDiskWrites(); - try { - performMigration(context); - } finally { - StrictMode.setThreadPolicy(savedPolicy); - } - - migrationDone = true; - } - - private static void performMigration(Context context) { - final SharedPreferences appPrefs = forApp(context, disableMigrations); - - final int currentVersion = appPrefs.getInt(PREFS_VERSION_KEY, 0); - Log.d(LOGTAG, "Current version = " + currentVersion + ", prefs version = " + PREFS_VERSION); - - if (currentVersion == PREFS_VERSION) { - return; - } - - Log.d(LOGTAG, "Performing migration"); - - final Editor appEditor = appPrefs.edit(); - - // The migration always moves prefs to the default profile, not - // the current one. We might have to revisit this if we ever support - // multiple profiles. - final String defaultProfileName; - try { - defaultProfileName = GeckoProfile.getDefaultProfileName(context); - } catch (Exception e) { - throw new IllegalStateException("Failed to get default profile name for migration"); - } - - final Editor profileEditor = forProfileName(context, defaultProfileName, disableMigrations).edit(); - final Editor crashEditor = forCrashReporter(context, disableMigrations).edit(); - - List<String> profileKeys; - Editor pmEditor = null; - - for (int v = currentVersion + 1; v <= PREFS_VERSION; v++) { - Log.d(LOGTAG, "Migrating to version = " + v); - - switch (v) { - case 1: - profileKeys = Arrays.asList(PROFILE_MIGRATIONS_0_TO_1); - pmEditor = migrateFromPreferenceManager(context, appEditor, profileEditor, profileKeys); - break; - case 2: - profileKeys = Arrays.asList(PROFILE_MIGRATIONS_1_TO_2); - migrateCrashReporterSettings(appPrefs, appEditor, crashEditor, profileKeys); - break; - } - } - - // Update prefs version accordingly. - appEditor.putInt(PREFS_VERSION_KEY, PREFS_VERSION); - - appEditor.apply(); - profileEditor.apply(); - crashEditor.apply(); - if (pmEditor != null) { - pmEditor.apply(); - } - - Log.d(LOGTAG, "All keys have been migrated"); - } - - /** - * Moves all preferences stored in PreferenceManager's default prefs - * to either app or profile scopes. The profile-scoped keys are defined - * in given profileKeys list, all other keys are moved to the app scope. - */ - public static Editor migrateFromPreferenceManager(Context context, Editor appEditor, - Editor profileEditor, List<String> profileKeys) { - Log.d(LOGTAG, "Migrating from PreferenceManager"); - - final SharedPreferences pmPrefs = - PreferenceManager.getDefaultSharedPreferences(context); - - for (Map.Entry<String, ?> entry : pmPrefs.getAll().entrySet()) { - final String key = entry.getKey(); - - final Editor to; - if (profileKeys.contains(key)) { - to = profileEditor; - } else { - to = appEditor; - } - - putEntry(to, key, entry.getValue()); - } - - // Clear PreferenceManager's prefs once we're done - // and return the Editor to be committed. - return pmPrefs.edit().clear(); - } - - /** - * Moves the crash reporter's preferences from the app-wide prefs - * into its own shared prefs to avoid cross-process pref accesses. - */ - public static void migrateCrashReporterSettings(SharedPreferences appPrefs, Editor appEditor, - Editor crashEditor, List<String> profileKeys) { - Log.d(LOGTAG, "Migrating crash reporter settings"); - - for (Map.Entry<String, ?> entry : appPrefs.getAll().entrySet()) { - final String key = entry.getKey(); - - if (profileKeys.contains(key)) { - putEntry(crashEditor, key, entry.getValue()); - appEditor.remove(key); - } - } - } - - private static void putEntry(Editor to, String key, Object value) { - Log.d(LOGTAG, "Migrating key = " + key + " with value = " + value); - - if (value instanceof String) { - to.putString(key, (String) value); - } else if (value instanceof Boolean) { - to.putBoolean(key, (Boolean) value); - } else if (value instanceof Long) { - to.putLong(key, (Long) value); - } else if (value instanceof Float) { - to.putFloat(key, (Float) value); - } else if (value instanceof Integer) { - to.putInt(key, (Integer) value); - } else { - throw new IllegalStateException("Unrecognized value type for key: " + key); - } - } -}
\ No newline at end of file diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java deleted file mode 100644 index b57222a31..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java +++ /dev/null @@ -1,677 +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; - -import org.mozilla.gecko.annotation.RobocopTarget; -import org.mozilla.gecko.annotation.WrapForJNI; -import org.mozilla.gecko.mozglue.GeckoLoader; -import org.mozilla.gecko.util.ThreadUtils; - -import org.json.JSONException; -import org.json.JSONObject; - -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.os.MessageQueue; -import android.os.SystemClock; -import android.util.Log; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Locale; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; - -public class GeckoThread extends Thread { - private static final String LOGTAG = "GeckoThread"; - - public enum State { - // After being loaded by class loader. - @WrapForJNI INITIAL(0), - // After launching Gecko thread - @WrapForJNI LAUNCHED(1), - // After loading the mozglue library. - @WrapForJNI MOZGLUE_READY(2), - // After loading the libxul library. - @WrapForJNI LIBS_READY(3), - // After initializing nsAppShell and JNI calls. - @WrapForJNI JNI_READY(4), - // After initializing profile and prefs. - @WrapForJNI PROFILE_READY(5), - // After initializing frontend JS - @WrapForJNI RUNNING(6), - // After leaving Gecko event loop - @WrapForJNI EXITING(3), - // After exiting GeckoThread (corresponding to "Gecko:Exited" event) - @WrapForJNI EXITED(0); - - /* The rank is an arbitrary value reflecting the amount of components or features - * that are available for use. During startup and up to the RUNNING state, the - * rank value increases because more components are initialized and available for - * use. During shutdown and up to the EXITED state, the rank value decreases as - * components are shut down and become unavailable. EXITING has the same rank as - * LIBS_READY because both states have a similar amount of components available. - */ - private final int rank; - - private State(int rank) { - this.rank = rank; - } - - public boolean is(final State other) { - return this == other; - } - - public boolean isAtLeast(final State other) { - return this.rank >= other.rank; - } - - public boolean isAtMost(final State other) { - return this.rank <= other.rank; - } - - // Inclusive - public boolean isBetween(final State min, final State max) { - return this.rank >= min.rank && this.rank <= max.rank; - } - } - - public static final State MIN_STATE = State.INITIAL; - public static final State MAX_STATE = State.EXITED; - - private static volatile State sState = State.INITIAL; - - private static class QueuedCall { - public Method method; - public Object target; - public Object[] args; - public State state; - - public QueuedCall(final Method method, final Object target, - final Object[] args, final State state) { - this.method = method; - this.target = target; - this.args = args; - this.state = state; - } - } - - private static final int QUEUED_CALLS_COUNT = 16; - private static final ArrayList<QueuedCall> QUEUED_CALLS = new ArrayList<>(QUEUED_CALLS_COUNT); - - private static final Runnable UI_THREAD_CALLBACK = new Runnable() { - @Override - public void run() { - ThreadUtils.assertOnUiThread(); - long nextDelay = runUiThreadCallback(); - if (nextDelay >= 0) { - ThreadUtils.getUiHandler().postDelayed(this, nextDelay); - } - } - }; - - private static GeckoThread sGeckoThread; - - @WrapForJNI - private static final ClassLoader clsLoader = GeckoThread.class.getClassLoader(); - @WrapForJNI - private static MessageQueue msgQueue; - - private GeckoProfile mProfile; - - private final String mArgs; - private final String mAction; - private final boolean mDebugging; - - GeckoThread(GeckoProfile profile, String args, String action, boolean debugging) { - mProfile = profile; - mArgs = args; - mAction = action; - mDebugging = debugging; - - setName("Gecko"); - } - - public static boolean init(GeckoProfile profile, String args, String action, boolean debugging) { - ThreadUtils.assertOnUiThread(); - if (isState(State.INITIAL) && sGeckoThread == null) { - sGeckoThread = new GeckoThread(profile, args, action, debugging); - return true; - } - return false; - } - - private static boolean canUseProfile(final Context context, final GeckoProfile profile, - final String profileName, final File profileDir) { - if (profileDir != null && !profileDir.isDirectory()) { - return false; - } - - if (profile == null) { - // We haven't initialized; any profile is okay as long as we follow the guest mode setting. - return GeckoProfile.shouldUseGuestMode(context) == - GeckoProfile.isGuestProfile(context, profileName, profileDir); - } - - // We already initialized and have a profile; see if it matches ours. - try { - return profileDir == null ? profileName.equals(profile.getName()) : - profile.getDir().getCanonicalPath().equals(profileDir.getCanonicalPath()); - } catch (final IOException e) { - Log.e(LOGTAG, "Cannot compare profile " + profileName); - return false; - } - } - - public static boolean canUseProfile(final String profileName, final File profileDir) { - if (profileName == null) { - throw new IllegalArgumentException("Null profile name"); - } - return canUseProfile(GeckoAppShell.getApplicationContext(), getActiveProfile(), - profileName, profileDir); - } - - public static boolean initWithProfile(final String profileName, final File profileDir) { - if (profileName == null) { - throw new IllegalArgumentException("Null profile name"); - } - - final Context context = GeckoAppShell.getApplicationContext(); - final GeckoProfile profile = getActiveProfile(); - - if (!canUseProfile(context, profile, profileName, profileDir)) { - // Profile is incompatible with current profile. - return false; - } - - if (profile != null) { - // We already have a compatible profile. - return true; - } - - // We haven't initialized yet; okay to initialize now. - return init(GeckoProfile.get(context, profileName, profileDir), - /* args */ null, /* action */ null, /* debugging */ false); - } - - public static boolean launch() { - ThreadUtils.assertOnUiThread(); - if (checkAndSetState(State.INITIAL, State.LAUNCHED)) { - sGeckoThread.start(); - return true; - } - return false; - } - - public static boolean isLaunched() { - return !isState(State.INITIAL); - } - - @RobocopTarget - public static boolean isRunning() { - return isState(State.RUNNING); - } - - // Invoke the given Method and handle checked Exceptions. - private static void invokeMethod(final Method method, final Object obj, final Object[] args) { - try { - method.setAccessible(true); - method.invoke(obj, args); - } catch (final IllegalAccessException e) { - throw new IllegalStateException("Unexpected exception", e); - } catch (final InvocationTargetException e) { - throw new UnsupportedOperationException("Cannot make call", e.getCause()); - } - } - - // Queue a call to the given method. - private static void queueNativeCallLocked(final Class<?> cls, final String methodName, - final Object obj, final Object[] args, - final State state) { - final ArrayList<Class<?>> argTypes = new ArrayList<>(args.length); - final ArrayList<Object> argValues = new ArrayList<>(args.length); - - for (int i = 0; i < args.length; i++) { - if (args[i] instanceof Class) { - argTypes.add((Class<?>) args[i]); - argValues.add(args[++i]); - continue; - } - Class<?> argType = args[i].getClass(); - if (argType == Boolean.class) argType = Boolean.TYPE; - else if (argType == Byte.class) argType = Byte.TYPE; - else if (argType == Character.class) argType = Character.TYPE; - else if (argType == Double.class) argType = Double.TYPE; - else if (argType == Float.class) argType = Float.TYPE; - else if (argType == Integer.class) argType = Integer.TYPE; - else if (argType == Long.class) argType = Long.TYPE; - else if (argType == Short.class) argType = Short.TYPE; - argTypes.add(argType); - argValues.add(args[i]); - } - final Method method; - try { - method = cls.getDeclaredMethod( - methodName, argTypes.toArray(new Class<?>[argTypes.size()])); - } catch (final NoSuchMethodException e) { - throw new IllegalArgumentException("Cannot find method", e); - } - - if (!Modifier.isNative(method.getModifiers())) { - // As a precaution, we disallow queuing non-native methods. Queuing non-native - // methods is dangerous because the method could end up being called on either - // the original thread or the Gecko thread depending on timing. Native methods - // usually handle this by posting an event to the Gecko thread automatically, - // but there is no automatic mechanism for non-native methods. - throw new UnsupportedOperationException("Not allowed to queue non-native methods"); - } - - if (isStateAtLeast(state)) { - invokeMethod(method, obj, argValues.toArray()); - return; - } - - QUEUED_CALLS.add(new QueuedCall( - method, obj, argValues.toArray(), state)); - } - - /** - * Queue a call to the given static method until Gecko is in the given state. - * - * @param state The Gecko state in which the native call could be executed. - * Default is State.RUNNING, which means this queued call will - * run when Gecko is at or after RUNNING state. - * @param cls Class that declares the static method. - * @param methodName Name of the static method. - * @param args Args to call the static method with; to specify a parameter type, - * pass in a Class instance first, followed by the value. - */ - public static void queueNativeCallUntil(final State state, final Class<?> cls, - final String methodName, final Object... args) { - synchronized (QUEUED_CALLS) { - queueNativeCallLocked(cls, methodName, null, args, state); - } - } - - /** - * Queue a call to the given static method until Gecko is in the RUNNING state. - */ - public static void queueNativeCall(final Class<?> cls, final String methodName, - final Object... args) { - synchronized (QUEUED_CALLS) { - queueNativeCallLocked(cls, methodName, null, args, State.RUNNING); - } - } - - /** - * Queue a call to the given instance method until Gecko is in the given state. - * - * @param state The Gecko state in which the native call could be executed. - * @param obj Object that declares the instance method. - * @param methodName Name of the instance method. - * @param args Args to call the instance method with; to specify a parameter type, - * pass in a Class instance first, followed by the value. - */ - public static void queueNativeCallUntil(final State state, final Object obj, - final String methodName, final Object... args) { - synchronized (QUEUED_CALLS) { - queueNativeCallLocked(obj.getClass(), methodName, obj, args, state); - } - } - - /** - * Queue a call to the given instance method until Gecko is in the RUNNING state. - */ - public static void queueNativeCall(final Object obj, final String methodName, - final Object... args) { - synchronized (QUEUED_CALLS) { - queueNativeCallLocked(obj.getClass(), methodName, obj, args, State.RUNNING); - } - } - - // Run all queued methods - private static void flushQueuedNativeCallsLocked(final State state) { - int lastSkipped = -1; - for (int i = 0; i < QUEUED_CALLS.size(); i++) { - final QueuedCall call = QUEUED_CALLS.get(i); - if (call == null) { - // We already handled the call. - continue; - } - if (!state.isAtLeast(call.state)) { - // The call is not ready yet; skip it. - lastSkipped = i; - continue; - } - // Mark as handled. - QUEUED_CALLS.set(i, null); - - invokeMethod(call.method, call.target, call.args); - } - if (lastSkipped < 0) { - // We're done here; release the memory - QUEUED_CALLS.clear(); - QUEUED_CALLS.trimToSize(); - } else if (lastSkipped < QUEUED_CALLS.size() - 1) { - // We skipped some; free up null entries at the end, - // but keep all the previous entries for later. - QUEUED_CALLS.subList(lastSkipped + 1, QUEUED_CALLS.size()).clear(); - } - } - - private static String initGeckoEnvironment() { - final Context context = GeckoAppShell.getApplicationContext(); - GeckoLoader.loadMozGlue(context); - setState(State.MOZGLUE_READY); - - final Locale locale = Locale.getDefault(); - final Resources res = context.getResources(); - if (locale.toString().equalsIgnoreCase("zh_hk")) { - final Locale mappedLocale = Locale.TRADITIONAL_CHINESE; - Locale.setDefault(mappedLocale); - Configuration config = res.getConfiguration(); - config.locale = mappedLocale; - res.updateConfiguration(config, null); - } - - String[] pluginDirs = null; - try { - pluginDirs = GeckoAppShell.getPluginDirectories(); - } catch (Exception e) { - Log.w(LOGTAG, "Caught exception getting plugin dirs.", e); - } - - final String resourcePath = context.getPackageResourcePath(); - GeckoLoader.setupGeckoEnvironment(context, pluginDirs, context.getFilesDir().getPath()); - - GeckoLoader.loadSQLiteLibs(context, resourcePath); - GeckoLoader.loadNSSLibs(context, resourcePath); - GeckoLoader.loadGeckoLibs(context, resourcePath); - setState(State.LIBS_READY); - - return resourcePath; - } - - private String addCustomProfileArg(String args) { - String profileArg = ""; - - // Make sure a profile exists. - final GeckoProfile profile = getProfile(); - profile.getDir(); // call the lazy initializer - - // If args don't include the profile, make sure it's included. - if (args == null || !args.matches(".*\\B-(P|profile)\\s+\\S+.*")) { - if (profile.isCustomProfile()) { - profileArg = " -profile " + profile.getDir().getAbsolutePath(); - } else { - profileArg = " -P " + profile.getName(); - } - } - - return (args != null ? args : "") + profileArg; - } - - private String getGeckoArgs(final String apkPath) { - // argv[0] is the program name, which for us is the package name. - final Context context = GeckoAppShell.getApplicationContext(); - final StringBuilder args = new StringBuilder(context.getPackageName()); - args.append(" -greomni ").append(apkPath); - - final String userArgs = addCustomProfileArg(mArgs); - if (userArgs != null) { - args.append(' ').append(userArgs); - } - - // In un-official builds, we want to load Javascript resources fresh - // with each build. In official builds, the startup cache is purged by - // the buildid mechanism, but most un-official builds don't bump the - // buildid, so we purge here instead. - if (!AppConstants.MOZILLA_OFFICIAL) { - Log.w(LOGTAG, "STARTUP PERFORMANCE WARNING: un-official build: purging the " + - "startup (JavaScript) caches."); - args.append(" -purgecaches"); - } - - return args.toString(); - } - - public static GeckoProfile getActiveProfile() { - if (sGeckoThread == null) { - return null; - } - final GeckoProfile profile = sGeckoThread.mProfile; - if (profile != null) { - return profile; - } - return sGeckoThread.getProfile(); - } - - public synchronized GeckoProfile getProfile() { - if (mProfile == null) { - final Context context = GeckoAppShell.getApplicationContext(); - mProfile = GeckoProfile.initFromArgs(context, mArgs); - } - return mProfile; - } - - @Override - public void run() { - Log.i(LOGTAG, "preparing to run Gecko"); - - Looper.prepare(); - GeckoThread.msgQueue = Looper.myQueue(); - ThreadUtils.sGeckoThread = this; - ThreadUtils.sGeckoHandler = new Handler(); - - // Preparation for pumpMessageLoop() - final MessageQueue.IdleHandler idleHandler = new MessageQueue.IdleHandler() { - @Override public boolean queueIdle() { - final Handler geckoHandler = ThreadUtils.sGeckoHandler; - Message idleMsg = Message.obtain(geckoHandler); - // Use |Message.obj == GeckoHandler| to identify our "queue is empty" message - idleMsg.obj = geckoHandler; - geckoHandler.sendMessageAtFrontOfQueue(idleMsg); - // Keep this IdleHandler - return true; - } - }; - Looper.myQueue().addIdleHandler(idleHandler); - - if (mDebugging) { - try { - Thread.sleep(5 * 1000 /* 5 seconds */); - } catch (final InterruptedException e) { - } - } - - final String args = getGeckoArgs(initGeckoEnvironment()); - - // This can only happen after the call to initGeckoEnvironment - // above, because otherwise the JNI code hasn't been loaded yet. - ThreadUtils.postToUiThread(new Runnable() { - @Override public void run() { - registerUiThread(); - } - }); - - Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - runGecko"); - - if (!AppConstants.MOZILLA_OFFICIAL) { - Log.i(LOGTAG, "RunGecko - args = " + args); - } - - // And go. - GeckoLoader.nativeRun(args); - - // And... we're done. - setState(State.EXITED); - - try { - final JSONObject msg = new JSONObject(); - msg.put("type", "Gecko:Exited"); - GeckoAppShell.getGeckoInterface().getAppEventDispatcher().dispatchEvent(msg, null); - EventDispatcher.getInstance().dispatchEvent(msg, null); - } catch (final JSONException e) { - Log.e(LOGTAG, "unable to dispatch event", e); - } - - // Remove pumpMessageLoop() idle handler - Looper.myQueue().removeIdleHandler(idleHandler); - } - - @WrapForJNI(calledFrom = "gecko") - private static boolean pumpMessageLoop(final Message msg) { - final Handler geckoHandler = ThreadUtils.sGeckoHandler; - - if (msg.obj == geckoHandler && msg.getTarget() == geckoHandler) { - // Our "queue is empty" message; see runGecko() - return false; - } - - if (msg.getTarget() == null) { - Looper.myLooper().quit(); - } else { - msg.getTarget().dispatchMessage(msg); - } - - return true; - } - - /** - * Check that the current Gecko thread state matches the given state. - * - * @param state State to check - * @return True if the current Gecko thread state matches - */ - public static boolean isState(final State state) { - return sState.is(state); - } - - /** - * Check that the current Gecko thread state is at the given state or further along, - * according to the order defined in the State enum. - * - * @param state State to check - * @return True if the current Gecko thread state matches - */ - public static boolean isStateAtLeast(final State state) { - return sState.isAtLeast(state); - } - - /** - * Check that the current Gecko thread state is at the given state or prior, - * according to the order defined in the State enum. - * - * @param state State to check - * @return True if the current Gecko thread state matches - */ - public static boolean isStateAtMost(final State state) { - return sState.isAtMost(state); - } - - /** - * Check that the current Gecko thread state falls into an inclusive range of states, - * according to the order defined in the State enum. - * - * @param minState Lower range of allowable states - * @param maxState Upper range of allowable states - * @return True if the current Gecko thread state matches - */ - public static boolean isStateBetween(final State minState, final State maxState) { - return sState.isBetween(minState, maxState); - } - - @WrapForJNI(calledFrom = "gecko") - private static void setState(final State newState) { - ThreadUtils.assertOnGeckoThread(); - synchronized (QUEUED_CALLS) { - flushQueuedNativeCallsLocked(newState); - sState = newState; - } - } - - @WrapForJNI(calledFrom = "gecko") - private static boolean checkAndSetState(final State currentState, final State newState) { - synchronized (QUEUED_CALLS) { - if (sState == currentState) { - flushQueuedNativeCallsLocked(newState); - sState = newState; - return true; - } - } - return false; - } - - @WrapForJNI(stubName = "SpeculativeConnect") - private static native void speculativeConnectNative(String uri); - - public static void speculativeConnect(final String uri) { - // This is almost always called before Gecko loads, so we don't - // bother checking here if Gecko is actually loaded or not. - // Speculative connection depends on proxy settings, - // so the earliest it can happen is after profile is ready. - queueNativeCallUntil(State.PROFILE_READY, GeckoThread.class, - "speculativeConnectNative", uri); - } - - @WrapForJNI @RobocopTarget - public static native void waitOnGecko(); - - @WrapForJNI(stubName = "OnPause", dispatchTo = "gecko") - private static native void nativeOnPause(); - - public static void onPause() { - if (isStateAtLeast(State.PROFILE_READY)) { - nativeOnPause(); - } else { - queueNativeCallUntil(State.PROFILE_READY, GeckoThread.class, - "nativeOnPause"); - } - } - - @WrapForJNI(stubName = "OnResume", dispatchTo = "gecko") - private static native void nativeOnResume(); - - public static void onResume() { - if (isStateAtLeast(State.PROFILE_READY)) { - nativeOnResume(); - } else { - queueNativeCallUntil(State.PROFILE_READY, GeckoThread.class, - "nativeOnResume"); - } - } - - @WrapForJNI(stubName = "CreateServices", dispatchTo = "gecko") - private static native void nativeCreateServices(String category, String data); - - public static void createServices(final String category, final String data) { - if (isStateAtLeast(State.PROFILE_READY)) { - nativeCreateServices(category, data); - } else { - queueNativeCallUntil(State.PROFILE_READY, GeckoThread.class, "nativeCreateServices", - String.class, category, String.class, data); - } - } - - // Implemented in mozglue/android/APKOpen.cpp. - /* package */ static native void registerUiThread(); - - @WrapForJNI(calledFrom = "ui") - /* package */ static native long runUiThreadCallback(); - - @WrapForJNI - private static void requestUiThreadCallback(long delay) { - ThreadUtils.getUiHandler().postDelayed(UI_THREAD_CALLBACK, delay); - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoView.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoView.java deleted file mode 100644 index 93d738361..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoView.java +++ /dev/null @@ -1,736 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- - * vim: ts=4 sw=4 expandtab: - * 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; - -import java.util.Set; - -import org.json.JSONException; -import org.json.JSONObject; -import org.mozilla.gecko.annotation.ReflectionTarget; -import org.mozilla.gecko.annotation.WrapForJNI; -import org.mozilla.gecko.gfx.LayerView; -import org.mozilla.gecko.mozglue.JNIObject; -import org.mozilla.gecko.util.EventCallback; -import org.mozilla.gecko.util.GeckoEventListener; -import org.mozilla.gecko.util.NativeEventListener; -import org.mozilla.gecko.util.NativeJSObject; -import org.mozilla.gecko.util.ThreadUtils; - -import android.app.Activity; -import android.content.Context; -import android.content.SharedPreferences; -import android.os.Binder; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.Parcel; -import android.os.Parcelable; -import android.util.AttributeSet; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.KeyEvent; -import android.view.View; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputConnection; - -public class GeckoView extends LayerView - implements ContextGetter, GeckoEventListener, NativeEventListener { - - private static final String DEFAULT_SHARED_PREFERENCES_FILE = "GeckoView"; - private static final String LOGTAG = "GeckoView"; - - private ChromeDelegate mChromeDelegate; - private ContentDelegate mContentDelegate; - - private InputConnectionListener mInputConnectionListener; - - protected boolean onAttachedToWindowCalled; - protected String chromeURI = getGeckoInterface().getDefaultChromeURI(); - protected int screenId = 0; // default to the primary screen - - @Override - public void handleMessage(final String event, final JSONObject message) { - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - try { - if (event.equals("Gecko:Ready")) { - handleReady(message); - } else if (event.equals("Content:StateChange")) { - handleStateChange(message); - } else if (event.equals("Content:LoadError")) { - handleLoadError(message); - } else if (event.equals("Content:PageShow")) { - handlePageShow(message); - } else if (event.equals("DOMTitleChanged")) { - handleTitleChanged(message); - } else if (event.equals("Link:Favicon")) { - handleLinkFavicon(message); - } else if (event.equals("Prompt:Show") || event.equals("Prompt:ShowTop")) { - handlePrompt(message); - } else if (event.equals("Accessibility:Event")) { - int mode = getImportantForAccessibility(); - if (mode == View.IMPORTANT_FOR_ACCESSIBILITY_YES || - mode == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { - GeckoAccessibility.sendAccessibilityEvent(message); - } - } - } catch (Exception e) { - Log.e(LOGTAG, "handleMessage threw for " + event, e); - } - } - }); - } - - @Override - public void handleMessage(final String event, final NativeJSObject message, final EventCallback callback) { - try { - if ("Accessibility:Ready".equals(event)) { - GeckoAccessibility.updateAccessibilitySettings(getContext()); - } else if ("GeckoView:Message".equals(event)) { - // We need to pull out the bundle while on the Gecko thread. - NativeJSObject json = message.optObject("data", null); - if (json == null) { - // Must have payload to call the message handler. - return; - } - final Bundle data = json.toBundle(); - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - handleScriptMessage(data, callback); - } - }); - } - } catch (Exception e) { - Log.w(LOGTAG, "handleMessage threw for " + event, e); - } - } - - @WrapForJNI(dispatchTo = "proxy") - protected static final class Window extends JNIObject { - @WrapForJNI(skip = true) - /* package */ Window() {} - - static native void open(Window instance, GeckoView view, Object compositor, - String chromeURI, int screenId); - - @Override protected native void disposeNative(); - native void close(); - native void reattach(GeckoView view, Object compositor); - native void loadUri(String uri, int flags); - } - - // Object to hold onto our nsWindow connection when GeckoView gets destroyed. - private static class StateBinder extends Binder implements Parcelable { - public final Parcelable superState; - public final Window window; - - public StateBinder(Parcelable superState, Window window) { - this.superState = superState; - this.window = window; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - // Always write out the super-state, so that even if we lose this binder, we - // will still have something to pass into super.onRestoreInstanceState. - out.writeParcelable(superState, flags); - out.writeStrongBinder(this); - } - - @ReflectionTarget - public static final Parcelable.Creator<StateBinder> CREATOR - = new Parcelable.Creator<StateBinder>() { - @Override - public StateBinder createFromParcel(Parcel in) { - final Parcelable superState = in.readParcelable(null); - final IBinder binder = in.readStrongBinder(); - if (binder instanceof StateBinder) { - return (StateBinder) binder; - } - // Not the original object we saved; return null state. - return new StateBinder(superState, null); - } - - @Override - public StateBinder[] newArray(int size) { - return new StateBinder[size]; - } - }; - } - - protected Window window; - private boolean stateSaved; - - public GeckoView(Context context) { - super(context); - init(context); - } - - public GeckoView(Context context, AttributeSet attrs) { - super(context, attrs); - init(context); - } - - private void init(Context context) { - if (GeckoAppShell.getApplicationContext() == null) { - GeckoAppShell.setApplicationContext(context.getApplicationContext()); - } - - // Set the GeckoInterface if the context is an activity and the GeckoInterface - // has not already been set - if (context instanceof Activity && getGeckoInterface() == null) { - setGeckoInterface(new BaseGeckoInterface(context)); - GeckoAppShell.setContextGetter(this); - } - - // Perform common initialization for Fennec/GeckoView. - GeckoAppShell.setLayerView(this); - - initializeView(EventDispatcher.getInstance()); - } - - @Override - protected Parcelable onSaveInstanceState() - { - final Parcelable superState = super.onSaveInstanceState(); - stateSaved = true; - return new StateBinder(superState, this.window); - } - - @Override - protected void onRestoreInstanceState(final Parcelable state) - { - final StateBinder stateBinder = (StateBinder) state; - - if (stateBinder.window != null) { - this.window = stateBinder.window; - } - stateSaved = false; - - if (onAttachedToWindowCalled) { - reattachWindow(); - } - - // We have to always call super.onRestoreInstanceState because View keeps - // track of these calls and throws an exception when we don't call it. - super.onRestoreInstanceState(stateBinder.superState); - } - - protected void openWindow() { - - if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) { - Window.open(window, this, getCompositor(), - chromeURI, screenId); - } else { - GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY, Window.class, - "open", window, GeckoView.class, this, Object.class, getCompositor(), - String.class, chromeURI, screenId); - } - } - - protected void reattachWindow() { - if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) { - window.reattach(this, getCompositor()); - } else { - GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY, - window, "reattach", GeckoView.class, this, Object.class, getCompositor()); - } - } - - @Override - public void onAttachedToWindow() - { - final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics(); - - if (window == null) { - // Open a new nsWindow if we didn't have one from before. - window = new Window(); - openWindow(); - } else { - reattachWindow(); - } - - super.onAttachedToWindow(); - - onAttachedToWindowCalled = true; - } - - @Override - public void onDetachedFromWindow() - { - super.onDetachedFromWindow(); - super.destroy(); - - if (stateSaved) { - // If we saved state earlier, we don't want to close the nsWindow. - return; - } - - if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) { - window.close(); - window.disposeNative(); - } else { - GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY, - window, "close"); - GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY, - window, "disposeNative"); - } - - onAttachedToWindowCalled = false; - } - - @WrapForJNI public static final int LOAD_DEFAULT = 0; - @WrapForJNI public static final int LOAD_NEW_TAB = 1; - @WrapForJNI public static final int LOAD_SWITCH_TAB = 2; - - public void loadUri(String uri, int flags) { - if (window == null) { - throw new IllegalStateException("Not attached to window"); - } - - if (GeckoThread.isRunning()) { - window.loadUri(uri, flags); - } else { - GeckoThread.queueNativeCall(window, "loadUri", String.class, uri, flags); - } - } - - /* package */ void setInputConnectionListener(final InputConnectionListener icl) { - mInputConnectionListener = icl; - } - - @Override - public Handler getHandler() { - if (mInputConnectionListener != null) { - return mInputConnectionListener.getHandler(super.getHandler()); - } - return super.getHandler(); - } - - @Override - public InputConnection onCreateInputConnection(EditorInfo outAttrs) { - if (mInputConnectionListener != null) { - return mInputConnectionListener.onCreateInputConnection(outAttrs); - } - return null; - } - - @Override - public boolean onKeyPreIme(int keyCode, KeyEvent event) { - if (super.onKeyPreIme(keyCode, event)) { - return true; - } - return mInputConnectionListener != null && - mInputConnectionListener.onKeyPreIme(keyCode, event); - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (super.onKeyUp(keyCode, event)) { - return true; - } - return mInputConnectionListener != null && - mInputConnectionListener.onKeyUp(keyCode, event); - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - if (super.onKeyDown(keyCode, event)) { - return true; - } - return mInputConnectionListener != null && - mInputConnectionListener.onKeyDown(keyCode, event); - } - - @Override - public boolean onKeyLongPress(int keyCode, KeyEvent event) { - if (super.onKeyLongPress(keyCode, event)) { - return true; - } - return mInputConnectionListener != null && - mInputConnectionListener.onKeyLongPress(keyCode, event); - } - - @Override - public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { - if (super.onKeyMultiple(keyCode, repeatCount, event)) { - return true; - } - return mInputConnectionListener != null && - mInputConnectionListener.onKeyMultiple(keyCode, repeatCount, event); - } - - /* package */ boolean isIMEEnabled() { - return mInputConnectionListener != null && - mInputConnectionListener.isIMEEnabled(); - } - - public void importScript(final String url) { - if (url.startsWith("resource://android/assets/")) { - GeckoAppShell.notifyObservers("GeckoView:ImportScript", url); - return; - } - - throw new IllegalArgumentException("Must import script from 'resources://android/assets/' location."); - } - - private void handleReady(final JSONObject message) { - if (mChromeDelegate != null) { - mChromeDelegate.onReady(this); - } - } - - private void handleStateChange(final JSONObject message) throws JSONException { - int state = message.getInt("state"); - if ((state & GeckoAppShell.WPL_STATE_IS_NETWORK) != 0) { - if ((state & GeckoAppShell.WPL_STATE_START) != 0) { - if (mContentDelegate != null) { - int id = message.getInt("tabID"); - mContentDelegate.onPageStart(this, new Browser(id), message.getString("uri")); - } - } else if ((state & GeckoAppShell.WPL_STATE_STOP) != 0) { - if (mContentDelegate != null) { - int id = message.getInt("tabID"); - mContentDelegate.onPageStop(this, new Browser(id), message.getBoolean("success")); - } - } - } - } - - private void handleLoadError(final JSONObject message) throws JSONException { - if (mContentDelegate != null) { - int id = message.getInt("tabID"); - mContentDelegate.onPageStop(GeckoView.this, new Browser(id), false); - } - } - - private void handlePageShow(final JSONObject message) throws JSONException { - if (mContentDelegate != null) { - int id = message.getInt("tabID"); - mContentDelegate.onPageShow(GeckoView.this, new Browser(id)); - } - } - - private void handleTitleChanged(final JSONObject message) throws JSONException { - if (mContentDelegate != null) { - int id = message.getInt("tabID"); - mContentDelegate.onReceivedTitle(GeckoView.this, new Browser(id), message.getString("title")); - } - } - - private void handleLinkFavicon(final JSONObject message) throws JSONException { - if (mContentDelegate != null) { - int id = message.getInt("tabID"); - mContentDelegate.onReceivedFavicon(GeckoView.this, new Browser(id), message.getString("href"), message.getInt("size")); - } - } - - private void handlePrompt(final JSONObject message) throws JSONException { - if (mChromeDelegate != null) { - String hint = message.optString("hint"); - if ("alert".equals(hint)) { - String text = message.optString("text"); - mChromeDelegate.onAlert(GeckoView.this, null, text, new PromptResult(message)); - } else if ("confirm".equals(hint)) { - String text = message.optString("text"); - mChromeDelegate.onConfirm(GeckoView.this, null, text, new PromptResult(message)); - } else if ("prompt".equals(hint)) { - String text = message.optString("text"); - String defaultValue = message.optString("textbox0"); - mChromeDelegate.onPrompt(GeckoView.this, null, text, defaultValue, new PromptResult(message)); - } else if ("remotedebug".equals(hint)) { - mChromeDelegate.onDebugRequest(GeckoView.this, new PromptResult(message)); - } - } - } - - private void handleScriptMessage(final Bundle data, final EventCallback callback) { - if (mChromeDelegate != null) { - MessageResult result = null; - if (callback != null) { - result = new MessageResult(callback); - } - mChromeDelegate.onScriptMessage(GeckoView.this, data, result); - } - } - - /** - * Set the chrome callback handler. - * This will replace the current handler. - * @param chrome An implementation of GeckoViewChrome. - */ - public void setChromeDelegate(ChromeDelegate chrome) { - mChromeDelegate = chrome; - } - - /** - * Set the content callback handler. - * This will replace the current handler. - * @param content An implementation of ContentDelegate. - */ - public void setContentDelegate(ContentDelegate content) { - mContentDelegate = content; - } - - public static void setGeckoInterface(final BaseGeckoInterface geckoInterface) { - GeckoAppShell.setGeckoInterface(geckoInterface); - } - - public static GeckoAppShell.GeckoInterface getGeckoInterface() { - return GeckoAppShell.getGeckoInterface(); - } - - protected String getSharedPreferencesFile() { - return DEFAULT_SHARED_PREFERENCES_FILE; - } - - @Override - public SharedPreferences getSharedPreferences() { - return getContext().getSharedPreferences(getSharedPreferencesFile(), 0); - } - - /** - * Wrapper for a browser in the GeckoView container. Associated with a browser - * element in the Gecko system. - */ - public class Browser { - private final int mId; - private Browser(int Id) { - mId = Id; - } - - /** - * Get the ID of the Browser. This is the same ID used by Gecko for it's underlying - * browser element. - * @return The integer ID of the Browser. - */ - private int getId() { - return mId; - } - - /** - * Load a URL resource into the Browser. - * @param url The URL string. - */ - public void loadUrl(String url) { - JSONObject args = new JSONObject(); - try { - args.put("url", url); - args.put("parentId", -1); - args.put("newTab", false); - args.put("tabID", mId); - } catch (Exception e) { - Log.w(LOGTAG, "Error building JSON arguments for loadUrl.", e); - } - GeckoAppShell.notifyObservers("Tab:Load", args.toString()); - } - } - - /* Provides a means for the client to indicate whether a JavaScript - * dialog request should proceed. An instance of this class is passed to - * various GeckoViewChrome callback actions. - */ - public class PromptResult { - private final int RESULT_OK = 0; - private final int RESULT_CANCEL = 1; - - private final JSONObject mMessage; - - public PromptResult(JSONObject message) { - mMessage = message; - } - - private JSONObject makeResult(int resultCode) { - JSONObject result = new JSONObject(); - try { - result.put("button", resultCode); - } catch (JSONException ex) { } - return result; - } - - /** - * Handle a confirmation response from the user. - */ - public void confirm() { - JSONObject result = makeResult(RESULT_OK); - EventDispatcher.sendResponse(mMessage, result); - } - - /** - * Handle a confirmation response from the user. - * @param value String value to return to the browser context. - */ - public void confirmWithValue(String value) { - JSONObject result = makeResult(RESULT_OK); - try { - result.put("textbox0", value); - } catch (JSONException ex) { } - EventDispatcher.sendResponse(mMessage, result); - } - - /** - * Handle a cancellation response from the user. - */ - public void cancel() { - JSONObject result = makeResult(RESULT_CANCEL); - EventDispatcher.sendResponse(mMessage, result); - } - } - - /* Provides a means for the client to respond to a script message with some data. - * An instance of this class is passed to GeckoViewChrome.onScriptMessage. - */ - public class MessageResult { - private final EventCallback mCallback; - - public MessageResult(EventCallback callback) { - if (callback == null) { - throw new IllegalArgumentException("EventCallback should not be null."); - } - mCallback = callback; - } - - private JSONObject bundleToJSON(Bundle data) { - JSONObject result = new JSONObject(); - if (data == null) { - return result; - } - - final Set<String> keys = data.keySet(); - for (String key : keys) { - try { - result.put(key, data.get(key)); - } catch (JSONException e) { - } - } - return result; - } - - /** - * Handle a successful response to a script message. - * @param value Bundle value to return to the script context. - */ - public void success(Bundle data) { - mCallback.sendSuccess(bundleToJSON(data)); - } - - /** - * Handle a failure response to a script message. - */ - public void failure(Bundle data) { - mCallback.sendError(bundleToJSON(data)); - } - } - - public interface ChromeDelegate { - /** - * Tell the host application that Gecko is ready to handle requests. - * @param view The GeckoView that initiated the callback. - */ - public void onReady(GeckoView view); - - /** - * Tell the host application to display an alert dialog. - * @param view The GeckoView that initiated the callback. - * @param browser The Browser that is loading the content. - * @param message The string to display in the dialog. - * @param result A PromptResult used to send back the result without blocking. - * Defaults to cancel requests. - */ - public void onAlert(GeckoView view, GeckoView.Browser browser, String message, GeckoView.PromptResult result); - - /** - * Tell the host application to display a confirmation dialog. - * @param view The GeckoView that initiated the callback. - * @param browser The Browser that is loading the content. - * @param message The string to display in the dialog. - * @param result A PromptResult used to send back the result without blocking. - * Defaults to cancel requests. - */ - public void onConfirm(GeckoView view, GeckoView.Browser browser, String message, GeckoView.PromptResult result); - - /** - * Tell the host application to display an input prompt dialog. - * @param view The GeckoView that initiated the callback. - * @param browser The Browser that is loading the content. - * @param message The string to display in the dialog. - * @param defaultValue The string to use as default input. - * @param result A PromptResult used to send back the result without blocking. - * Defaults to cancel requests. - */ - public void onPrompt(GeckoView view, GeckoView.Browser browser, String message, String defaultValue, GeckoView.PromptResult result); - - /** - * Tell the host application to display a remote debugging request dialog. - * @param view The GeckoView that initiated the callback. - * @param result A PromptResult used to send back the result without blocking. - * Defaults to cancel requests. - */ - public void onDebugRequest(GeckoView view, GeckoView.PromptResult result); - - /** - * Receive a message from an imported script. - * @param view The GeckoView that initiated the callback. - * @param data Bundle of data sent with the message. Never null. - * @param result A MessageResult used to send back a response without blocking. Can be null. - * Defaults to do nothing. - */ - public void onScriptMessage(GeckoView view, Bundle data, GeckoView.MessageResult result); - } - - public interface ContentDelegate { - /** - * A Browser has started loading content from the network. - * @param view The GeckoView that initiated the callback. - * @param browser The Browser that is loading the content. - * @param url The resource being loaded. - */ - public void onPageStart(GeckoView view, GeckoView.Browser browser, String url); - - /** - * A Browser has finished loading content from the network. - * @param view The GeckoView that initiated the callback. - * @param browser The Browser that was loading the content. - * @param success Whether the page loaded successfully or an error occurred. - */ - public void onPageStop(GeckoView view, GeckoView.Browser browser, boolean success); - - /** - * A Browser is displaying content. This page could have been loaded via - * network or from the session history. - * @param view The GeckoView that initiated the callback. - * @param browser The Browser that is showing the content. - */ - public void onPageShow(GeckoView view, GeckoView.Browser browser); - - /** - * A page title was discovered in the content or updated after the content - * loaded. - * @param view The GeckoView that initiated the callback. - * @param browser The Browser that is showing the content. - * @param title The title sent from the content. - */ - public void onReceivedTitle(GeckoView view, GeckoView.Browser browser, String title); - - /** - * A link element was discovered in the content or updated after the content - * loaded that specifies a favicon. - * @param view The GeckoView that initiated the callback. - * @param browser The Browser that is showing the content. - * @param url The href of the link element specifying the favicon. - * @param size The maximum size specified for the favicon, or -1 for any size. - */ - public void onReceivedFavicon(GeckoView view, GeckoView.Browser browser, String url, int size); - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoViewChrome.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoViewChrome.java deleted file mode 100644 index 403c6dbca..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoViewChrome.java +++ /dev/null @@ -1,81 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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; - -import android.os.Bundle; - -public class GeckoViewChrome implements GeckoView.ChromeDelegate { - /** - * Tell the host application that Gecko is ready to handle requests. - * @param view The GeckoView that initiated the callback. - */ - @Override - public void onReady(GeckoView view) {} - - /** - * Tell the host application to display an alert dialog. - * @param view The GeckoView that initiated the callback. - * @param browser The Browser that is loading the content. - * @param message The string to display in the dialog. - * @param result A PromptResult used to send back the result without blocking. - * Defaults to cancel requests. - */ - @Override - public void onAlert(GeckoView view, GeckoView.Browser browser, String message, GeckoView.PromptResult result) { - result.cancel(); - } - - /** - * Tell the host application to display a confirmation dialog. - * @param view The GeckoView that initiated the callback. - * @param browser The Browser that is loading the content. - * @param message The string to display in the dialog. - * @param result A PromptResult used to send back the result without blocking. - * Defaults to cancel requests. - */ - @Override - public void onConfirm(GeckoView view, GeckoView.Browser browser, String message, GeckoView.PromptResult result) { - result.cancel(); - } - - /** - * Tell the host application to display an input prompt dialog. - * @param view The GeckoView that initiated the callback. - * @param browser The Browser that is loading the content. - * @param message The string to display in the dialog. - * @param defaultValue The string to use as default input. - * @param result A PromptResult used to send back the result without blocking. - * Defaults to cancel requests. - */ - @Override - public void onPrompt(GeckoView view, GeckoView.Browser browser, String message, String defaultValue, GeckoView.PromptResult result) { - result.cancel(); - } - - /** - * Tell the host application to display a remote debugging request dialog. - * @param view The GeckoView that initiated the callback. - * @param result A PromptResult used to send back the result without blocking. - * Defaults to cancel requests. - */ - @Override - public void onDebugRequest(GeckoView view, GeckoView.PromptResult result) { - result.cancel(); - } - - /** - * Receive a message from an imported script. - * @param view The GeckoView that initiated the callback. - * @param data Bundle of data sent with the message. Never null. - * @param result A MessageResult used to send back a response without blocking. Can be null. - * Defaults to cancel requests with a failed response. - */ - public void onScriptMessage(GeckoView view, Bundle data, GeckoView.MessageResult result) { - if (result != null) { - result.failure(null); - } - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoViewContent.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoViewContent.java deleted file mode 100644 index 22d0ede75..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoViewContent.java +++ /dev/null @@ -1,56 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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; - -public class GeckoViewContent implements GeckoView.ContentDelegate { - /** - * A Browser has started loading content from the network. - * @param view The GeckoView that initiated the callback. - * @param browser The Browser that is loading the content. - * @param url The resource being loaded. - */ - @Override - public void onPageStart(GeckoView view, GeckoView.Browser browser, String url) {} - - /** - * A Browser has finished loading content from the network. - * @param view The GeckoView that initiated the callback. - * @param browser The Browser that was loading the content. - * @param success Whether the page loaded successfully or an error occurred. - */ - @Override - public void onPageStop(GeckoView view, GeckoView.Browser browser, boolean success) {} - - /** - * A Browser is displaying content. This page could have been loaded via - * network or from the session history. - * @param view The GeckoView that initiated the callback. - * @param browser The Browser that is showing the content. - */ - @Override - public void onPageShow(GeckoView view, GeckoView.Browser browser) {} - - /** - * A page title was discovered in the content or updated after the content - * loaded. - * @param view The GeckoView that initiated the callback. - * @param browser The Browser that is showing the content. - * @param title The title sent from the content. - */ - @Override - public void onReceivedTitle(GeckoView view, GeckoView.Browser browser, String title) {} - - /** - * A link element was discovered in the content or updated after the content - * loaded that specifies a favicon. - * @param view The GeckoView that initiated the callback. - * @param browser The Browser that is showing the content. - * @param url The href of the link element specifying the favicon. - * @param size The maximum size specified for the favicon, or -1 for any size. - */ - @Override - public void onReceivedFavicon(GeckoView view, GeckoView.Browser browser, String url, int size) {} -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoViewFragment.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoViewFragment.java deleted file mode 100644 index 51320636e..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoViewFragment.java +++ /dev/null @@ -1,52 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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; - -import android.support.v4.app.Fragment; -import android.os.Bundle; -import android.os.Parcelable; -import android.util.Log; -import android.util.SparseArray; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -public class GeckoViewFragment extends android.support.v4.app.Fragment { - private static final String LOGTAG = "GeckoViewFragment"; - - private static Parcelable state = null; - private static GeckoViewFragment lastUsed = null; - private GeckoView geckoView = null; - - @Override - public void onCreate(Bundle savedInstanceState) { - setRetainInstance(true); - super.onCreate(savedInstanceState); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - geckoView = new GeckoView(getContext()); - return geckoView; - } - - @Override - public void onResume() { - if (state != null && lastUsed != this) { - // "Restore" the window from the previously used GeckoView to this GeckoView and attach it - geckoView.onRestoreInstanceState(state); - state = null; - } - super.onResume(); - } - - @Override - public void onPause() { - state = geckoView.onSaveInstanceState(); - lastUsed = this; - super.onPause(); - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/InputConnectionListener.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/InputConnectionListener.java deleted file mode 100644 index baddc4ed2..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/InputConnectionListener.java +++ /dev/null @@ -1,25 +0,0 @@ -/* 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; - -import android.os.Handler; -import android.view.KeyEvent; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputConnection; - -/** - * Interface for interacting with GeckoInputConnection from GeckoView. - */ -interface InputConnectionListener -{ - Handler getHandler(Handler defHandler); - InputConnection onCreateInputConnection(EditorInfo outAttrs); - boolean onKeyPreIme(int keyCode, KeyEvent event); - boolean onKeyDown(int keyCode, KeyEvent event); - boolean onKeyLongPress(int keyCode, KeyEvent event); - boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event); - boolean onKeyUp(int keyCode, KeyEvent event); - boolean isIMEEnabled(); -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/InputMethods.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/InputMethods.java deleted file mode 100644 index 57649b0da..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/InputMethods.java +++ /dev/null @@ -1,76 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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; - -import java.util.Collection; - -import org.mozilla.gecko.AppConstants.Versions; - -import android.content.Context; -import android.provider.Settings.Secure; -import android.view.inputmethod.InputMethodInfo; -import android.view.inputmethod.InputMethodManager; - -final public class InputMethods { - public static final String METHOD_ANDROID_LATINIME = "com.android.inputmethod.latin/.LatinIME"; - public static final String METHOD_ATOK = "com.justsystems.atokmobile.service/.AtokInputMethodService"; - public static final String METHOD_GOOGLE_JAPANESE_INPUT = "com.google.android.inputmethod.japanese/.MozcService"; - public static final String METHOD_GOOGLE_LATINIME = "com.google.android.inputmethod.latin/com.android.inputmethod.latin.LatinIME"; - public static final String METHOD_HTC_TOUCH_INPUT = "com.htc.android.htcime/.HTCIMEService"; - public static final String METHOD_IWNN = "jp.co.omronsoft.iwnnime.ml/.standardcommon.IWnnLanguageSwitcher"; - public static final String METHOD_OPENWNN_PLUS = "com.owplus.ime.openwnnplus/.OpenWnnJAJP"; - public static final String METHOD_SAMSUNG = "com.sec.android.inputmethod/.SamsungKeypad"; - public static final String METHOD_SIMEJI = "com.adamrocker.android.input.simeji/.OpenWnnSimeji"; - public static final String METHOD_SWIFTKEY = "com.touchtype.swiftkey/com.touchtype.KeyboardService"; - public static final String METHOD_SWYPE = "com.swype.android.inputmethod/.SwypeInputMethod"; - public static final String METHOD_SWYPE_BETA = "com.nuance.swype.input/.IME"; - public static final String METHOD_TOUCHPAL_KEYBOARD = "com.cootek.smartinputv5/com.cootek.smartinput5.TouchPalIME"; - - private InputMethods() {} - - public static String getCurrentInputMethod(Context context) { - String inputMethod = Secure.getString(context.getContentResolver(), Secure.DEFAULT_INPUT_METHOD); - return (inputMethod != null ? inputMethod : ""); - } - - public static InputMethodInfo getInputMethodInfo(Context context, String inputMethod) { - InputMethodManager imm = getInputMethodManager(context); - Collection<InputMethodInfo> infos = imm.getEnabledInputMethodList(); - for (InputMethodInfo info : infos) { - if (info.getId().equals(inputMethod)) { - return info; - } - } - return null; - } - - public static InputMethodManager getInputMethodManager(Context context) { - return (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); - } - - public static boolean needsSoftResetWorkaround(String inputMethod) { - // Stock latin IME on Android 4.2 and above - return Versions.feature17Plus && - (METHOD_ANDROID_LATINIME.equals(inputMethod) || - METHOD_GOOGLE_LATINIME.equals(inputMethod)); - } - - public static boolean shouldCommitCharAsKey(String inputMethod) { - return METHOD_HTC_TOUCH_INPUT.equals(inputMethod); - } - - public static boolean isGestureKeyboard(Context context) { - // SwiftKey is a gesture keyboard, but it doesn't seem to need any special-casing - // to do AwesomeBar auto-spacing. - String inputMethod = getCurrentInputMethod(context); - return (Versions.feature17Plus && - (METHOD_ANDROID_LATINIME.equals(inputMethod) || - METHOD_GOOGLE_LATINIME.equals(inputMethod))) || - METHOD_SWYPE.equals(inputMethod) || - METHOD_SWYPE_BETA.equals(inputMethod) || - METHOD_TOUCHPAL_KEYBOARD.equals(inputMethod); - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/NSSBridge.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/NSSBridge.java deleted file mode 100644 index 8d525b0ba..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/NSSBridge.java +++ /dev/null @@ -1,55 +0,0 @@ -/* 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; - -import org.mozilla.gecko.mozglue.GeckoLoader; - -import android.content.Context; -import org.mozilla.gecko.annotation.RobocopTarget; - -public class NSSBridge { - private static final String LOGTAG = "NSSBridge"; - - private static native String nativeEncrypt(String aDb, String aValue); - private static native String nativeDecrypt(String aDb, String aValue); - - @RobocopTarget - static public String encrypt(Context context, String aValue) - throws Exception { - String resourcePath = context.getPackageResourcePath(); - GeckoLoader.loadNSSLibs(context, resourcePath); - - String path = GeckoProfile.get(context).getDir().toString(); - return nativeEncrypt(path, aValue); - } - - @RobocopTarget - static public String encrypt(Context context, String profilePath, String aValue) - throws Exception { - String resourcePath = context.getPackageResourcePath(); - GeckoLoader.loadNSSLibs(context, resourcePath); - - return nativeEncrypt(profilePath, aValue); - } - - @RobocopTarget - static public String decrypt(Context context, String aValue) - throws Exception { - String resourcePath = context.getPackageResourcePath(); - GeckoLoader.loadNSSLibs(context, resourcePath); - - String path = GeckoProfile.get(context).getDir().toString(); - return nativeDecrypt(path, aValue); - } - - @RobocopTarget - static public String decrypt(Context context, String profilePath, String aValue) - throws Exception { - String resourcePath = context.getPackageResourcePath(); - GeckoLoader.loadNSSLibs(context, resourcePath); - - return nativeDecrypt(profilePath, aValue); - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/NotificationListener.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/NotificationListener.java deleted file mode 100644 index 85a68768f..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/NotificationListener.java +++ /dev/null @@ -1,17 +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; - -public interface NotificationListener -{ - void showNotification(String name, String cookie, String title, String text, - String host, String imageUrl); - - void showPersistentNotification(String name, String cookie, String title, String text, - String host, String imageUrl, String data); - - void closeNotification(String name); -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/PrefsHelper.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/PrefsHelper.java deleted file mode 100644 index b60f6fd88..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/PrefsHelper.java +++ /dev/null @@ -1,308 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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; - -import org.mozilla.gecko.annotation.RobocopTarget; -import org.mozilla.gecko.annotation.WrapForJNI; - -import android.support.v4.util.SimpleArrayMap; -import android.util.Log; -import android.util.SparseArray; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; - -/** - * Helper class to get/set gecko prefs. - */ -public final class PrefsHelper { - private static final String LOGTAG = "GeckoPrefsHelper"; - - // Map pref name to ArrayList for multiple observers or PrefHandler for single observer. - private static final SimpleArrayMap<String, Object> OBSERVERS = new SimpleArrayMap<>(); - private static final HashSet<String> INT_TO_STRING_PREFS = new HashSet<>(8); - private static final HashSet<String> INT_TO_BOOL_PREFS = new HashSet<>(2); - - static { - INT_TO_STRING_PREFS.add("browser.chrome.titlebarMode"); - INT_TO_STRING_PREFS.add("network.cookie.cookieBehavior"); - INT_TO_STRING_PREFS.add("font.size.inflation.minTwips"); - INT_TO_STRING_PREFS.add("home.sync.updateMode"); - INT_TO_STRING_PREFS.add("browser.image_blocking"); - INT_TO_BOOL_PREFS.add("browser.display.use_document_fonts"); - } - - @WrapForJNI - private static final int PREF_INVALID = -1; - @WrapForJNI - private static final int PREF_FINISH = 0; - @WrapForJNI - private static final int PREF_BOOL = 1; - @WrapForJNI - private static final int PREF_INT = 2; - @WrapForJNI - private static final int PREF_STRING = 3; - - @WrapForJNI(stubName = "GetPrefs", dispatchTo = "gecko") - private static native void nativeGetPrefs(String[] prefNames, PrefHandler handler); - @WrapForJNI(stubName = "SetPref", dispatchTo = "gecko") - private static native void nativeSetPref(String prefName, boolean flush, int type, - boolean boolVal, int intVal, String strVal); - @WrapForJNI(stubName = "AddObserver", dispatchTo = "gecko") - private static native void nativeAddObserver(String[] prefNames, PrefHandler handler, - String[] prefsToObserve); - @WrapForJNI(stubName = "RemoveObserver", dispatchTo = "gecko") - private static native void nativeRemoveObserver(String[] prefToUnobserve); - - @RobocopTarget - public static void getPrefs(final String[] prefNames, final PrefHandler callback) { - if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) { - nativeGetPrefs(prefNames, callback); - } else { - GeckoThread.queueNativeCallUntil( - GeckoThread.State.PROFILE_READY, PrefsHelper.class, "nativeGetPrefs", - String[].class, prefNames, PrefHandler.class, callback); - } - } - - public static void getPref(final String prefName, final PrefHandler callback) { - getPrefs(new String[] { prefName }, callback); - } - - public static void getPrefs(final ArrayList<String> prefNames, final PrefHandler callback) { - getPrefs(prefNames.toArray(new String[prefNames.size()]), callback); - } - - @RobocopTarget - public static void setPref(final String pref, final Object value, final boolean flush) { - final int type; - boolean boolVal = false; - int intVal = 0; - String strVal = null; - - if (INT_TO_STRING_PREFS.contains(pref)) { - // When sending to Java, we normalized special preferences that use integers - // and strings to represent booleans. Here, we convert them back to their - // actual types so we can store them. - type = PREF_INT; - intVal = Integer.parseInt(String.valueOf(value)); - } else if (INT_TO_BOOL_PREFS.contains(pref)) { - type = PREF_INT; - intVal = (Boolean) value ? 1 : 0; - } else if (value instanceof Boolean) { - type = PREF_BOOL; - boolVal = (Boolean) value; - } else if (value instanceof Integer) { - type = PREF_INT; - intVal = (Integer) value; - } else { - type = PREF_STRING; - strVal = String.valueOf(value); - } - - if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) { - nativeSetPref(pref, flush, type, boolVal, intVal, strVal); - } else { - GeckoThread.queueNativeCallUntil( - GeckoThread.State.PROFILE_READY, PrefsHelper.class, "nativeSetPref", - String.class, pref, flush, type, boolVal, intVal, String.class, strVal); - } - } - - public static void setPref(final String pref, final Object value) { - setPref(pref, value, /* flush */ false); - } - - @RobocopTarget - public synchronized static void addObserver(final String[] prefNames, - final PrefHandler handler) { - List<String> prefsToObserve = null; - - for (String pref : prefNames) { - final Object existing = OBSERVERS.get(pref); - - if (existing == null) { - // Not observing yet, so add observer. - if (prefsToObserve == null) { - prefsToObserve = new ArrayList<>(prefNames.length); - } - prefsToObserve.add(pref); - OBSERVERS.put(pref, handler); - - } else if (existing instanceof PrefHandler) { - // Already observing one, so turn it into an array. - final List<PrefHandler> handlerList = new ArrayList<>(2); - handlerList.add((PrefHandler) existing); - handlerList.add(handler); - OBSERVERS.put(pref, handlerList); - - } else { - // Already observing multiple, so add to existing array. - @SuppressWarnings("unchecked") - final List<PrefHandler> handlerList = (List) existing; - handlerList.add(handler); - } - } - - final String[] namesToObserve = prefsToObserve == null ? null : - prefsToObserve.toArray(new String[prefsToObserve.size()]); - - if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) { - nativeAddObserver(prefNames, handler, namesToObserve); - } else { - GeckoThread.queueNativeCallUntil( - GeckoThread.State.PROFILE_READY, PrefsHelper.class, "nativeAddObserver", - String[].class, prefNames, PrefHandler.class, handler, - String[].class, namesToObserve); - } - } - - @RobocopTarget - public synchronized static void removeObserver(final PrefHandler handler) { - List<String> prefsToUnobserve = null; - - for (int i = OBSERVERS.size() - 1; i >= 0; i--) { - final Object existing = OBSERVERS.valueAt(i); - boolean removeObserver = false; - - if (existing == handler) { - removeObserver = true; - - } else if (!(existing instanceof PrefHandler)) { - // Removing existing handler from list. - @SuppressWarnings("unchecked") - final List<PrefHandler> handlerList = (List) existing; - if (handlerList.remove(handler) && handlerList.isEmpty()) { - removeObserver = true; - } - } - - if (removeObserver) { - // Removed last handler, so remove observer. - if (prefsToUnobserve == null) { - prefsToUnobserve = new ArrayList<>(); - } - prefsToUnobserve.add(OBSERVERS.keyAt(i)); - OBSERVERS.removeAt(i); - } - } - - if (prefsToUnobserve == null) { - return; - } - - final String[] namesToUnobserve = - prefsToUnobserve.toArray(new String[prefsToUnobserve.size()]); - - if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) { - nativeRemoveObserver(namesToUnobserve); - } else { - GeckoThread.queueNativeCallUntil( - GeckoThread.State.PROFILE_READY, PrefsHelper.class, "nativeRemoveObserver", - String[].class, namesToUnobserve); - } - } - - @WrapForJNI(calledFrom = "gecko") - private static void callPrefHandler(final PrefHandler handler, int type, final String pref, - boolean boolVal, int intVal, String strVal) { - - // Some Gecko preferences use integers or strings to reference state instead of - // directly representing the value. Since the Java UI uses the type to determine - // which ui elements to show and how to handle them, we need to normalize these - // preferences to the correct type. - if (INT_TO_STRING_PREFS.contains(pref)) { - type = PREF_STRING; - strVal = String.valueOf(intVal); - } else if (INT_TO_BOOL_PREFS.contains(pref)) { - type = PREF_BOOL; - boolVal = intVal == 1; - } - - switch (type) { - case PREF_FINISH: - handler.finish(); - return; - case PREF_BOOL: - handler.prefValue(pref, boolVal); - return; - case PREF_INT: - handler.prefValue(pref, intVal); - return; - case PREF_STRING: - handler.prefValue(pref, strVal); - return; - } - throw new IllegalArgumentException(); - } - - @WrapForJNI(calledFrom = "gecko") - private synchronized static void onPrefChange(final String pref, final int type, - final boolean boolVal, final int intVal, - final String strVal) { - final Object existing = OBSERVERS.get(pref); - - if (existing == null) { - return; - } - - final Iterator<PrefHandler> itor; - PrefHandler handler; - - if (existing instanceof PrefHandler) { - itor = null; - handler = (PrefHandler) existing; - } else { - @SuppressWarnings("unchecked") - final List<PrefHandler> handlerList = (List) existing; - if (handlerList.isEmpty()) { - return; - } - itor = handlerList.iterator(); - handler = itor.next(); - } - - do { - callPrefHandler(handler, type, pref, boolVal, intVal, strVal); - handler.finish(); - - handler = itor != null && itor.hasNext() ? itor.next() : null; - } while (handler != null); - } - - public interface PrefHandler { - void prefValue(String pref, boolean value); - void prefValue(String pref, int value); - void prefValue(String pref, String value); - void finish(); - } - - public static abstract class PrefHandlerBase implements PrefHandler { - @Override - public void prefValue(String pref, boolean value) { - throw new UnsupportedOperationException( - "Unhandled boolean pref " + pref + "; wrong type?"); - } - - @Override - public void prefValue(String pref, int value) { - throw new UnsupportedOperationException( - "Unhandled int pref " + pref + "; wrong type?"); - } - - @Override - public void prefValue(String pref, String value) { - throw new UnsupportedOperationException( - "Unhandled String pref " + pref + "; wrong type?"); - } - - @Override - public void finish() { - } - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/SysInfo.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/SysInfo.java deleted file mode 100644 index 5c53ef465..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/SysInfo.java +++ /dev/null @@ -1,237 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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; - -import android.os.StrictMode; -import android.util.Log; - -import java.io.File; -import java.io.FileFilter; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; - -import java.util.regex.Pattern; - -/** - * A collection of system info values, broadly mirroring a subset of - * nsSystemInfo. See also the constants in AppConstants, which reflect - * much of nsIXULAppInfo. - */ -// Normally, we'd annotate with @RobocopTarget. Since SysInfo is compiled -// before RobocopTarget, we instead add o.m.g.SysInfo directly to the Proguard -// configuration. -public final class SysInfo { - private static final String LOG_TAG = "GeckoSysInfo"; - - // Number of bytes of /proc/meminfo to read in one go. - private static final int MEMINFO_BUFFER_SIZE_BYTES = 256; - - // We don't mind an instant of possible duplicate work, we only wish to - // avoid inconsistency, so we don't bother with synchronization for - // these. - private static volatile int cpuCount = -1; - - private static volatile int totalRAM = -1; - - /** - * Get the number of cores on the device. - * - * We can't use a nice tidy API call, because they're all wrong: - * - * <http://stackoverflow.com/questions/7962155/how-can-you-detect-a-dual-core- - * cpu-on-an-android-device-from-code> - * - * This method is based on that code. - * - * @return the number of CPU cores, or 1 if the number could not be - * determined. - */ - public static int getCPUCount() { - if (cpuCount > 0) { - return cpuCount; - } - - // Avoid a strict mode warning. - StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads(); - try { - return readCPUCount(); - } finally { - StrictMode.setThreadPolicy(savedPolicy); - } - } - - private static int readCPUCount() { - class CpuFilter implements FileFilter { - @Override - public boolean accept(File pathname) { - return Pattern.matches("cpu[0-9]+", pathname.getName()); - } - } - try { - final File dir = new File("/sys/devices/system/cpu/"); - return cpuCount = dir.listFiles(new CpuFilter()).length; - } catch (Exception e) { - Log.w(LOG_TAG, "Assuming 1 CPU; got exception.", e); - return cpuCount = 1; - } - } - - /** - * Helper functions used to extract key/value data from /proc/meminfo - * Pulled from: - * http://androidxref.com/4.2_r1/xref/frameworks/base/core/java/com/android/internal/util/MemInfoReader.java - */ - private static boolean matchMemText(byte[] buffer, int index, int bufferLength, byte[] text) { - final int N = text.length; - if ((index + N) >= bufferLength) { - return false; - } - for (int i = 0; i < N; i++) { - if (buffer[index + i] != text[i]) { - return false; - } - } - return true; - } - - /** - * Parses a line like: - * - * MemTotal: 1605324 kB - * - * into 1605324. - * - * @return the first uninterrupted sequence of digits following the - * specified index, parsed as an integer value in KB. - */ - private static int extractMemValue(byte[] buffer, int offset, int length) { - if (offset >= length) { - return 0; - } - - while (offset < length && buffer[offset] != '\n') { - if (buffer[offset] >= '0' && buffer[offset] <= '9') { - int start = offset++; - while (offset < length && - buffer[offset] >= '0' && - buffer[offset] <= '9') { - ++offset; - } - return Integer.parseInt(new String(buffer, start, offset - start), 10); - } - ++offset; - } - return 0; - } - - /** - * Fetch the total memory of the device in MB by parsing /proc/meminfo. - * - * Of course, Android doesn't have a neat and tidy way to find total - * RAM, so we do it by parsing /proc/meminfo. - * - * @return 0 if a problem occurred, or memory size in MB. - */ - public static int getMemSize() { - if (totalRAM >= 0) { - return totalRAM; - } - - // This is the string "MemTotal" that we're searching for in the buffer. - final byte[] MEMTOTAL = {'M', 'e', 'm', 'T', 'o', 't', 'a', 'l'}; - - // `/proc/meminfo` is not a real file and thus safe to read on the main thread. - final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads(); - try { - final byte[] buffer = new byte[MEMINFO_BUFFER_SIZE_BYTES]; - final FileInputStream is = new FileInputStream("/proc/meminfo"); - try { - final int length = is.read(buffer); - - for (int i = 0; i < length; i++) { - if (matchMemText(buffer, i, length, MEMTOTAL)) { - i += 8; - totalRAM = extractMemValue(buffer, i, length) / 1024; - Log.d(LOG_TAG, "System memory: " + totalRAM + "MB."); - return totalRAM; - } - } - } finally { - is.close(); - } - - Log.w(LOG_TAG, "Did not find MemTotal line in /proc/meminfo."); - return totalRAM = 0; - } catch (FileNotFoundException f) { - return totalRAM = 0; - } catch (IOException e) { - return totalRAM = 0; - } finally { - StrictMode.setThreadPolicy(savedPolicy); - } - } - - /** - * @return the SDK version supported by this device, such as '16'. - */ - public static int getVersion() { - return android.os.Build.VERSION.SDK_INT; - } - - /** - * @return the release version string, such as "4.1.2". - */ - public static String getReleaseVersion() { - return android.os.Build.VERSION.RELEASE; - } - - /** - * @return the kernel version string, such as "3.4.10-geb45596". - */ - public static String getKernelVersion() { - return System.getProperty("os.version", ""); - } - - /** - * @return the device manufacturer, such as "HTC". - */ - public static String getManufacturer() { - return android.os.Build.MANUFACTURER; - } - - /** - * @return the device name, such as "HTC One". - */ - public static String getDevice() { - // No, not android.os.Build.DEVICE. - return android.os.Build.MODEL; - } - - /** - * @return the Android "hardware" identifier, such as "m7". - */ - public static String getHardware() { - return android.os.Build.HARDWARE; - } - - /** - * @return the system OS name. Hardcoded to "Android". - */ - public static String getName() { - // We deliberately differ from PR_SI_SYSNAME, which is "Linux". - return "Android"; - } - - /** - * @return the Android architecture string, including ABI. - */ - public static String getArchABI() { - // Android likes to include the ABI, too ("armeabiv7"), so we - // differ to add value. - return android.os.Build.CPU_ABI; - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/TouchEventInterceptor.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/TouchEventInterceptor.java deleted file mode 100644 index 41a71dfa5..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/TouchEventInterceptor.java +++ /dev/null @@ -1,14 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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; - -import android.view.MotionEvent; -import android.view.View; - -public interface TouchEventInterceptor extends View.OnTouchListener { - /** Override this method for a chance to consume events before the view or its children */ - public boolean onInterceptTouchEvent(View view, MotionEvent event); -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/JNITarget.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/JNITarget.java deleted file mode 100644 index d6140a1ff..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/JNITarget.java +++ /dev/null @@ -1,14 +0,0 @@ -/* 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.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD}) -@Retention(RetentionPolicy.CLASS) -public @interface JNITarget {} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/ReflectionTarget.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/ReflectionTarget.java deleted file mode 100644 index e873ebeb9..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/ReflectionTarget.java +++ /dev/null @@ -1,18 +0,0 @@ -/* 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.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/* - * Used to indicate to ProGuard that this definition is accessed - * via reflection and should not be stripped from the source. - */ -@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD}) -@Retention(RetentionPolicy.CLASS) -public @interface ReflectionTarget {} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/RobocopTarget.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/RobocopTarget.java deleted file mode 100644 index e15130674..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/RobocopTarget.java +++ /dev/null @@ -1,15 +0,0 @@ -/* 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.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD}) -@Retention(RetentionPolicy.CLASS) -public @interface RobocopTarget {} - diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/WebRTCJNITarget.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/WebRTCJNITarget.java deleted file mode 100644 index f58dea148..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/WebRTCJNITarget.java +++ /dev/null @@ -1,14 +0,0 @@ -/* 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.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD}) -@Retention(RetentionPolicy.CLASS) -public @interface WebRTCJNITarget {} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/WrapForJNI.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/WrapForJNI.java deleted file mode 100644 index 358ed5d56..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/WrapForJNI.java +++ /dev/null @@ -1,51 +0,0 @@ -/* 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.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * This annotation is used to tag methods that are to have wrapper methods generated. - * Such methods will be protected from destruction by ProGuard, and allow us to avoid - * writing by hand large amounts of boring boilerplate. - */ -@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR}) -@Retention(RetentionPolicy.RUNTIME) -public @interface WrapForJNI { - /** - * Skip this member when generating wrappers for a whole class. - */ - boolean skip() default false; - - /** - * Optional parameter specifying the name of the generated method stub. If omitted, - * the capitalized name of the Java method will be used. - */ - String stubName() default ""; - - /** - * Action to take if member access returns an exception. - * One of "abort", "ignore", or "nsresult". "nsresult" is not supported for native - * methods. - */ - String exceptionMode() default "abort"; - - /** - * The thread that the method will be called from. - * One of "any", "gecko", or "ui". Not supported for fields. - */ - String calledFrom() default "any"; - - /** - * The thread that the method call will be dispatched to. - * One of "current", "gecko", or "proxy". Not supported for non-native methods, - * fields, and constructors. Only void-return methods are supported for anything other - * than current thread. - */ - String dispatchTo() default "current"; -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/BitmapUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/BitmapUtils.java deleted file mode 100644 index a4b516519..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/BitmapUtils.java +++ /dev/null @@ -1,290 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.gfx; - -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.util.Base64; -import android.util.Log; - -public final class BitmapUtils { - private static final String LOGTAG = "GeckoBitmapUtils"; - - private BitmapUtils() {} - - public static Bitmap decodeByteArray(byte[] bytes) { - return decodeByteArray(bytes, null); - } - - public static Bitmap decodeByteArray(byte[] bytes, BitmapFactory.Options options) { - return decodeByteArray(bytes, 0, bytes.length, options); - } - - public static Bitmap decodeByteArray(byte[] bytes, int offset, int length) { - return decodeByteArray(bytes, offset, length, null); - } - - public static Bitmap decodeByteArray(byte[] bytes, int offset, int length, BitmapFactory.Options options) { - if (bytes.length <= 0) { - throw new IllegalArgumentException("bytes.length " + bytes.length - + " must be a positive number"); - } - - Bitmap bitmap = null; - try { - bitmap = BitmapFactory.decodeByteArray(bytes, offset, length, options); - } catch (OutOfMemoryError e) { - Log.e(LOGTAG, "decodeByteArray(bytes.length=" + bytes.length - + ", options= " + options + ") OOM!", e); - return null; - } - - if (bitmap == null) { - Log.w(LOGTAG, "decodeByteArray() returning null because BitmapFactory returned null"); - return null; - } - - if (bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) { - Log.w(LOGTAG, "decodeByteArray() returning null because BitmapFactory returned " - + "a bitmap with dimensions " + bitmap.getWidth() - + "x" + bitmap.getHeight()); - return null; - } - - return bitmap; - } - - public static Bitmap decodeStream(InputStream inputStream) { - try { - return BitmapFactory.decodeStream(inputStream); - } catch (OutOfMemoryError e) { - Log.e(LOGTAG, "decodeStream() OOM!", e); - return null; - } - } - - public static Bitmap decodeUrl(Uri uri) { - return decodeUrl(uri.toString()); - } - - public static Bitmap decodeUrl(String urlString) { - URL url; - - try { - url = new URL(urlString); - } catch (MalformedURLException e) { - Log.w(LOGTAG, "decodeUrl: malformed URL " + urlString); - return null; - } - - return decodeUrl(url); - } - - public static Bitmap decodeUrl(URL url) { - InputStream stream = null; - - try { - stream = url.openStream(); - } catch (IOException e) { - Log.w(LOGTAG, "decodeUrl: IOException downloading " + url); - return null; - } - - if (stream == null) { - Log.w(LOGTAG, "decodeUrl: stream not found downloading " + url); - return null; - } - - Bitmap bitmap = decodeStream(stream); - - try { - stream.close(); - } catch (IOException e) { - Log.w(LOGTAG, "decodeUrl: IOException closing stream " + url, e); - } - - return bitmap; - } - - public static Bitmap decodeResource(Context context, int id) { - return decodeResource(context, id, null); - } - - public static Bitmap decodeResource(Context context, int id, BitmapFactory.Options options) { - Resources resources = context.getResources(); - try { - return BitmapFactory.decodeResource(resources, id, options); - } catch (OutOfMemoryError e) { - Log.e(LOGTAG, "decodeResource() OOM! Resource id=" + id, e); - return null; - } - } - - public static int getDominantColor(Bitmap source) { - return getDominantColor(source, true); - } - - public static int getDominantColor(Bitmap source, boolean applyThreshold) { - if (source == null) - return Color.argb(255, 255, 255, 255); - - // Keep track of how many times a hue in a given bin appears in the image. - // Hue values range [0 .. 360), so dividing by 10, we get 36 bins. - int[] colorBins = new int[36]; - - // The bin with the most colors. Initialize to -1 to prevent accidentally - // thinking the first bin holds the dominant color. - int maxBin = -1; - - // Keep track of sum hue/saturation/value per hue bin, which we'll use to - // compute an average to for the dominant color. - float[] sumHue = new float[36]; - float[] sumSat = new float[36]; - float[] sumVal = new float[36]; - float[] hsv = new float[3]; - - int height = source.getHeight(); - int width = source.getWidth(); - int[] pixels = new int[width * height]; - source.getPixels(pixels, 0, width, 0, 0, width, height); - for (int row = 0; row < height; row++) { - for (int col = 0; col < width; col++) { - int c = pixels[col + row * width]; - // Ignore pixels with a certain transparency. - if (Color.alpha(c) < 128) - continue; - - Color.colorToHSV(c, hsv); - - // If a threshold is applied, ignore arbitrarily chosen values for "white" and "black". - if (applyThreshold && (hsv[1] <= 0.35f || hsv[2] <= 0.35f)) - continue; - - // We compute the dominant color by putting colors in bins based on their hue. - int bin = (int) Math.floor(hsv[0] / 10.0f); - - // Update the sum hue/saturation/value for this bin. - sumHue[bin] = sumHue[bin] + hsv[0]; - sumSat[bin] = sumSat[bin] + hsv[1]; - sumVal[bin] = sumVal[bin] + hsv[2]; - - // Increment the number of colors in this bin. - colorBins[bin]++; - - // Keep track of the bin that holds the most colors. - if (maxBin < 0 || colorBins[bin] > colorBins[maxBin]) - maxBin = bin; - } - } - - // maxBin may never get updated if the image holds only transparent and/or black/white pixels. - if (maxBin < 0) - return Color.argb(255, 255, 255, 255); - - // Return a color with the average hue/saturation/value of the bin with the most colors. - hsv[0] = sumHue[maxBin] / colorBins[maxBin]; - hsv[1] = sumSat[maxBin] / colorBins[maxBin]; - hsv[2] = sumVal[maxBin] / colorBins[maxBin]; - return Color.HSVToColor(hsv); - } - - /** - * Decodes a bitmap from a Base64 data URI. - * - * @param dataURI a Base64-encoded data URI string - * @return the decoded bitmap, or null if the data URI is invalid - */ - public static Bitmap getBitmapFromDataURI(String dataURI) { - if (dataURI == null) { - return null; - } - - byte[] raw = getBytesFromDataURI(dataURI); - if (raw == null || raw.length == 0) { - return null; - } - - return decodeByteArray(raw); - } - - /** - * Return a byte[] containing the bytes in a given base64 string, or null if this is not a valid - * base64 string. - */ - public static byte[] getBytesFromBase64(String base64) { - try { - return Base64.decode(base64, Base64.DEFAULT); - } catch (Exception e) { - Log.e(LOGTAG, "exception decoding bitmap from data URI: " + base64, e); - } - - return null; - } - - public static byte[] getBytesFromDataURI(String dataURI) { - final String base64 = dataURI.substring(dataURI.indexOf(',') + 1); - return getBytesFromBase64(base64); - } - - public static Bitmap getBitmapFromDrawable(Drawable drawable) { - if (drawable instanceof BitmapDrawable) { - return ((BitmapDrawable) drawable).getBitmap(); - } - - int width = drawable.getIntrinsicWidth(); - width = width > 0 ? width : 1; - int height = drawable.getIntrinsicHeight(); - height = height > 0 ? height : 1; - - Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); - drawable.draw(canvas); - - return bitmap; - } - - public static int getResource(final Context context, final Uri resourceUrl) { - final String scheme = resourceUrl.getScheme(); - if (!"drawable".equals(scheme)) { - // Return a "not found" default icon that's easy to spot. - return android.R.drawable.sym_def_app_icon; - } - - String resource = resourceUrl.getSchemeSpecificPart(); - if (resource.startsWith("//")) { - resource = resource.substring(2); - } - - final Resources res = context.getResources(); - int id = res.getIdentifier(resource, "drawable", context.getPackageName()); - if (id != 0) { - return id; - } - - // For backwards compatibility, we also search in system resources. - id = res.getIdentifier(resource, "drawable", "android"); - if (id != 0) { - return id; - } - - Log.w(LOGTAG, "Cannot find drawable/" + resource); - // Return a "not found" default icon that's easy to spot. - return android.R.drawable.sym_def_app_icon; - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/BufferedImage.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/BufferedImage.java deleted file mode 100644 index 4dbcf61bb..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/BufferedImage.java +++ /dev/null @@ -1,94 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.gfx; - -import org.mozilla.gecko.mozglue.DirectBufferAllocator; - -import android.graphics.Bitmap; -import android.util.Log; - -import java.nio.ByteBuffer; - -/** A buffered image that simply saves a buffer of pixel data. */ -public class BufferedImage { - private ByteBuffer mBuffer; - private Bitmap mBitmap; - private IntSize mSize; - private int mFormat; - - private static final String LOGTAG = "GeckoBufferedImage"; - - /** Creates an empty buffered image */ - public BufferedImage() { - mSize = new IntSize(0, 0); - } - - /** Creates a buffered image from an Android bitmap. */ - public BufferedImage(Bitmap bitmap) { - mFormat = bitmapConfigToFormat(bitmap.getConfig()); - mSize = new IntSize(bitmap.getWidth(), bitmap.getHeight()); - mBitmap = bitmap; - } - - private synchronized void freeBuffer() { - if (mBuffer != null) { - mBuffer = DirectBufferAllocator.free(mBuffer); - } - } - - public void destroy() { - try { - freeBuffer(); - } catch (Exception ex) { - Log.e(LOGTAG, "error clearing buffer: ", ex); - } - } - - public ByteBuffer getBuffer() { - if (mBuffer == null) { - int bpp = bitsPerPixelForFormat(mFormat); - mBuffer = DirectBufferAllocator.allocate(mSize.getArea() * bpp); - mBitmap.copyPixelsToBuffer(mBuffer.asIntBuffer()); - mBitmap = null; - } - return mBuffer; - } - - public IntSize getSize() { return mSize; } - public int getFormat() { return mFormat; } - - public static final int FORMAT_INVALID = -1; - public static final int FORMAT_ARGB32 = 0; - public static final int FORMAT_RGB24 = 1; - public static final int FORMAT_A8 = 2; - public static final int FORMAT_A1 = 3; - public static final int FORMAT_RGB16_565 = 4; - - private static int bitsPerPixelForFormat(int format) { - switch (format) { - case FORMAT_A1: return 1; - case FORMAT_A8: return 8; - case FORMAT_RGB16_565: return 16; - case FORMAT_RGB24: return 24; - case FORMAT_ARGB32: return 32; - default: - throw new RuntimeException("Unknown Cairo format"); - } - } - - private static int bitmapConfigToFormat(Bitmap.Config config) { - if (config == null) - return FORMAT_ARGB32; /* Droid Pro fix. */ - - switch (config) { - case ALPHA_8: return FORMAT_A8; - case ARGB_4444: throw new RuntimeException("ARGB_444 unsupported"); - case ARGB_8888: return FORMAT_ARGB32; - case RGB_565: return FORMAT_RGB16_565; - default: throw new RuntimeException("Unknown Skia bitmap config"); - } - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/BufferedImageGLInfo.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/BufferedImageGLInfo.java deleted file mode 100644 index 41f38e1ba..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/BufferedImageGLInfo.java +++ /dev/null @@ -1,35 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.gfx; - -import javax.microedition.khronos.opengles.GL10; - -/** Information needed to render buffered bitmaps using OpenGL ES. */ -public class BufferedImageGLInfo { - public final int internalFormat; - public final int format; - public final int type; - - public BufferedImageGLInfo(int bufferedImageFormat) { - switch (bufferedImageFormat) { - case BufferedImage.FORMAT_ARGB32: - internalFormat = format = GL10.GL_RGBA; type = GL10.GL_UNSIGNED_BYTE; - break; - case BufferedImage.FORMAT_RGB24: - internalFormat = format = GL10.GL_RGB; type = GL10.GL_UNSIGNED_BYTE; - break; - case BufferedImage.FORMAT_RGB16_565: - internalFormat = format = GL10.GL_RGB; type = GL10.GL_UNSIGNED_SHORT_5_6_5; - break; - case BufferedImage.FORMAT_A8: - case BufferedImage.FORMAT_A1: - throw new RuntimeException("BufferedImage FORMAT_A1 and FORMAT_A8 unsupported"); - default: - throw new RuntimeException("Unknown BufferedImage format"); - } - } -} - diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/DynamicToolbarAnimator.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/DynamicToolbarAnimator.java deleted file mode 100644 index e299b5744..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/DynamicToolbarAnimator.java +++ /dev/null @@ -1,605 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.gfx; - -import org.mozilla.gecko.AppConstants; -import org.mozilla.gecko.PrefsHelper; -import org.mozilla.gecko.util.FloatUtils; -import org.mozilla.gecko.util.ThreadUtils; - -import android.graphics.PointF; -import android.support.v4.view.ViewCompat; -import android.util.Log; -import android.view.MotionEvent; -import android.view.animation.LinearInterpolator; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.EnumSet; -import java.util.List; -import java.util.Set; - -public class DynamicToolbarAnimator { - private static final String LOGTAG = "GeckoDynamicToolbarAnimator"; - private static final String PREF_SCROLL_TOOLBAR_THRESHOLD = "browser.ui.scroll-toolbar-threshold"; - - public static enum PinReason { - RELAYOUT, - ACTION_MODE, - FULL_SCREEN, - CARET_DRAG - } - - private final Set<PinReason> pinFlags = Collections.synchronizedSet(EnumSet.noneOf(PinReason.class)); - - // The duration of the animation in ns - private static final long ANIMATION_DURATION = 150000000; - - private final GeckoLayerClient mTarget; - private final List<LayerView.DynamicToolbarListener> mListeners; - - /* The translation to be applied to the toolbar UI view. This is the - * distance from the default/initial location (at the top of the screen, - * visible to the user) to where we want it to be. This variable should - * always be between 0 (toolbar fully visible) and the height of the toolbar - * (toolbar fully hidden), inclusive. - */ - private float mToolbarTranslation; - - /* The translation to be applied to the LayerView. This is the distance from - * the default/initial location (just below the toolbar, with the bottom - * extending past the bottom of the screen) to where we want it to be. - * This variable should always be between 0 and the height of the toolbar, - * inclusive. - */ - private float mLayerViewTranslation; - - /* This stores the maximum translation that can be applied to the toolbar - * and layerview when scrolling. This is populated with the height of the - * toolbar. */ - private float mMaxTranslation; - - /* This interpolator is used for the above mentioned animation */ - private LinearInterpolator mInterpolator; - - /* This is the proportion of the viewport rect that needs to be travelled - * while scrolling before the translation will start taking effect. - */ - private float SCROLL_TOOLBAR_THRESHOLD = 0.20f; - /* The ID of the prefs listener for the scroll-toolbar threshold */ - private final PrefsHelper.PrefHandler mPrefObserver; - - /* While we are resizing the viewport to account for the toolbar, the Java - * code and painted layer metrics in the compositor have different notions - * of the CSS viewport height. The Java value is stored in the - * GeckoLayerClient's viewport metrics, and the Gecko one is stored here. - * This allows us to adjust fixed-pos items correctly. - * You must synchronize on mTarget.getLock() to read/write this. */ - private Integer mHeightDuringResize; - - /* This tracks if we should trigger a "snap" on the next composite. A "snap" - * is when we simultaneously move the LayerView and change the scroll offset - * in the compositor so that everything looks the same on the screen but - * has really been shifted. - * You must synchronize on |this| to read/write this. */ - private boolean mSnapRequired = false; - - /* The task that handles showing/hiding toolbar */ - private DynamicToolbarAnimationTask mAnimationTask; - - /* The start point of a drag, used for scroll-based dynamic toolbar - * behaviour. */ - private PointF mTouchStart; - private float mLastTouch; - - /* Set to true when root content is being scrolled */ - private boolean mScrollingRootContent; - - public DynamicToolbarAnimator(GeckoLayerClient aTarget) { - mTarget = aTarget; - mListeners = new ArrayList<LayerView.DynamicToolbarListener>(); - - mInterpolator = new LinearInterpolator(); - - // Listen to the dynamic toolbar pref - mPrefObserver = new PrefsHelper.PrefHandlerBase() { - @Override - public void prefValue(String pref, int value) { - SCROLL_TOOLBAR_THRESHOLD = value / 100.0f; - } - }; - PrefsHelper.addObserver(new String[] { PREF_SCROLL_TOOLBAR_THRESHOLD }, mPrefObserver); - } - - public void destroy() { - PrefsHelper.removeObserver(mPrefObserver); - } - - public void addTranslationListener(LayerView.DynamicToolbarListener aListener) { - mListeners.add(aListener); - } - - public void removeTranslationListener(LayerView.DynamicToolbarListener aListener) { - mListeners.remove(aListener); - } - - private void fireListeners() { - for (LayerView.DynamicToolbarListener listener : mListeners) { - listener.onTranslationChanged(mToolbarTranslation, mLayerViewTranslation); - } - } - - void onPanZoomStopped() { - for (LayerView.DynamicToolbarListener listener : mListeners) { - listener.onPanZoomStopped(); - } - } - - void onMetricsChanged(ImmutableViewportMetrics aMetrics) { - for (LayerView.DynamicToolbarListener listener : mListeners) { - listener.onMetricsChanged(aMetrics); - } - } - - public void setMaxTranslation(float maxTranslation) { - ThreadUtils.assertOnUiThread(); - if (maxTranslation < 0) { - Log.e(LOGTAG, "Got a negative max-translation value: " + maxTranslation + "; clamping to zero"); - mMaxTranslation = 0; - } else { - mMaxTranslation = maxTranslation; - } - } - - public float getMaxTranslation() { - return mMaxTranslation; - } - - public float getToolbarTranslation() { - return mToolbarTranslation; - } - - /** - * If true, scroll changes will not affect translation. - */ - public boolean isPinned() { - return !pinFlags.isEmpty(); - } - - public boolean isPinnedBy(PinReason reason) { - return pinFlags.contains(reason); - } - - public void setPinned(boolean pinned, PinReason reason) { - if (pinned) { - pinFlags.add(reason); - } else { - pinFlags.remove(reason); - } - } - - public void showToolbar(boolean immediately) { - animateToolbar(true, immediately); - } - - public void hideToolbar(boolean immediately) { - animateToolbar(false, immediately); - } - - public void setScrollingRootContent(boolean isRootContent) { - mScrollingRootContent = isRootContent; - } - - private void animateToolbar(final boolean showToolbar, boolean immediately) { - ThreadUtils.assertOnUiThread(); - - if (mAnimationTask != null) { - mTarget.getView().removeRenderTask(mAnimationTask); - mAnimationTask = null; - } - - float desiredTranslation = (showToolbar ? 0 : mMaxTranslation); - Log.v(LOGTAG, "Requested " + (immediately ? "immediate " : "") + "toolbar animation to translation " + desiredTranslation); - if (FloatUtils.fuzzyEquals(mToolbarTranslation, desiredTranslation)) { - // If we're already pretty much in the desired position, don't bother - // with a full animation; do an immediate jump - immediately = true; - Log.v(LOGTAG, "Changing animation to immediate jump"); - } - - if (showToolbar && immediately) { - // Special case for showing the toolbar immediately: some of the call - // sites expect this to happen synchronously, so let's do that. This - // is safe because if we are showing the toolbar from a hidden state - // there is no chance of showing garbage - mToolbarTranslation = desiredTranslation; - fireListeners(); - // And then proceed with the normal flow (some of which will be - // a no-op now)... - } - - if (!showToolbar) { - // If we are hiding the toolbar, we need to move the LayerView first, - // so that we don't end up showing garbage under the toolbar when - // it is hidden. In the case that we are showing the toolbar, we - // move the LayerView after the toolbar is shown - the - // DynamicToolbarAnimationTask calls that upon completion. - shiftLayerView(desiredTranslation); - } - - mAnimationTask = new DynamicToolbarAnimationTask(desiredTranslation, immediately, showToolbar); - mTarget.getView().postRenderTask(mAnimationTask); - } - - private synchronized void shiftLayerView(float desiredTranslation) { - float layerViewTranslationNeeded = desiredTranslation - mLayerViewTranslation; - mLayerViewTranslation = desiredTranslation; - synchronized (mTarget.getLock()) { - if (layerViewTranslationNeeded == 0 && isResizing()) { - // We're already in the middle of a snap, so this new call is - // redundant as it's snapping to the same place. Ignore it. - return; - } - mHeightDuringResize = new Integer(mTarget.getViewportMetrics().viewportRectHeight); - mSnapRequired = mTarget.setViewportSize( - mTarget.getView().getWidth(), - mTarget.getView().getHeight() - Math.round(mMaxTranslation - mLayerViewTranslation), - new PointF(0, -layerViewTranslationNeeded)); - if (!mSnapRequired) { - mHeightDuringResize = null; - ThreadUtils.postToUiThread(new Runnable() { - // Post to run it outside of the synchronize blocks. The - // delay shouldn't hurt. - @Override - public void run() { - fireListeners(); - } - }); - } - // Request a composite, which will trigger the snap. - mTarget.getView().requestRender(); - } - } - - IntSize getViewportSize() { - ThreadUtils.assertOnUiThread(); - - int viewWidth = mTarget.getView().getWidth(); - int viewHeight = mTarget.getView().getHeight(); - float toolbarTranslation = mToolbarTranslation; - if (mAnimationTask != null) { - // If we have an animation going, mToolbarTranslation may be in flux - // and we should use the final value it will settle on. - toolbarTranslation = mAnimationTask.getFinalToolbarTranslation(); - } - int viewHeightVisible = viewHeight - Math.round(mMaxTranslation - toolbarTranslation); - return new IntSize(viewWidth, viewHeightVisible); - } - - boolean isResizing() { - return mHeightDuringResize != null; - } - - private final Runnable mSnapRunnable = new Runnable() { - private int mFrame = 0; - - @Override - public final void run() { - // It takes 2 frames for the view translation to take effect, at - // least on a Nexus 4 device running Android 4.2.2. So we wait for - // two frames before doing the notifyAll(), otherwise we get a - // short user-visible glitch. - // TODO: find a better way to do this, if possible. - if (mFrame == 1) { - synchronized (this) { - this.notifyAll(); - } - mFrame = 0; - return; - } - - if (mFrame == 0) { - fireListeners(); - } - - ViewCompat.postOnAnimation(mTarget.getView(), this); - mFrame++; - } - }; - - void scrollChangeResizeCompleted() { - synchronized (mTarget.getLock()) { - Log.v(LOGTAG, "Scrollchange resize completed"); - mHeightDuringResize = null; - } - } - - /** - * "Shrinks" the absolute value of aValue by moving it closer to zero by - * aShrinkAmount, but prevents it from crossing over zero. If aShrinkAmount - * is negative it is ignored. - * @return The shrunken value. - */ - private static float shrinkAbs(float aValue, float aShrinkAmount) { - if (aShrinkAmount <= 0) { - return aValue; - } - float shrinkBy = Math.min(Math.abs(aValue), aShrinkAmount); - return (aValue < 0 ? aValue + shrinkBy : aValue - shrinkBy); - } - - /** - * This function takes in a scroll amount and decides how much of that - * should be used up to translate things on screen because of the dynamic - * toolbar behaviour. It returns the maximum amount that could be used - * for translation purposes; the rest must be used for scrolling. - */ - private float decideTranslation(float aDelta, - ImmutableViewportMetrics aMetrics, - float aTouchTravelDistance) { - - float exposeThreshold = aMetrics.getHeight() * SCROLL_TOOLBAR_THRESHOLD; - float translation = aDelta; - - if (translation < 0) { // finger moving upwards - translation = shrinkAbs(translation, aMetrics.getOverscroll().top); - - // If the toolbar is in a state between fully hidden and fully shown - // (i.e. the user is actively translating it), then we want the - // translation to take effect right away. Or if the user has moved - // their finger past the required threshold (and is not trying to - // scroll past the bottom of the page) then also we want the touch - // to cause translation. If the toolbar is fully visible, we only - // want the toolbar to hide if the user is scrolling the root content. - boolean inBetween = (mToolbarTranslation != 0 && mToolbarTranslation != mMaxTranslation); - boolean reachedThreshold = -aTouchTravelDistance >= exposeThreshold; - boolean atBottomOfPage = aMetrics.viewportRectBottom() >= aMetrics.pageRectBottom; - if (inBetween || (mScrollingRootContent && reachedThreshold && !atBottomOfPage)) { - return translation; - } - } else { // finger moving downwards - translation = shrinkAbs(translation, aMetrics.getOverscroll().bottom); - - // Ditto above comment, but in this case if they reached the top and - // the toolbar is not shown, then we do want to allow translation - // right away. - boolean inBetween = (mToolbarTranslation != 0 && mToolbarTranslation != mMaxTranslation); - boolean reachedThreshold = aTouchTravelDistance >= exposeThreshold; - boolean atTopOfPage = aMetrics.viewportRectTop <= aMetrics.pageRectTop; - boolean isToolbarTranslated = (mToolbarTranslation != 0); - if (inBetween || reachedThreshold || (atTopOfPage && isToolbarTranslated)) { - return translation; - } - } - - return 0; - } - - // Timestamp of the start of the touch event used to calculate toolbar velocity - private long mLastEventTime; - // Current velocity of the toolbar. Used to populate the velocity queue in C++APZ. - private float mVelocity; - - boolean onInterceptTouchEvent(MotionEvent event) { - if (isPinned()) { - return false; - } - - // Animations should never co-exist with the user touching the screen. - if (mAnimationTask != null) { - mTarget.getView().removeRenderTask(mAnimationTask); - mAnimationTask = null; - } - - // we only care about single-finger drags here; any other kind of event - // should reset and cause us to start over. - if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE || - event.getActionMasked() != MotionEvent.ACTION_MOVE || - event.getPointerCount() != 1) - { - if (mTouchStart != null) { - Log.v(LOGTAG, "Resetting touch sequence due to non-move"); - mTouchStart = null; - mVelocity = 0.0f; - } - - if (event.getActionMasked() == MotionEvent.ACTION_UP) { - // We need to do this even if the toolbar is already fully - // visible or fully hidden, because this is what triggers the - // viewport resize in content and updates the viewport metrics. - boolean toolbarMostlyVisible = mToolbarTranslation < (mMaxTranslation / 2); - Log.v(LOGTAG, "All fingers lifted, completing " + (toolbarMostlyVisible ? "show" : "hide")); - animateToolbar(toolbarMostlyVisible, false); - } - return false; - } - - if (mTouchStart != null) { - float prevDir = mLastTouch - mTouchStart.y; - float newDir = event.getRawY() - mLastTouch; - if (prevDir != 0 && newDir != 0 && ((prevDir < 0) != (newDir < 0))) { - // If the direction of movement changed, reset the travel - // distance properties. - mTouchStart = null; - mVelocity = 0.0f; - } - } - - if (mTouchStart == null) { - mTouchStart = new PointF(event.getRawX(), event.getRawY()); - mLastTouch = event.getRawY(); - mLastEventTime = event.getEventTime(); - return false; - } - - float deltaY = event.getRawY() - mLastTouch; - long currentTime = event.getEventTime(); - float deltaTime = (float)(currentTime - mLastEventTime); - mLastEventTime = currentTime; - if (deltaTime > 0.0f) { - mVelocity = -deltaY / deltaTime; - } else { - mVelocity = 0.0f; - } - mLastTouch = event.getRawY(); - float travelDistance = event.getRawY() - mTouchStart.y; - - ImmutableViewportMetrics metrics = mTarget.getViewportMetrics(); - - if (metrics.getPageHeight() <= mTarget.getView().getHeight() && - mToolbarTranslation == 0) { - // If the page is short and the toolbar is already visible, don't - // allow translating it out of view. - return false; - } - - float translation = decideTranslation(deltaY, metrics, travelDistance); - - float oldToolbarTranslation = mToolbarTranslation; - float oldLayerViewTranslation = mLayerViewTranslation; - mToolbarTranslation = FloatUtils.clamp(mToolbarTranslation - translation, 0, mMaxTranslation); - mLayerViewTranslation = FloatUtils.clamp(mLayerViewTranslation - translation, 0, mMaxTranslation); - - if (oldToolbarTranslation == mToolbarTranslation && - oldLayerViewTranslation == mLayerViewTranslation) { - return false; - } - - if (mToolbarTranslation == mMaxTranslation) { - Log.v(LOGTAG, "Toolbar at maximum translation, calling shiftLayerView(" + mMaxTranslation + ")"); - shiftLayerView(mMaxTranslation); - } else if (mToolbarTranslation == 0) { - Log.v(LOGTAG, "Toolbar at minimum translation, calling shiftLayerView(0)"); - shiftLayerView(0); - } - - fireListeners(); - mTarget.getView().requestRender(); - return true; - } - - // Get the current velocity of the toolbar. - float getVelocity() { - return mVelocity; - } - - public PointF getVisibleEndOfLayerView() { - return new PointF(mTarget.getView().getWidth(), - mTarget.getView().getHeight() - mMaxTranslation + mLayerViewTranslation); - } - - private float bottomOfCssViewport(ImmutableViewportMetrics aMetrics) { - return (isResizing() ? mHeightDuringResize : aMetrics.getHeight()) - + mMaxTranslation - mLayerViewTranslation; - } - - private synchronized boolean getAndClearSnapRequired() { - boolean snapRequired = mSnapRequired; - mSnapRequired = false; - return snapRequired; - } - - void populateViewTransform(ViewTransform aTransform, ImmutableViewportMetrics aMetrics) { - if (getAndClearSnapRequired()) { - synchronized (mSnapRunnable) { - ViewCompat.postOnAnimation(mTarget.getView(), mSnapRunnable); - try { - // hold the in-progress composite until the views have been - // translated because otherwise there is a visible glitch. - // don't hold for more than 100ms just in case. - mSnapRunnable.wait(100); - } catch (InterruptedException ie) { - } - } - } - - aTransform.x = aMetrics.viewportRectLeft; - aTransform.y = aMetrics.viewportRectTop; - aTransform.width = aMetrics.viewportRectWidth; - aTransform.height = aMetrics.viewportRectHeight; - aTransform.scale = aMetrics.zoomFactor; - - aTransform.fixedLayerMarginTop = mLayerViewTranslation - mToolbarTranslation; - float bottomOfScreen = mTarget.getView().getHeight(); - // We want to move a fixed item from "bottomOfCssViewport" to - // "bottomOfScreen". But also the bottom margin > 0 means that bottom - // fixed-pos items will move upwards. - aTransform.fixedLayerMarginBottom = bottomOfCssViewport(aMetrics) - bottomOfScreen; - //Log.v(LOGTAG, "ViewTransform is x=" + aTransform.x + " y=" + aTransform.y - // + " z=" + aTransform.scale + " t=" + aTransform.fixedLayerMarginTop - // + " b=" + aTransform.fixedLayerMarginBottom); - } - - class DynamicToolbarAnimationTask extends RenderTask { - private final float mStartTranslation; - private final float mEndTranslation; - private final boolean mImmediate; - private final boolean mShiftLayerView; - private boolean mContinueAnimation; - - public DynamicToolbarAnimationTask(float aTranslation, boolean aImmediate, boolean aShiftLayerView) { - super(false); - mContinueAnimation = true; - mStartTranslation = mToolbarTranslation; - mEndTranslation = aTranslation; - mImmediate = aImmediate; - mShiftLayerView = aShiftLayerView; - } - - float getFinalToolbarTranslation() { - return mEndTranslation; - } - - @Override - public boolean internalRun(long timeDelta, long currentFrameStartTime) { - if (!mContinueAnimation) { - return false; - } - - // Calculate the progress (between 0 and 1) - final float progress = mImmediate - ? 1.0f - : mInterpolator.getInterpolation( - Math.min(1.0f, (System.nanoTime() - getStartTime()) - / (float)ANIMATION_DURATION)); - - // This runs on the compositor thread, so we need to post the - // actual work to the UI thread. - ThreadUtils.assertNotOnUiThread(); - - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - // Move the toolbar as per the animation - mToolbarTranslation = FloatUtils.interpolate(mStartTranslation, mEndTranslation, progress); - fireListeners(); - - if (mShiftLayerView && progress >= 1.0f) { - shiftLayerView(mEndTranslation); - } - } - }); - - mTarget.getView().requestRender(); - if (progress >= 1.0f) { - mContinueAnimation = false; - } - return mContinueAnimation; - } - } - - class SnapMetrics { - public final int viewportWidth; - public final int viewportHeight; - public final float scrollChangeY; - - SnapMetrics(ImmutableViewportMetrics aMetrics, float aScrollChange) { - viewportWidth = aMetrics.viewportRectWidth; - viewportHeight = aMetrics.viewportRectHeight; - scrollChangeY = aScrollChange; - } - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/FloatSize.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/FloatSize.java deleted file mode 100644 index 4b495ab77..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/FloatSize.java +++ /dev/null @@ -1,54 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.gfx; - -import org.mozilla.gecko.util.FloatUtils; - -import org.json.JSONException; -import org.json.JSONObject; - -public class FloatSize { - public final float width, height; - - public FloatSize(FloatSize size) { width = size.width; height = size.height; } - public FloatSize(IntSize size) { width = size.width; height = size.height; } - public FloatSize(float aWidth, float aHeight) { width = aWidth; height = aHeight; } - - public FloatSize(JSONObject json) { - try { - width = (float)json.getDouble("width"); - height = (float)json.getDouble("height"); - } catch (JSONException e) { - throw new RuntimeException(e); - } - } - - @Override - public String toString() { return "(" + width + "," + height + ")"; } - - public boolean isPositive() { - return (width > 0 && height > 0); - } - - public boolean fuzzyEquals(FloatSize size) { - return (FloatUtils.fuzzyEquals(size.width, width) && - FloatUtils.fuzzyEquals(size.height, height)); - } - - public FloatSize scale(float factor) { - return new FloatSize(width * factor, height * factor); - } - - /* - * Returns the size that represents a linear transition between this size and `to` at time `t`, - * which is on the scale [0, 1). - */ - public FloatSize interpolate(FloatSize to, float t) { - return new FloatSize(FloatUtils.interpolate(width, to.width, t), - FloatUtils.interpolate(height, to.height, t)); - } -} - diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/FullScreenState.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/FullScreenState.java deleted file mode 100644 index 9574bbe0e..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/FullScreenState.java +++ /dev/null @@ -1,12 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.gfx; - -public enum FullScreenState { - NONE, - ROOT_ELEMENT, - NON_ROOT_ELEMENT -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoLayerClient.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoLayerClient.java deleted file mode 100644 index d504fe13e..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoLayerClient.java +++ /dev/null @@ -1,694 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.gfx; - -import org.mozilla.gecko.annotation.RobocopTarget; -import org.mozilla.gecko.annotation.WrapForJNI; -import org.mozilla.gecko.GeckoAppShell; -import org.mozilla.gecko.gfx.LayerView.DrawListener; -import org.mozilla.gecko.EventDispatcher; -import org.mozilla.gecko.util.FloatUtils; -import org.mozilla.gecko.AppConstants; - -import android.content.Context; -import android.graphics.Color; -import android.graphics.Matrix; -import android.graphics.PointF; -import android.graphics.RectF; -import android.os.SystemClock; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.InputDevice; -import android.view.MotionEvent; -import org.json.JSONObject; - -import java.util.ArrayList; -import java.util.List; - -class GeckoLayerClient implements LayerView.Listener, PanZoomTarget -{ - private static final String LOGTAG = "GeckoLayerClient"; - private static int sPaintSyncId = 1; - - private LayerRenderer mLayerRenderer; - private boolean mLayerRendererInitialized; - - private final Context mContext; - private IntSize mScreenSize; - private IntSize mWindowSize; - - /* - * The viewport metrics being used to draw the current frame. This is only - * accessed by the compositor thread, and so needs no synchronisation. - */ - private ImmutableViewportMetrics mFrameMetrics; - - private final List<DrawListener> mDrawListeners; - - /* Used as temporaries by syncViewportInfo */ - private final ViewTransform mCurrentViewTransform; - - private boolean mForceRedraw; - - /* The current viewport metrics. - * This is volatile so that we can read and write to it from different threads. - * We avoid synchronization to make getting the viewport metrics from - * the compositor as cheap as possible. The viewport is immutable so - * we don't need to worry about anyone mutating it while we're reading from it. - * Specifically: - * 1) reading mViewportMetrics from any thread is fine without synchronization - * 2) writing to mViewportMetrics requires synchronizing on the layer controller object - * 3) whenever reading multiple fields from mViewportMetrics without synchronization (i.e. in - * case 1 above) you should always first grab a local copy of the reference, and then use - * that because mViewportMetrics might get reassigned in between reading the different - * fields. */ - private volatile ImmutableViewportMetrics mViewportMetrics; - - private volatile boolean mGeckoIsReady; - - /* package */ final PanZoomController mPanZoomController; - private final DynamicToolbarAnimator mToolbarAnimator; - /* package */ final LayerView mView; - - /* This flag is true from the time that browser.js detects a first-paint is about to start, - * to the time that we receive the first-paint composite notification from the compositor. - * Note that there is a small race condition with this; if there are two paints that both - * have the first-paint flag set, and the second paint happens concurrently with the - * composite for the first paint, then this flag may be set to true prematurely. Fixing this - * is possible but risky; see https://bugzilla.mozilla.org/show_bug.cgi?id=797615#c751 - */ - private volatile boolean mContentDocumentIsDisplayed; - - private SynthesizedEventState mPointerState; - - @WrapForJNI(stubName = "ClearColor") - private volatile int mClearColor = Color.WHITE; - - public GeckoLayerClient(Context context, LayerView view, EventDispatcher eventDispatcher) { - // we can fill these in with dummy values because they are always written - // to before being read - mContext = context; - mScreenSize = new IntSize(0, 0); - mWindowSize = new IntSize(0, 0); - mCurrentViewTransform = new ViewTransform(0, 0, 1); - - mForceRedraw = true; - DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); - mViewportMetrics = new ImmutableViewportMetrics(displayMetrics) - .setViewportSize(view.getWidth(), view.getHeight()); - - mFrameMetrics = mViewportMetrics; - - mDrawListeners = new ArrayList<DrawListener>(); - mToolbarAnimator = new DynamicToolbarAnimator(this); - mPanZoomController = PanZoomController.Factory.create(this, view, eventDispatcher); - mView = view; - mView.setListener(this); - mContentDocumentIsDisplayed = true; - } - - public void setOverscrollHandler(final Overscroll listener) { - mPanZoomController.setOverscrollHandler(listener); - } - - public void setGeckoReady(boolean ready) { - mGeckoIsReady = ready; - } - - @Override // PanZoomTarget - public boolean isGeckoReady() { - return mGeckoIsReady; - } - - /** Attaches to root layer so that Gecko appears. */ - @WrapForJNI(calledFrom = "gecko") - private void onGeckoReady() { - mGeckoIsReady = true; - - mLayerRenderer = mView.getRenderer(); - - sendResizeEventIfNecessary(true, null); - - // Gecko being ready is one of the two conditions (along with having an available - // surface) that cause us to create the compositor. So here, now that we know gecko - // is ready, call updateCompositor() to see if we can actually do the creation. - // This needs to run on the UI thread so that the surface validity can't change on - // us while we're in the middle of creating the compositor. - mView.post(new Runnable() { - @Override - public void run() { - mPanZoomController.attach(); - mView.updateCompositor(); - } - }); - } - - public void destroy() { - mPanZoomController.destroy(); - mToolbarAnimator.destroy(); - mDrawListeners.clear(); - mGeckoIsReady = false; - } - - public LayerView getView() { - return mView; - } - - public FloatSize getViewportSize() { - return mViewportMetrics.getSize(); - } - - /** - * The view calls this function to indicate that the viewport changed size. It must hold the - * monitor while calling it. - * - * TODO: Refactor this to use an interface. Expose that interface only to the view and not - * to the layer client. That way, the layer client won't be tempted to call this, which might - * result in an infinite loop. - */ - boolean setViewportSize(int width, int height, PointF scrollChange) { - if (mViewportMetrics.viewportRectWidth == width && - mViewportMetrics.viewportRectHeight == height && - (scrollChange == null || (scrollChange.x == 0 && scrollChange.y == 0))) { - return false; - } - mViewportMetrics = mViewportMetrics.setViewportSize(width, height); - if (scrollChange != null) { - mViewportMetrics = mPanZoomController.adjustScrollForSurfaceShift(mViewportMetrics, scrollChange); - } - - if (mGeckoIsReady) { - // here we send gecko a resize message. The code in browser.js is responsible for - // picking up on that resize event, modifying the viewport as necessary, and informing - // us of the new viewport. - sendResizeEventIfNecessary(true, scrollChange); - - // the following call also sends gecko a message, which will be processed after the resize - // message above has updated the viewport. this message ensures that if we have just put - // focus in a text field, we scroll the content so that the text field is in view. - GeckoAppShell.viewSizeChanged(); - } - return true; - } - - PanZoomController getPanZoomController() { - return mPanZoomController; - } - - DynamicToolbarAnimator getDynamicToolbarAnimator() { - return mToolbarAnimator; - } - - /* Informs Gecko that the screen size has changed. */ - private void sendResizeEventIfNecessary(boolean force, PointF scrollChange) { - DisplayMetrics metrics = mContext.getResources().getDisplayMetrics(); - - IntSize newScreenSize = new IntSize(metrics.widthPixels, metrics.heightPixels); - IntSize newWindowSize = new IntSize(mViewportMetrics.viewportRectWidth, - mViewportMetrics.viewportRectHeight); - - boolean screenSizeChanged = !mScreenSize.equals(newScreenSize); - boolean windowSizeChanged = !mWindowSize.equals(newWindowSize); - - if (!force && !screenSizeChanged && !windowSizeChanged) { - return; - } - - mScreenSize = newScreenSize; - mWindowSize = newWindowSize; - - if (screenSizeChanged) { - Log.d(LOGTAG, "Screen-size changed to " + mScreenSize); - } - - if (windowSizeChanged) { - Log.d(LOGTAG, "Window-size changed to " + mWindowSize); - } - - if (mView != null) { - mView.notifySizeChanged(mWindowSize.width, mWindowSize.height, - mScreenSize.width, mScreenSize.height); - } - - String json = ""; - try { - if (scrollChange != null) { - int id = ++sPaintSyncId; - if (id == 0) { - // never use 0 as that is the default value for "this is not - // a special transaction" - id = ++sPaintSyncId; - } - JSONObject jsonObj = new JSONObject(); - jsonObj.put("x", scrollChange.x / mViewportMetrics.zoomFactor); - jsonObj.put("y", scrollChange.y / mViewportMetrics.zoomFactor); - jsonObj.put("id", id); - json = jsonObj.toString(); - } - } catch (Exception e) { - Log.e(LOGTAG, "Unable to convert point to JSON", e); - } - GeckoAppShell.notifyObservers("Window:Resize", json); - } - - /** - * The different types of Viewport messages handled. All viewport events - * expect a display-port to be returned, but can handle one not being - * returned. - */ - private enum ViewportMessageType { - UPDATE, // The viewport has changed and should be entirely updated - PAGE_SIZE // The viewport's page-size has changed - } - - @WrapForJNI(calledFrom = "gecko") - void contentDocumentChanged() { - mContentDocumentIsDisplayed = false; - } - - @WrapForJNI(calledFrom = "gecko") - boolean isContentDocumentDisplayed() { - return mContentDocumentIsDisplayed; - } - - /** The compositor invokes this function just before compositing a frame where the document - * is different from the document composited on the last frame. In these cases, the viewport - * information we have in Java is no longer valid and needs to be replaced with the new - * viewport information provided. - */ - @WrapForJNI - public void setFirstPaintViewport(float offsetX, float offsetY, float zoom, - float cssPageLeft, float cssPageTop, float cssPageRight, float cssPageBottom) { - synchronized (getLock()) { - ImmutableViewportMetrics currentMetrics = getViewportMetrics(); - - RectF cssPageRect = new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom); - RectF pageRect = RectUtils.scaleAndRound(cssPageRect, zoom); - - final ImmutableViewportMetrics newMetrics = currentMetrics - .setViewportOrigin(offsetX, offsetY) - .setZoomFactor(zoom) - .setPageRect(pageRect, cssPageRect); - // Since we have switched to displaying a different document, we need to update any - // viewport-related state we have lying around (i.e. mViewportMetrics). - // Usually this information is updated via handleViewportMessage - // while we remain on the same document. - setViewportMetrics(newMetrics, true); - - // Indicate that the document is about to be composited so the - // LayerView background can be removed. - if (mView.getPaintState() == LayerView.PAINT_START) { - mView.setPaintState(LayerView.PAINT_BEFORE_FIRST); - } - } - - mContentDocumentIsDisplayed = true; - } - - /** The compositor invokes this function on every frame to figure out what part of the - * page to display, and to inform Java of the current display port. Since it is called - * on every frame, it needs to be ultra-fast. - * It avoids taking any locks or allocating any objects. We keep around a - * mCurrentViewTransform so we don't need to allocate a new ViewTransform - * every time we're called. NOTE: we might be able to return a ImmutableViewportMetrics - * which would avoid the copy into mCurrentViewTransform. - */ - private ViewTransform syncViewportInfo(int x, int y, int width, int height, float resolution, boolean layersUpdated, - int paintSyncId) { - // getViewportMetrics is thread safe so we don't need to synchronize. - // We save the viewport metrics here, so we later use it later in - // createFrame (which will be called by nsWindow::DrawWindowUnderlay on - // the native side, by the compositor). The viewport - // metrics can change between here and there, as it's accessed outside - // of the compositor thread. - mFrameMetrics = getViewportMetrics(); - - if (paintSyncId == sPaintSyncId) { - mToolbarAnimator.scrollChangeResizeCompleted(); - } - mToolbarAnimator.populateViewTransform(mCurrentViewTransform, mFrameMetrics); - - if (layersUpdated) { - for (DrawListener listener : mDrawListeners) { - listener.drawFinished(); - } - } - - return mCurrentViewTransform; - } - - @WrapForJNI - public ViewTransform syncFrameMetrics(float scrollX, float scrollY, float zoom, - float cssPageLeft, float cssPageTop, float cssPageRight, float cssPageBottom, - int dpX, int dpY, int dpWidth, int dpHeight, float paintedResolution, - boolean layersUpdated, int paintSyncId) - { - // TODO: optimize this so it doesn't create so much garbage - it's a - // hot path - RectF cssPageRect = new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom); - synchronized (getLock()) { - mViewportMetrics = mViewportMetrics.setViewportOrigin(scrollX, scrollY) - .setZoomFactor(zoom) - .setPageRect(RectUtils.scale(cssPageRect, zoom), cssPageRect); - } - return syncViewportInfo(dpX, dpY, dpWidth, dpHeight, paintedResolution, - layersUpdated, paintSyncId); - } - - class PointerInfo { - // We reserve one pointer ID for the mouse, so that tests don't have - // to worry about tracking pointer IDs if they just want to test mouse - // event synthesization. If somebody tries to use this ID for a - // synthesized touch event we'll throw an exception. - public static final int RESERVED_MOUSE_POINTER_ID = 100000; - - public int pointerId; - public int source; - public int screenX; - public int screenY; - public double pressure; - public int orientation; - - public MotionEvent.PointerCoords getCoords() { - MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords(); - coords.orientation = orientation; - coords.pressure = (float)pressure; - coords.x = screenX; - coords.y = screenY; - return coords; - } - } - - class SynthesizedEventState { - public final ArrayList<PointerInfo> pointers; - public long downTime; - - SynthesizedEventState() { - pointers = new ArrayList<PointerInfo>(); - } - - int getPointerIndex(int pointerId) { - for (int i = 0; i < pointers.size(); i++) { - if (pointers.get(i).pointerId == pointerId) { - return i; - } - } - return -1; - } - - int addPointer(int pointerId, int source) { - PointerInfo info = new PointerInfo(); - info.pointerId = pointerId; - info.source = source; - pointers.add(info); - return pointers.size() - 1; - } - - int getPointerCount(int source) { - int count = 0; - for (int i = 0; i < pointers.size(); i++) { - if (pointers.get(i).source == source) { - count++; - } - } - return count; - } - - MotionEvent.PointerProperties[] getPointerProperties(int source) { - MotionEvent.PointerProperties[] props = new MotionEvent.PointerProperties[getPointerCount(source)]; - int index = 0; - for (int i = 0; i < pointers.size(); i++) { - if (pointers.get(i).source == source) { - MotionEvent.PointerProperties p = new MotionEvent.PointerProperties(); - p.id = pointers.get(i).pointerId; - switch (source) { - case InputDevice.SOURCE_TOUCHSCREEN: - p.toolType = MotionEvent.TOOL_TYPE_FINGER; - break; - case InputDevice.SOURCE_MOUSE: - p.toolType = MotionEvent.TOOL_TYPE_MOUSE; - break; - } - props[index++] = p; - } - } - return props; - } - - MotionEvent.PointerCoords[] getPointerCoords(int source) { - MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[getPointerCount(source)]; - int index = 0; - for (int i = 0; i < pointers.size(); i++) { - if (pointers.get(i).source == source) { - coords[index++] = pointers.get(i).getCoords(); - } - } - return coords; - } - } - - private void synthesizeNativePointer(int source, int pointerId, - int eventType, int screenX, int screenY, double pressure, - int orientation) - { - Log.d(LOGTAG, "Synthesizing pointer from " + source + " id " + pointerId + " at " + screenX + ", " + screenY); - - if (mPointerState == null) { - mPointerState = new SynthesizedEventState(); - } - - // Find the pointer if it already exists - int pointerIndex = mPointerState.getPointerIndex(pointerId); - - // Event-specific handling - switch (eventType) { - case MotionEvent.ACTION_POINTER_UP: - if (pointerIndex < 0) { - Log.d(LOGTAG, "Requested synthesis of a pointer-up for a pointer that doesn't exist!"); - return; - } - if (mPointerState.pointers.size() == 1) { - // Last pointer is going up - eventType = MotionEvent.ACTION_UP; - } - break; - case MotionEvent.ACTION_CANCEL: - if (pointerIndex < 0) { - Log.d(LOGTAG, "Requested synthesis of a pointer-cancel for a pointer that doesn't exist!"); - return; - } - break; - case MotionEvent.ACTION_POINTER_DOWN: - if (pointerIndex < 0) { - // Adding a new pointer - pointerIndex = mPointerState.addPointer(pointerId, source); - if (pointerIndex == 0) { - // first pointer - eventType = MotionEvent.ACTION_DOWN; - mPointerState.downTime = SystemClock.uptimeMillis(); - } - } else { - // We're moving an existing pointer - eventType = MotionEvent.ACTION_MOVE; - } - break; - case MotionEvent.ACTION_HOVER_MOVE: - if (pointerIndex < 0) { - // Mouse-move a pointer without it going "down". However - // in order to send the right MotionEvent without a lot of - // duplicated code, we add the pointer to mPointerState, - // and then remove it at the bottom of this function. - pointerIndex = mPointerState.addPointer(pointerId, source); - } else { - // We're moving an existing mouse pointer that went down. - eventType = MotionEvent.ACTION_MOVE; - } - break; - } - - // Update the pointer with the new info - PointerInfo info = mPointerState.pointers.get(pointerIndex); - info.screenX = screenX; - info.screenY = screenY; - info.pressure = pressure; - info.orientation = orientation; - - // Dispatch the event - int action = (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT); - action &= MotionEvent.ACTION_POINTER_INDEX_MASK; - action |= (eventType & MotionEvent.ACTION_MASK); - boolean isButtonDown = (source == InputDevice.SOURCE_MOUSE) && - (eventType == MotionEvent.ACTION_DOWN || eventType == MotionEvent.ACTION_MOVE); - final MotionEvent event = MotionEvent.obtain( - /*downTime*/ mPointerState.downTime, - /*eventTime*/ SystemClock.uptimeMillis(), - /*action*/ action, - /*pointerCount*/ mPointerState.getPointerCount(source), - /*pointerProperties*/ mPointerState.getPointerProperties(source), - /*pointerCoords*/ mPointerState.getPointerCoords(source), - /*metaState*/ 0, - /*buttonState*/ (isButtonDown ? MotionEvent.BUTTON_PRIMARY : 0), - /*xPrecision*/ 0, - /*yPrecision*/ 0, - /*deviceId*/ 0, - /*edgeFlags*/ 0, - /*source*/ source, - /*flags*/ 0); - mView.post(new Runnable() { - @Override - public void run() { - mView.dispatchTouchEvent(event); - } - }); - - // Forget about removed pointers - if (eventType == MotionEvent.ACTION_POINTER_UP || - eventType == MotionEvent.ACTION_UP || - eventType == MotionEvent.ACTION_CANCEL || - eventType == MotionEvent.ACTION_HOVER_MOVE) - { - mPointerState.pointers.remove(pointerIndex); - } - } - - @WrapForJNI(calledFrom = "gecko") - public void synthesizeNativeTouchPoint(int pointerId, int eventType, int screenX, - int screenY, double pressure, int orientation) - { - if (pointerId == PointerInfo.RESERVED_MOUSE_POINTER_ID) { - throw new IllegalArgumentException("Use a different pointer ID in your test, this one is reserved for mouse"); - } - synthesizeNativePointer(InputDevice.SOURCE_TOUCHSCREEN, pointerId, - eventType, screenX, screenY, pressure, orientation); - } - - @WrapForJNI(calledFrom = "gecko") - public void synthesizeNativeMouseEvent(int eventType, int screenX, int screenY) { - synthesizeNativePointer(InputDevice.SOURCE_MOUSE, PointerInfo.RESERVED_MOUSE_POINTER_ID, - eventType, screenX, screenY, 0, 0); - } - - @WrapForJNI - public LayerRenderer.Frame createFrame() { - // Create the shaders and textures if necessary. - if (!mLayerRendererInitialized) { - if (mLayerRenderer == null) { - return null; - } - mLayerRenderer.createDefaultProgram(); - mLayerRendererInitialized = true; - } - - try { - return mLayerRenderer.createFrame(mFrameMetrics); - } catch (Exception e) { - Log.w(LOGTAG, e); - return null; - } - } - - private void geometryChanged() { - /* Let Gecko know if the screensize has changed */ - sendResizeEventIfNecessary(false, null); - } - - /** Implementation of LayerView.Listener */ - @Override - public void surfaceChanged(int width, int height) { - IntSize viewportSize = mToolbarAnimator.getViewportSize(); - setViewportSize(viewportSize.width, viewportSize.height, null); - } - - ImmutableViewportMetrics getViewportMetrics() { - return mViewportMetrics; - } - - /* - * You must hold the monitor while calling this. - */ - private void setViewportMetrics(ImmutableViewportMetrics metrics, boolean notifyGecko) { - // This class owns the viewport size and the fixed layer margins; don't let other pieces - // of code clobber either of them. The only place the viewport size should ever be - // updated is in GeckoLayerClient.setViewportSize, and the only place the margins should - // ever be updated is in GeckoLayerClient.setFixedLayerMargins; both of these assign to - // mViewportMetrics directly. - metrics = metrics.setViewportSize(mViewportMetrics.viewportRectWidth, mViewportMetrics.viewportRectHeight); - mViewportMetrics = metrics; - - viewportMetricsChanged(notifyGecko); - } - - /* - * You must hold the monitor while calling this. - */ - private void viewportMetricsChanged(boolean notifyGecko) { - mToolbarAnimator.onMetricsChanged(mViewportMetrics); - - mView.requestRender(); - if (notifyGecko && mGeckoIsReady) { - geometryChanged(); - } - } - - /* - * Updates the viewport metrics, overriding the viewport size and margins - * which are normally retained when calling setViewportMetrics. - * You must hold the monitor while calling this. - */ - void forceViewportMetrics(ImmutableViewportMetrics metrics, boolean notifyGecko, boolean forceRedraw) { - if (forceRedraw) { - mForceRedraw = true; - } - mViewportMetrics = metrics; - viewportMetricsChanged(notifyGecko); - } - - /** Implementation of PanZoomTarget */ - @Override - public void panZoomStopped() { - mToolbarAnimator.onPanZoomStopped(); - } - - Object getLock() { - return this; - } - - Matrix getMatrixForLayerRectToViewRect() { - if (!mGeckoIsReady) { - return null; - } - - ImmutableViewportMetrics viewportMetrics = mViewportMetrics; - PointF origin = viewportMetrics.getOrigin(); - float zoom = viewportMetrics.zoomFactor; - ImmutableViewportMetrics geckoViewport = mViewportMetrics; - PointF geckoOrigin = geckoViewport.getOrigin(); - float geckoZoom = geckoViewport.zoomFactor; - - Matrix matrix = new Matrix(); - matrix.postTranslate(geckoOrigin.x / geckoZoom, geckoOrigin.y / geckoZoom); - matrix.postScale(zoom, zoom); - matrix.postTranslate(-origin.x, -origin.y); - return matrix; - } - - @Override - public void setScrollingRootContent(boolean isRootContent) { - mToolbarAnimator.setScrollingRootContent(isRootContent); - } - - public void addDrawListener(DrawListener listener) { - mDrawListeners.add(listener); - } - - public void removeDrawListener(DrawListener listener) { - mDrawListeners.remove(listener); - } - - public void setClearColor(int color) { - mClearColor = color; - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/ImmutableViewportMetrics.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/ImmutableViewportMetrics.java deleted file mode 100644 index 8072deecc..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/ImmutableViewportMetrics.java +++ /dev/null @@ -1,282 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.gfx; - -import org.mozilla.gecko.annotation.WrapForJNI; -import org.mozilla.gecko.util.FloatUtils; - -import android.graphics.PointF; -import android.graphics.RectF; -import android.util.DisplayMetrics; - -/** - * ImmutableViewportMetrics are used to store the viewport metrics - * in way that we can access a version of them from multiple threads - * without having to take a lock - */ -public class ImmutableViewportMetrics { - - // We need to flatten the RectF and FloatSize structures - // because Java doesn't have the concept of const classes - public final float pageRectLeft; - public final float pageRectTop; - public final float pageRectRight; - public final float pageRectBottom; - public final float cssPageRectLeft; - public final float cssPageRectTop; - public final float cssPageRectRight; - public final float cssPageRectBottom; - public final float viewportRectLeft; - public final float viewportRectTop; - public final int viewportRectWidth; - public final int viewportRectHeight; - - public final float zoomFactor; - - public ImmutableViewportMetrics(DisplayMetrics metrics) { - viewportRectLeft = pageRectLeft = cssPageRectLeft = 0; - viewportRectTop = pageRectTop = cssPageRectTop = 0; - viewportRectWidth = metrics.widthPixels; - viewportRectHeight = metrics.heightPixels; - pageRectRight = cssPageRectRight = metrics.widthPixels; - pageRectBottom = cssPageRectBottom = metrics.heightPixels; - zoomFactor = 1.0f; - } - - /** This constructor is used by native code in AndroidJavaWrappers.cpp, be - * careful when modifying the signature. - */ - @WrapForJNI(calledFrom = "gecko") - private ImmutableViewportMetrics(float aPageRectLeft, float aPageRectTop, - float aPageRectRight, float aPageRectBottom, float aCssPageRectLeft, - float aCssPageRectTop, float aCssPageRectRight, float aCssPageRectBottom, - float aViewportRectLeft, float aViewportRectTop, int aViewportRectWidth, - int aViewportRectHeight, float aZoomFactor) - { - pageRectLeft = aPageRectLeft; - pageRectTop = aPageRectTop; - pageRectRight = aPageRectRight; - pageRectBottom = aPageRectBottom; - cssPageRectLeft = aCssPageRectLeft; - cssPageRectTop = aCssPageRectTop; - cssPageRectRight = aCssPageRectRight; - cssPageRectBottom = aCssPageRectBottom; - viewportRectLeft = aViewportRectLeft; - viewportRectTop = aViewportRectTop; - viewportRectWidth = aViewportRectWidth; - viewportRectHeight = aViewportRectHeight; - zoomFactor = aZoomFactor; - } - - public float getWidth() { - return viewportRectWidth; - } - - public float getHeight() { - return viewportRectHeight; - } - - public float viewportRectRight() { - return viewportRectLeft + viewportRectWidth; - } - - public float viewportRectBottom() { - return viewportRectTop + viewportRectHeight; - } - - public PointF getOrigin() { - return new PointF(viewportRectLeft, viewportRectTop); - } - - public FloatSize getSize() { - return new FloatSize(viewportRectWidth, viewportRectHeight); - } - - public RectF getViewport() { - return new RectF(viewportRectLeft, - viewportRectTop, - viewportRectRight(), - viewportRectBottom()); - } - - public RectF getCssViewport() { - return RectUtils.scale(getViewport(), 1 / zoomFactor); - } - - public RectF getPageRect() { - return new RectF(pageRectLeft, pageRectTop, pageRectRight, pageRectBottom); - } - - public float getPageWidth() { - return pageRectRight - pageRectLeft; - } - - public float getPageHeight() { - return pageRectBottom - pageRectTop; - } - - public RectF getCssPageRect() { - return new RectF(cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom); - } - - public RectF getOverscroll() { - return new RectF(Math.max(0, pageRectLeft - viewportRectLeft), - Math.max(0, pageRectTop - viewportRectTop), - Math.max(0, viewportRectRight() - pageRectRight), - Math.max(0, viewportRectBottom() - pageRectBottom)); - } - - /* - * Returns the viewport metrics that represent a linear transition between "this" and "to" at - * time "t", which is on the scale [0, 1). This function interpolates all values stored in - * the viewport metrics. - */ - public ImmutableViewportMetrics interpolate(ImmutableViewportMetrics to, float t) { - return new ImmutableViewportMetrics( - FloatUtils.interpolate(pageRectLeft, to.pageRectLeft, t), - FloatUtils.interpolate(pageRectTop, to.pageRectTop, t), - FloatUtils.interpolate(pageRectRight, to.pageRectRight, t), - FloatUtils.interpolate(pageRectBottom, to.pageRectBottom, t), - FloatUtils.interpolate(cssPageRectLeft, to.cssPageRectLeft, t), - FloatUtils.interpolate(cssPageRectTop, to.cssPageRectTop, t), - FloatUtils.interpolate(cssPageRectRight, to.cssPageRectRight, t), - FloatUtils.interpolate(cssPageRectBottom, to.cssPageRectBottom, t), - FloatUtils.interpolate(viewportRectLeft, to.viewportRectLeft, t), - FloatUtils.interpolate(viewportRectTop, to.viewportRectTop, t), - (int)FloatUtils.interpolate(viewportRectWidth, to.viewportRectWidth, t), - (int)FloatUtils.interpolate(viewportRectHeight, to.viewportRectHeight, t), - FloatUtils.interpolate(zoomFactor, to.zoomFactor, t)); - } - - public ImmutableViewportMetrics setViewportSize(int width, int height) { - if (width == viewportRectWidth && height == viewportRectHeight) { - return this; - } - - return new ImmutableViewportMetrics( - pageRectLeft, pageRectTop, pageRectRight, pageRectBottom, - cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom, - viewportRectLeft, viewportRectTop, width, height, - zoomFactor); - } - - public ImmutableViewportMetrics setViewportOrigin(float newOriginX, float newOriginY) { - return new ImmutableViewportMetrics( - pageRectLeft, pageRectTop, pageRectRight, pageRectBottom, - cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom, - newOriginX, newOriginY, viewportRectWidth, viewportRectHeight, - zoomFactor); - } - - public ImmutableViewportMetrics setZoomFactor(float newZoomFactor) { - return new ImmutableViewportMetrics( - pageRectLeft, pageRectTop, pageRectRight, pageRectBottom, - cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom, - viewportRectLeft, viewportRectTop, viewportRectWidth, viewportRectHeight, - newZoomFactor); - } - - public ImmutableViewportMetrics offsetViewportBy(float dx, float dy) { - return setViewportOrigin(viewportRectLeft + dx, viewportRectTop + dy); - } - - public ImmutableViewportMetrics offsetViewportByAndClamp(float dx, float dy) { - return setViewportOrigin( - Math.max(pageRectLeft, Math.min(viewportRectLeft + dx, pageRectRight - getWidth())), - Math.max(pageRectTop, Math.min(viewportRectTop + dy, pageRectBottom - getHeight()))); - } - - public ImmutableViewportMetrics setPageRect(RectF pageRect, RectF cssPageRect) { - return new ImmutableViewportMetrics( - pageRect.left, pageRect.top, pageRect.right, pageRect.bottom, - cssPageRect.left, cssPageRect.top, cssPageRect.right, cssPageRect.bottom, - viewportRectLeft, viewportRectTop, viewportRectWidth, viewportRectHeight, - zoomFactor); - } - - public ImmutableViewportMetrics setPageRectFrom(ImmutableViewportMetrics aMetrics) { - if (aMetrics.cssPageRectLeft == cssPageRectLeft && - aMetrics.cssPageRectTop == cssPageRectTop && - aMetrics.cssPageRectRight == cssPageRectRight && - aMetrics.cssPageRectBottom == cssPageRectBottom) { - return this; - } - RectF css = aMetrics.getCssPageRect(); - return setPageRect(RectUtils.scale(css, zoomFactor), css); - } - - /* This will set the zoom factor and re-scale page-size and viewport offset - * accordingly. The given focus will remain at the same point on the screen - * after scaling. - */ - public ImmutableViewportMetrics scaleTo(float newZoomFactor, PointF focus) { - // cssPageRect* is invariant, since we're setting the scale factor - // here. The page rect is based on the CSS page rect. - float newPageRectLeft = cssPageRectLeft * newZoomFactor; - float newPageRectTop = cssPageRectTop * newZoomFactor; - float newPageRectRight = cssPageRectLeft + ((cssPageRectRight - cssPageRectLeft) * newZoomFactor); - float newPageRectBottom = cssPageRectTop + ((cssPageRectBottom - cssPageRectTop) * newZoomFactor); - - PointF origin = getOrigin(); - origin.offset(focus.x, focus.y); - origin = PointUtils.scale(origin, newZoomFactor / zoomFactor); - origin.offset(-focus.x, -focus.y); - - return new ImmutableViewportMetrics( - newPageRectLeft, newPageRectTop, newPageRectRight, newPageRectBottom, - cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom, - origin.x, origin.y, viewportRectWidth, viewportRectHeight, - newZoomFactor); - } - - /** Clamps the viewport to remain within the page rect. */ - public ImmutableViewportMetrics clamp() { - RectF newViewport = getViewport(); - - // The viewport bounds ought to never exceed the page bounds. - if (newViewport.right > pageRectRight) - newViewport.offset((pageRectRight) - newViewport.right, 0); - if (newViewport.left < pageRectLeft) - newViewport.offset(pageRectLeft - newViewport.left, 0); - - if (newViewport.bottom > pageRectBottom) - newViewport.offset(0, (pageRectBottom) - newViewport.bottom); - if (newViewport.top < pageRectTop) - newViewport.offset(0, pageRectTop - newViewport.top); - - // Note that since newViewport is only translated around, the viewport's - // width and height are unchanged. - return new ImmutableViewportMetrics( - pageRectLeft, pageRectTop, pageRectRight, pageRectBottom, - cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom, - newViewport.left, newViewport.top, viewportRectWidth, viewportRectHeight, - zoomFactor); - } - - public boolean fuzzyEquals(ImmutableViewportMetrics other) { - // Don't bother checking the pageRectXXX values because they are a product - // of the cssPageRectXXX values and the zoomFactor, except with more rounding - // error. Checking those is both inefficient and can lead to false negatives. - return FloatUtils.fuzzyEquals(cssPageRectLeft, other.cssPageRectLeft) - && FloatUtils.fuzzyEquals(cssPageRectTop, other.cssPageRectTop) - && FloatUtils.fuzzyEquals(cssPageRectRight, other.cssPageRectRight) - && FloatUtils.fuzzyEquals(cssPageRectBottom, other.cssPageRectBottom) - && FloatUtils.fuzzyEquals(viewportRectLeft, other.viewportRectLeft) - && FloatUtils.fuzzyEquals(viewportRectTop, other.viewportRectTop) - && viewportRectWidth == other.viewportRectWidth - && viewportRectHeight == other.viewportRectHeight - && FloatUtils.fuzzyEquals(zoomFactor, other.zoomFactor); - } - - @Override - public String toString() { - return "ImmutableViewportMetrics v=(" + viewportRectLeft + "," + viewportRectTop + "," - + viewportRectWidth + "x" + viewportRectHeight + ") p=(" + pageRectLeft + "," - + pageRectTop + "," + pageRectRight + "," + pageRectBottom + ") c=(" - + cssPageRectLeft + "," + cssPageRectTop + "," + cssPageRectRight + "," - + cssPageRectBottom + ") z=" + zoomFactor; - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/IntSize.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/IntSize.java deleted file mode 100644 index 0e847158d..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/IntSize.java +++ /dev/null @@ -1,89 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.gfx; - -import org.json.JSONException; -import org.json.JSONObject; - -public class IntSize { - public final int width, height; - - public IntSize(IntSize size) { width = size.width; height = size.height; } - public IntSize(int inWidth, int inHeight) { width = inWidth; height = inHeight; } - - public IntSize(FloatSize size) { - width = Math.round(size.width); - height = Math.round(size.height); - } - - public IntSize(JSONObject json) { - try { - width = json.getInt("width"); - height = json.getInt("height"); - } catch (JSONException e) { - throw new RuntimeException(e); - } - } - - public int getArea() { - return width * height; - } - - public boolean equals(IntSize size) { - return ((size.width == width) && (size.height == height)); - } - - public boolean isPositive() { - return (width > 0 && height > 0); - } - - @Override - public String toString() { return "(" + width + "," + height + ")"; } - - public IntSize scale(float factor) { - return new IntSize(Math.round(width * factor), - Math.round(height * factor)); - } - - /* Returns the power of two that is greater than or equal to value */ - public static int nextPowerOfTwo(int value) { - // code taken from http://acius2.blogspot.com/2007/11/calculating-next-power-of-2.html - if (0 == value--) { - return 1; - } - value = (value >> 1) | value; - value = (value >> 2) | value; - value = (value >> 4) | value; - value = (value >> 8) | value; - value = (value >> 16) | value; - return value + 1; - } - - public IntSize nextPowerOfTwo() { - return new IntSize(nextPowerOfTwo(width), nextPowerOfTwo(height)); - } - - public static boolean isPowerOfTwo(int value) { - if (value == 0) - return false; - return (value & (value - 1)) == 0; - } - - public static int largestPowerOfTwoLessThan(float value) { - int val = (int) Math.floor(value); - if (val <= 0) { - throw new IllegalArgumentException("Error: value must be > 0"); - } - // keep dropping the least-significant set bits until only one is left - int bestVal = val; - while (val != 0) { - bestVal = val; - val &= (val - 1); - } - return bestVal; - } -} - diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerRenderer.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerRenderer.java deleted file mode 100644 index 1a087cc2a..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerRenderer.java +++ /dev/null @@ -1,275 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.gfx; - -import org.mozilla.gecko.AppConstants; -import org.mozilla.gecko.GeckoAppShell; -import org.mozilla.gecko.mozglue.DirectBufferAllocator; - -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Matrix; -import android.graphics.PointF; -import android.graphics.Rect; -import android.graphics.RectF; -import android.opengl.GLES20; -import android.os.SystemClock; -import android.util.Log; - -import org.mozilla.gecko.annotation.WrapForJNI; -import org.mozilla.gecko.util.ThreadUtils; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.FloatBuffer; -import java.nio.IntBuffer; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.ArrayList; -import java.util.List; - -import javax.microedition.khronos.egl.EGLConfig; - -/** - * The layer renderer implements the rendering logic for a layer view. - */ -public class LayerRenderer { - private static final String LOGTAG = "GeckoLayerRenderer"; - - /* - * The amount of time a frame is allowed to take to render before we declare it a dropped - * frame. - */ - private static final int MAX_FRAME_TIME = 16; /* 1000 ms / 60 FPS */ - private static final long NANOS_PER_MS = 1000000; - private static final int MAX_SCROLL_SPEED_TO_REQUEST_ZOOM_RENDER = 5; - - private final LayerView mView; - private ByteBuffer mCoordByteBuffer; - private FloatBuffer mCoordBuffer; - private int mMaxTextureSize; - - private long mLastFrameTime; - private final CopyOnWriteArrayList<RenderTask> mTasks; - - // Dropped frames display - private final int[] mFrameTimings; - private int mCurrentFrame, mFrameTimingsSum, mDroppedFrames; - - private IntBuffer mPixelBuffer; - private List<LayerView.ZoomedViewListener> mZoomedViewListeners; - private float mLastViewLeft; - private float mLastViewTop; - - public LayerRenderer(LayerView view) { - mView = view; - - mTasks = new CopyOnWriteArrayList<RenderTask>(); - mLastFrameTime = System.nanoTime(); - - mFrameTimings = new int[60]; - mCurrentFrame = mFrameTimingsSum = mDroppedFrames = 0; - - mZoomedViewListeners = new ArrayList<LayerView.ZoomedViewListener>(); - } - - public void destroy() { - if (mCoordByteBuffer != null) { - DirectBufferAllocator.free(mCoordByteBuffer); - mCoordByteBuffer = null; - mCoordBuffer = null; - } - mZoomedViewListeners.clear(); - } - - void onSurfaceCreated(EGLConfig config) { - createDefaultProgram(); - } - - public void createDefaultProgram() { - int maxTextureSizeResult[] = new int[1]; - GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxTextureSizeResult, 0); - mMaxTextureSize = maxTextureSizeResult[0]; - } - - public int getMaxTextureSize() { - return mMaxTextureSize; - } - - public void postRenderTask(RenderTask aTask) { - mTasks.add(aTask); - mView.requestRender(); - } - - public void removeRenderTask(RenderTask aTask) { - mTasks.remove(aTask); - } - - private void runRenderTasks(CopyOnWriteArrayList<RenderTask> tasks, boolean after, long frameStartTime) { - for (RenderTask task : tasks) { - if (task.runAfter != after) { - continue; - } - - boolean stillRunning = task.run(frameStartTime - mLastFrameTime, frameStartTime); - - // Remove the task from the list if its finished - if (!stillRunning) { - tasks.remove(task); - } - } - } - - /** Used by robocop for testing purposes. Not for production use! */ - IntBuffer getPixels() { - IntBuffer pixelBuffer = IntBuffer.allocate(mView.getWidth() * mView.getHeight()); - synchronized (pixelBuffer) { - mPixelBuffer = pixelBuffer; - mView.requestRender(); - try { - pixelBuffer.wait(); - } catch (InterruptedException ie) { - } - mPixelBuffer = null; - } - return pixelBuffer; - } - - private void updateDroppedFrames(long frameStartTime) { - int frameElapsedTime = (int)((System.nanoTime() - frameStartTime) / NANOS_PER_MS); - - /* Update the running statistics. */ - mFrameTimingsSum -= mFrameTimings[mCurrentFrame]; - mFrameTimingsSum += frameElapsedTime; - mDroppedFrames -= (mFrameTimings[mCurrentFrame] + 1) / MAX_FRAME_TIME; - mDroppedFrames += (frameElapsedTime + 1) / MAX_FRAME_TIME; - - mFrameTimings[mCurrentFrame] = frameElapsedTime; - mCurrentFrame = (mCurrentFrame + 1) % mFrameTimings.length; - - int averageTime = mFrameTimingsSum / mFrameTimings.length; - } - - public Frame createFrame(ImmutableViewportMetrics metrics) { - return new Frame(metrics); - } - - public class Frame { - // The timestamp recording the start of this frame. - private long mFrameStartTime; - // A fixed snapshot of the viewport metrics that this frame is using to render content. - private final ImmutableViewportMetrics mFrameMetrics; - - public Frame(ImmutableViewportMetrics metrics) { - mFrameMetrics = metrics; - } - - /** This function is invoked via JNI; be careful when modifying signature. */ - @WrapForJNI - public void beginDrawing() { - mFrameStartTime = System.nanoTime(); - - // Run through pre-render tasks - runRenderTasks(mTasks, false, mFrameStartTime); - } - - - private void maybeRequestZoomedViewRender() { - // Concurrently update of mZoomedViewListeners should not be an issue here - // because the following line is just a short-circuit - if (mZoomedViewListeners.size() == 0) { - return; - } - - // When scrolling fast, do not request zoomed view render to avoid to slow down - // the scroll in the main view. - // Speed is estimated using the offset changes between 2 display frame calls - final float viewLeft = Math.round(mFrameMetrics.getViewport().left); - final float viewTop = Math.round(mFrameMetrics.getViewport().top); - boolean shouldWaitToRender = false; - - if (Math.abs(mLastViewLeft - viewLeft) > MAX_SCROLL_SPEED_TO_REQUEST_ZOOM_RENDER || - Math.abs(mLastViewTop - viewTop) > MAX_SCROLL_SPEED_TO_REQUEST_ZOOM_RENDER) { - shouldWaitToRender = true; - } - - mLastViewLeft = viewLeft; - mLastViewTop = viewTop; - - if (shouldWaitToRender) { - return; - } - - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - for (LayerView.ZoomedViewListener listener : mZoomedViewListeners) { - listener.requestZoomedViewRender(); - } - } - }); - } - - - /** This function is invoked via JNI; be careful when modifying signature. */ - @WrapForJNI - public void endDrawing() { - PanningPerfAPI.recordFrameTime(); - - runRenderTasks(mTasks, true, mFrameStartTime); - maybeRequestZoomedViewRender(); - - /* Used by robocop for testing purposes */ - IntBuffer pixelBuffer = mPixelBuffer; - if (pixelBuffer != null) { - synchronized (pixelBuffer) { - pixelBuffer.position(0); - GLES20.glReadPixels(0, 0, Math.round(mFrameMetrics.getWidth()), - Math.round(mFrameMetrics.getHeight()), GLES20.GL_RGBA, - GLES20.GL_UNSIGNED_BYTE, pixelBuffer); - pixelBuffer.notify(); - } - } - - // Remove background color once we've painted. GeckoLayerClient is - // responsible for setting this flag before current document is - // composited. - if (mView.getPaintState() == LayerView.PAINT_BEFORE_FIRST) { - mView.post(new Runnable() { - @Override - public void run() { - mView.setSurfaceBackgroundColor(Color.TRANSPARENT); - } - }); - mView.setPaintState(LayerView.PAINT_AFTER_FIRST); - } - mLastFrameTime = mFrameStartTime; - } - } - - public void updateZoomedView(final ByteBuffer data) { - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - for (LayerView.ZoomedViewListener listener : mZoomedViewListeners) { - data.position(0); - listener.updateView(data); - } - } - }); - } - - public void addZoomedViewListener(LayerView.ZoomedViewListener listener) { - ThreadUtils.assertOnUiThread(); - mZoomedViewListeners.add(listener); - } - - public void removeZoomedViewListener(LayerView.ZoomedViewListener listener) { - ThreadUtils.assertOnUiThread(); - mZoomedViewListeners.remove(listener); - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerView.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerView.java deleted file mode 100644 index 969aa3f24..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerView.java +++ /dev/null @@ -1,711 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.gfx; - -import java.nio.ByteBuffer; -import java.nio.IntBuffer; - -import org.mozilla.gecko.AndroidGamepadManager; -import org.mozilla.gecko.annotation.RobocopTarget; -import org.mozilla.gecko.annotation.WrapForJNI; -import org.mozilla.gecko.AppConstants; -import org.mozilla.gecko.AppConstants.Versions; -import org.mozilla.gecko.EventDispatcher; -import org.mozilla.gecko.GeckoAccessibility; -import org.mozilla.gecko.GeckoAppShell; -import org.mozilla.gecko.GeckoThread; -import org.mozilla.gecko.mozglue.JNIObject; -import org.mozilla.gecko.util.ThreadUtils; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Matrix; -import android.graphics.Point; -import android.graphics.PointF; -import android.graphics.Rect; -import android.graphics.SurfaceTexture; -import android.os.Parcelable; -import android.util.AttributeSet; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.SurfaceHolder; -import android.view.SurfaceView; -import android.view.TextureView; -import android.view.View; -import android.view.ViewGroup; -import android.view.InputDevice; -import android.widget.FrameLayout; - -/** - * A view rendered by the layer compositor. - */ -public class LayerView extends FrameLayout { - private static final String LOGTAG = "GeckoLayerView"; - - private GeckoLayerClient mLayerClient; - private PanZoomController mPanZoomController; - private DynamicToolbarAnimator mToolbarAnimator; - private LayerRenderer mRenderer; - /* Must be a PAINT_xxx constant */ - private int mPaintState; - private FullScreenState mFullScreenState; - - private SurfaceView mSurfaceView; - private TextureView mTextureView; - - private Listener mListener; - - /* This should only be modified on the Java UI thread. */ - private final Overscroll mOverscroll; - - private boolean mServerSurfaceValid; - private int mWidth, mHeight; - - private boolean onAttachedToWindowCalled; - - /* This is written by the Gecko thread and the UI thread, and read by the UI thread. */ - @WrapForJNI(stubName = "CompositorCreated", calledFrom = "ui") - /* package */ volatile boolean mCompositorCreated; - - private class Compositor extends JNIObject { - public Compositor() { - } - - @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko") - @Override protected native void disposeNative(); - - // Gecko thread sets its Java instances; does not block UI thread. - @WrapForJNI(calledFrom = "any", dispatchTo = "gecko") - /* package */ native void attachToJava(GeckoLayerClient layerClient, - NativePanZoomController npzc); - - @WrapForJNI(calledFrom = "any", dispatchTo = "gecko") - /* package */ native void onSizeChanged(int windowWidth, int windowHeight, - int screenWidth, int screenHeight); - - // Gecko thread creates compositor; blocks UI thread. - @WrapForJNI(calledFrom = "ui", dispatchTo = "proxy") - /* package */ native void createCompositor(int width, int height, Object surface); - - // Gecko thread pauses compositor; blocks UI thread. - @WrapForJNI(calledFrom = "ui", dispatchTo = "current") - /* package */ native void syncPauseCompositor(); - - // UI thread resumes compositor and notifies Gecko thread; does not block UI thread. - @WrapForJNI(calledFrom = "ui", dispatchTo = "current") - /* package */ native void syncResumeResizeCompositor(int width, int height, Object surface); - - @WrapForJNI(calledFrom = "any", dispatchTo = "current") - /* package */ native void syncInvalidateAndScheduleComposite(); - - @WrapForJNI(calledFrom = "gecko") - private void reattach() { - mCompositorCreated = true; - } - - @WrapForJNI(calledFrom = "gecko") - private void destroy() { - // The nsWindow has been closed. First mark our compositor as destroyed. - LayerView.this.mCompositorCreated = false; - - LayerView.this.mLayerClient.setGeckoReady(false); - - // Then clear out any pending calls on the UI thread by disposing on the UI thread. - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - disposeNative(); - } - }); - } - } - - private final Compositor mCompositor = new Compositor(); - - /* Flags used to determine when to show the painted surface. */ - public static final int PAINT_START = 0; - public static final int PAINT_BEFORE_FIRST = 1; - public static final int PAINT_AFTER_FIRST = 2; - - public boolean shouldUseTextureView() { - // Disable TextureView support for now as it causes panning/zooming - // performance regressions (see bug 792259). Uncomment the code below - // once this bug is fixed. - return false; - - /* - // we can only use TextureView on ICS or higher - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - Log.i(LOGTAG, "Not using TextureView: not on ICS+"); - return false; - } - - try { - // and then we can only use it if we have a hardware accelerated window - Method m = View.class.getMethod("isHardwareAccelerated", (Class[]) null); - return (Boolean) m.invoke(this); - } catch (Exception e) { - Log.i(LOGTAG, "Not using TextureView: caught exception checking for hw accel: " + e.toString()); - return false; - } */ - } - - public LayerView(Context context, AttributeSet attrs) { - super(context, attrs); - - mPaintState = PAINT_START; - mFullScreenState = FullScreenState.NONE; - - mOverscroll = new OverscrollEdgeEffect(this); - } - - public LayerView(Context context) { - this(context, null); - } - - public void initializeView(EventDispatcher eventDispatcher) { - mLayerClient = new GeckoLayerClient(getContext(), this, eventDispatcher); - if (mOverscroll != null) { - mLayerClient.setOverscrollHandler(mOverscroll); - } - - mPanZoomController = mLayerClient.getPanZoomController(); - mToolbarAnimator = mLayerClient.getDynamicToolbarAnimator(); - - mRenderer = new LayerRenderer(this); - - setFocusable(true); - setFocusableInTouchMode(true); - - GeckoAccessibility.setDelegate(this); - } - - /** - * MotionEventHelper dragAsync() robocop tests can instruct - * PanZoomController not to generate longpress events. - */ - public void setIsLongpressEnabled(boolean isLongpressEnabled) { - mPanZoomController.setIsLongpressEnabled(isLongpressEnabled); - } - - private static Point getEventRadius(MotionEvent event) { - return new Point((int)event.getToolMajor() / 2, - (int)event.getToolMinor() / 2); - } - - public void showSurface() { - // Fix this if TextureView support is turned back on above - mSurfaceView.setVisibility(View.VISIBLE); - } - - public void hideSurface() { - // Fix this if TextureView support is turned back on above - mSurfaceView.setVisibility(View.INVISIBLE); - } - - public void destroy() { - if (mLayerClient != null) { - mLayerClient.destroy(); - } - if (mRenderer != null) { - mRenderer.destroy(); - } - } - - @Override - public void dispatchDraw(final Canvas canvas) { - super.dispatchDraw(canvas); - - // We must have a layer client to get valid viewport metrics - if (mLayerClient != null && mOverscroll != null) { - mOverscroll.draw(canvas, getViewportMetrics()); - } - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - requestFocus(); - } - - if (mToolbarAnimator != null && mToolbarAnimator.onInterceptTouchEvent(event)) { - if (mPanZoomController != null) { - mPanZoomController.onMotionEventVelocity(event.getEventTime(), mToolbarAnimator.getVelocity()); - } - return true; - } - if (!mLayerClient.isGeckoReady()) { - // If gecko isn't loaded yet, don't try sending events to the - // native code because it's just going to crash - return true; - } - if (mPanZoomController != null && mPanZoomController.onTouchEvent(event)) { - return true; - } - return false; - } - - @Override - public boolean onHoverEvent(MotionEvent event) { - // If we get a touchscreen hover event, and accessibility is not enabled, - // don't send it to gecko. - if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN && - !GeckoAccessibility.isEnabled()) { - return false; - } - - if (!mLayerClient.isGeckoReady()) { - // If gecko isn't loaded yet, don't try sending events to the - // native code because it's just going to crash - return true; - } else if (mPanZoomController != null && mPanZoomController.onMotionEvent(event)) { - return true; - } - - return false; - } - - @Override - public boolean onGenericMotionEvent(MotionEvent event) { - if (AndroidGamepadManager.handleMotionEvent(event)) { - return true; - } - if (!mLayerClient.isGeckoReady()) { - // If gecko isn't loaded yet, don't try sending events to the - // native code because it's just going to crash - return true; - } - if (mPanZoomController != null && mPanZoomController.onMotionEvent(event)) { - return true; - } - return false; - } - - @Override - protected void onRestoreInstanceState(final Parcelable state) { - if (onAttachedToWindowCalled) { - attachCompositor(); - } - super.onRestoreInstanceState(state); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - - // We are adding descendants to this LayerView, but we don't want the - // descendants to affect the way LayerView retains its focus. - setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS); - - // This check should not be done before the view is attached to a window - // as hardware acceleration will not be enabled at that point. - // We must create and add the SurfaceView instance before the view tree - // is fully created to avoid flickering (see bug 801477). - if (shouldUseTextureView()) { - mTextureView = new TextureView(getContext()); - mTextureView.setSurfaceTextureListener(new SurfaceTextureListener()); - - // The background is set to this color when the LayerView is - // created, and it will be shown immediately at startup. Shortly - // after, the tab's background color will be used before any content - // is shown. - mTextureView.setBackgroundColor(Color.WHITE); - addView(mTextureView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); - } else { - // This will stop PropertyAnimator from creating a drawing cache (i.e. a bitmap) - // from a SurfaceView, which is just not possible (the bitmap will be transparent). - setWillNotCacheDrawing(false); - - mSurfaceView = new LayerSurfaceView(getContext(), this); - mSurfaceView.setBackgroundColor(Color.WHITE); - addView(mSurfaceView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - - SurfaceHolder holder = mSurfaceView.getHolder(); - holder.addCallback(new SurfaceListener()); - } - - attachCompositor(); - - onAttachedToWindowCalled = true; - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - - onAttachedToWindowCalled = false; - } - - // Don't expose GeckoLayerClient to things outside this package; only expose it as an Object - GeckoLayerClient getLayerClient() { return mLayerClient; } - - public PanZoomController getPanZoomController() { return mPanZoomController; } - public DynamicToolbarAnimator getDynamicToolbarAnimator() { return mToolbarAnimator; } - - public ImmutableViewportMetrics getViewportMetrics() { - return mLayerClient.getViewportMetrics(); - } - - public Matrix getMatrixForLayerRectToViewRect() { - return mLayerClient.getMatrixForLayerRectToViewRect(); - } - - public void setSurfaceBackgroundColor(int newColor) { - if (mSurfaceView != null) { - mSurfaceView.setBackgroundColor(newColor); - } - } - - public void requestRender() { - if (mCompositorCreated) { - mCompositor.syncInvalidateAndScheduleComposite(); - } - } - - public void postRenderTask(RenderTask task) { - mRenderer.postRenderTask(task); - } - - public void removeRenderTask(RenderTask task) { - mRenderer.removeRenderTask(task); - } - - public int getMaxTextureSize() { - return mRenderer.getMaxTextureSize(); - } - - /** Used by robocop for testing purposes. Not for production use! */ - @RobocopTarget - public IntBuffer getPixels() { - return mRenderer.getPixels(); - } - - /* paintState must be a PAINT_xxx constant. */ - public void setPaintState(int paintState) { - mPaintState = paintState; - } - - public int getPaintState() { - return mPaintState; - } - - public LayerRenderer getRenderer() { - return mRenderer; - } - - public void setListener(Listener listener) { - mListener = listener; - } - - Listener getListener() { - return mListener; - } - - private void attachCompositor() { - final NativePanZoomController npzc = (NativePanZoomController) mPanZoomController; - - if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) { - mCompositor.attachToJava(mLayerClient, npzc); - } else { - GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY, - mCompositor, "attachToJava", - GeckoLayerClient.class, mLayerClient, - NativePanZoomController.class, npzc); - } - } - - @WrapForJNI(calledFrom = "ui") - protected Object getCompositor() { - return mCompositor; - } - - void serverSurfaceChanged(int newWidth, int newHeight) { - ThreadUtils.assertOnUiThread(); - - mWidth = newWidth; - mHeight = newHeight; - mServerSurfaceValid = true; - - updateCompositor(); - } - - void updateCompositor() { - ThreadUtils.assertOnUiThread(); - - if (mCompositorCreated) { - // If the compositor has already been created, just resume it instead. We don't need - // to block here because if the surface is destroyed before the compositor grabs it, - // we can handle that gracefully (i.e. the compositor will remain paused). - if (!mServerSurfaceValid) { - return; - } - // Asking Gecko to resume the compositor takes too long (see - // https://bugzilla.mozilla.org/show_bug.cgi?id=735230#c23), so we - // resume the compositor directly. We still need to inform Gecko about - // the compositor resuming, so that Gecko knows that it can now draw. - // It is important to not notify Gecko until after the compositor has - // been resumed, otherwise Gecko may send updates that get dropped. - mCompositor.syncResumeResizeCompositor(mWidth, mHeight, getSurface()); - return; - } - - // Only try to create the compositor if we have a valid surface and gecko is up. When these - // two conditions are satisfied, we can be relatively sure that the compositor creation will - // happen without needing to block anywhere. - if (mServerSurfaceValid && getLayerClient().isGeckoReady()) { - mCompositorCreated = true; - mCompositor.createCompositor(mWidth, mHeight, getSurface()); - } - } - - /* When using a SurfaceView (mSurfaceView != null), resizing happens in two - * phases. First, the LayerView changes size, then, often some frames later, - * the SurfaceView changes size. Because of this, we need to split the - * resize into two phases to avoid jittering. - * - * The first phase is the LayerView size change. mListener is notified so - * that a synchronous draw can be performed (otherwise a blank frame will - * appear). - * - * The second phase is the SurfaceView size change. At this point, the - * backing GL surface is resized and another synchronous draw is performed. - * Gecko is also sent the new window size, and this will likely cause an - * extra draw a few frames later, after it's re-rendered and caught up. - * - * In the case that there is no valid GL surface (for example, when - * resuming, or when coming back from the awesomescreen), or we're using a - * TextureView instead of a SurfaceView, the first phase is skipped. - */ - private void onSizeChanged(int width, int height) { - if (!mServerSurfaceValid || mSurfaceView == null) { - surfaceChanged(width, height); - return; - } - - if (mCompositorCreated) { - mCompositor.syncResumeResizeCompositor(width, height, getSurface()); - } - - if (mOverscroll != null) { - mOverscroll.setSize(width, height); - } - } - - private void surfaceChanged(int width, int height) { - serverSurfaceChanged(width, height); - - if (mListener != null) { - mListener.surfaceChanged(width, height); - } - - if (mOverscroll != null) { - mOverscroll.setSize(width, height); - } - } - - void notifySizeChanged(int windowWidth, int windowHeight, int screenWidth, int screenHeight) { - mCompositor.onSizeChanged(windowWidth, windowHeight, screenWidth, screenHeight); - } - - void serverSurfaceDestroyed() { - ThreadUtils.assertOnUiThread(); - - // We need to coordinate with Gecko when pausing composition, to ensure - // that Gecko never executes a draw event while the compositor is paused. - // This is sent synchronously to make sure that we don't attempt to use - // any outstanding Surfaces after we call this (such as from a - // serverSurfaceDestroyed notification), and to make sure that any in-flight - // Gecko draw events have been processed. When this returns, composition is - // definitely paused -- it'll synchronize with the Gecko event loop, which - // in turn will synchronize with the compositor thread. - if (mCompositorCreated) { - mCompositor.syncPauseCompositor(); - } - - mServerSurfaceValid = false; - } - - private void onDestroyed() { - serverSurfaceDestroyed(); - } - - public Object getNativeWindow() { - if (mSurfaceView != null) - return mSurfaceView.getHolder(); - - return mTextureView.getSurfaceTexture(); - } - - public Object getSurface() { - if (mSurfaceView != null) { - return mSurfaceView.getHolder().getSurface(); - } - return null; - } - - // This method is called on the Gecko main thread. - @WrapForJNI(calledFrom = "gecko") - private static void updateZoomedView(ByteBuffer data) { - LayerView layerView = GeckoAppShell.getLayerView(); - if (layerView != null) { - LayerRenderer layerRenderer = layerView.getRenderer(); - if (layerRenderer != null) { - layerRenderer.updateZoomedView(data); - } - } - } - - public interface Listener { - void surfaceChanged(int width, int height); - } - - private class SurfaceListener implements SurfaceHolder.Callback { - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width, - int height) { - onSizeChanged(width, height); - } - - @Override - public void surfaceCreated(SurfaceHolder holder) { - } - - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - onDestroyed(); - } - } - - /* A subclass of SurfaceView to listen to layout changes, as - * View.OnLayoutChangeListener requires API level 11. - */ - private class LayerSurfaceView extends SurfaceView { - private LayerView mParent; - - public LayerSurfaceView(Context aContext, LayerView aParent) { - super(aContext); - mParent = aParent; - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - if (changed && mParent.mServerSurfaceValid) { - mParent.surfaceChanged(right - left, bottom - top); - } - } - } - - private class SurfaceTextureListener implements TextureView.SurfaceTextureListener { - @Override - public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { - // We don't do this for surfaceCreated above because it is always followed by a surfaceChanged, - // but that is not the case here. - onSizeChanged(width, height); - } - - @Override - public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { - onDestroyed(); - return true; // allow Android to call release() on the SurfaceTexture, we are done drawing to it - } - - @Override - public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { - onSizeChanged(width, height); - } - - @Override - public void onSurfaceTextureUpdated(SurfaceTexture surface) { - - } - } - - @RobocopTarget - public void addDrawListener(DrawListener listener) { - mLayerClient.addDrawListener(listener); - } - - @RobocopTarget - public void removeDrawListener(DrawListener listener) { - mLayerClient.removeDrawListener(listener); - } - - @RobocopTarget - public static interface DrawListener { - public void drawFinished(); - } - - @Override - public void setOverScrollMode(int overscrollMode) { - super.setOverScrollMode(overscrollMode); - } - - @Override - public int getOverScrollMode() { - return super.getOverScrollMode(); - } - - public float getZoomFactor() { - return getLayerClient().getViewportMetrics().zoomFactor; - } - - @Override - public void onFocusChanged (boolean gainFocus, int direction, Rect previouslyFocusedRect) { - super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); - GeckoAccessibility.onLayerViewFocusChanged(gainFocus); - } - - public void setFullScreenState(FullScreenState state) { - mFullScreenState = state; - } - - public boolean isFullScreen() { - return mFullScreenState != FullScreenState.NONE; - } - - public void setMaxTranslation(float aMaxTranslation) { - mToolbarAnimator.setMaxTranslation(aMaxTranslation); - } - - public void setSurfaceTranslation(float translation) { - setTranslationY(translation); - } - - public float getSurfaceTranslation() { - return getTranslationY(); - } - - // Public hooks for dynamic toolbar translation - - public interface DynamicToolbarListener { - public void onTranslationChanged(float aToolbarTranslation, float aLayerViewTranslation); - public void onPanZoomStopped(); - public void onMetricsChanged(ImmutableViewportMetrics viewport); - } - - // Public hooks for zoomed view - - public interface ZoomedViewListener { - public void requestZoomedViewRender(); - public void updateView(ByteBuffer data); - } - - public void addZoomedViewListener(ZoomedViewListener listener) { - mRenderer.addZoomedViewListener(listener); - } - - public void removeZoomedViewListener(ZoomedViewListener listener) { - mRenderer.removeZoomedViewListener(listener); - } - - public void setClearColor(int color) { - if (mLayerClient != null) { - mLayerClient.setClearColor(color); - } - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/NativePanZoomController.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/NativePanZoomController.java deleted file mode 100644 index 6b643c85b..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/NativePanZoomController.java +++ /dev/null @@ -1,300 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.gfx; - -import org.mozilla.gecko.GeckoAppShell; -import org.mozilla.gecko.GeckoThread; -import org.mozilla.gecko.PrefsHelper; -import org.mozilla.gecko.annotation.WrapForJNI; -import org.mozilla.gecko.gfx.DynamicToolbarAnimator.PinReason; -import org.mozilla.gecko.mozglue.JNIObject; -import org.mozilla.gecko.util.ThreadUtils; - -import org.json.JSONObject; - -import android.graphics.PointF; -import android.util.TypedValue; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.View; -import android.view.InputDevice; - -class NativePanZoomController extends JNIObject implements PanZoomController { - private final PanZoomTarget mTarget; - private final LayerView mView; - private boolean mDestroyed; - private Overscroll mOverscroll; - boolean mNegateWheelScroll; - private float mPointerScrollFactor; - private PrefsHelper.PrefHandler mPrefsObserver; - private long mLastDownTime; - private static final float MAX_SCROLL = 0.075f * GeckoAppShell.getDpi(); - - @WrapForJNI(calledFrom = "ui") - private native boolean handleMotionEvent( - int action, int actionIndex, long time, int metaState, - int pointerId[], float x[], float y[], float orientation[], float pressure[], - float toolMajor[], float toolMinor[]); - - @WrapForJNI(calledFrom = "ui") - private native boolean handleScrollEvent( - long time, int metaState, - float x, float y, - float hScroll, float vScroll); - - @WrapForJNI(calledFrom = "ui") - private native boolean handleMouseEvent( - int action, long time, int metaState, - float x, float y, int buttons); - - @WrapForJNI(calledFrom = "ui") - private native void handleMotionEventVelocity(long time, float ySpeed); - - private boolean handleMotionEvent(MotionEvent event) { - if (mDestroyed) { - return false; - } - - final int action = event.getActionMasked(); - final int count = event.getPointerCount(); - - if (action == MotionEvent.ACTION_DOWN) { - mLastDownTime = event.getDownTime(); - } else if (mLastDownTime != event.getDownTime()) { - return false; - } - - final int[] pointerId = new int[count]; - final float[] x = new float[count]; - final float[] y = new float[count]; - final float[] orientation = new float[count]; - final float[] pressure = new float[count]; - final float[] toolMajor = new float[count]; - final float[] toolMinor = new float[count]; - - final MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords(); - - for (int i = 0; i < count; i++) { - pointerId[i] = event.getPointerId(i); - event.getPointerCoords(i, coords); - - x[i] = coords.x; - y[i] = coords.y; - - orientation[i] = coords.orientation; - pressure[i] = coords.pressure; - - // If we are converting to CSS pixels, we should adjust the radii as well. - toolMajor[i] = coords.toolMajor; - toolMinor[i] = coords.toolMinor; - } - - return handleMotionEvent(action, event.getActionIndex(), event.getEventTime(), - event.getMetaState(), pointerId, x, y, orientation, pressure, - toolMajor, toolMinor); - } - - private boolean handleScrollEvent(MotionEvent event) { - if (mDestroyed) { - return false; - } - - final int count = event.getPointerCount(); - - if (count <= 0) { - return false; - } - - final MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords(); - event.getPointerCoords(0, coords); - final float x = coords.x; - final float y = coords.y; - - final float flipFactor = mNegateWheelScroll ? -1.0f : 1.0f; - final float hScroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL) * flipFactor * mPointerScrollFactor; - final float vScroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL) * flipFactor * mPointerScrollFactor; - - return handleScrollEvent(event.getEventTime(), event.getMetaState(), x, y, hScroll, vScroll); - } - - private boolean handleMouseEvent(MotionEvent event) { - if (mDestroyed) { - return false; - } - - final int count = event.getPointerCount(); - - if (count <= 0) { - return false; - } - - final MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords(); - event.getPointerCoords(0, coords); - final float x = coords.x; - final float y = coords.y; - - return handleMouseEvent(event.getActionMasked(), event.getEventTime(), event.getMetaState(), x, y, event.getButtonState()); - } - - - NativePanZoomController(PanZoomTarget target, View view) { - mTarget = target; - mView = (LayerView) view; - mDestroyed = true; - - String[] prefs = { "ui.scrolling.negate_wheel_scroll" }; - mPrefsObserver = new PrefsHelper.PrefHandlerBase() { - @Override public void prefValue(String pref, boolean value) { - if (pref.equals("ui.scrolling.negate_wheel_scroll")) { - mNegateWheelScroll = value; - } - } - }; - PrefsHelper.addObserver(prefs, mPrefsObserver); - - TypedValue outValue = new TypedValue(); - if (view.getContext().getTheme().resolveAttribute(android.R.attr.listPreferredItemHeight, outValue, true)) { - mPointerScrollFactor = outValue.getDimension(view.getContext().getResources().getDisplayMetrics()); - } else { - mPointerScrollFactor = MAX_SCROLL; - } - } - - @Override - public boolean onTouchEvent(MotionEvent event) { -// NOTE: This commented out block of code allows Fennec to generate -// mouse event instead of converting them to touch events. -// This gives Fennec similar behaviour to desktop when using -// a mouse. -// -// if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) { -// return handleMouseEvent(event); -// } else { -// return handleMotionEvent(event); -// } - return handleMotionEvent(event); - } - - @Override - public boolean onMotionEvent(MotionEvent event) { - final int action = event.getActionMasked(); - if (action == MotionEvent.ACTION_SCROLL) { - if (event.getDownTime() >= mLastDownTime) { - mLastDownTime = event.getDownTime(); - } else if ((InputDevice.getDevice(event.getDeviceId()).getSources() & InputDevice.SOURCE_TOUCHPAD) == InputDevice.SOURCE_TOUCHPAD) { - return false; - } - return handleScrollEvent(event); - } else if ((action == MotionEvent.ACTION_HOVER_MOVE) || - (action == MotionEvent.ACTION_HOVER_ENTER) || - (action == MotionEvent.ACTION_HOVER_EXIT)) { - return handleMouseEvent(event); - } else { - return false; - } - } - - @Override - public void onMotionEventVelocity(final long aEventTime, final float aSpeedY) { - handleMotionEventVelocity(aEventTime, aSpeedY); - } - - @Override @WrapForJNI(calledFrom = "ui") // PanZoomController - public void destroy() { - if (mPrefsObserver != null) { - PrefsHelper.removeObserver(mPrefsObserver); - mPrefsObserver = null; - } - if (mDestroyed || !mTarget.isGeckoReady()) { - return; - } - mDestroyed = true; - disposeNative(); - } - - @Override - public void attach() { - mDestroyed = false; - } - - @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko") @Override // JNIObject - protected native void disposeNative(); - - @Override - public void setOverscrollHandler(final Overscroll handler) { - mOverscroll = handler; - } - - @WrapForJNI(stubName = "SetIsLongpressEnabled") // Called from test thread. - private native void nativeSetIsLongpressEnabled(boolean isLongpressEnabled); - - @Override // PanZoomController - public void setIsLongpressEnabled(boolean isLongpressEnabled) { - if (!mDestroyed) { - nativeSetIsLongpressEnabled(isLongpressEnabled); - } - } - - @WrapForJNI(calledFrom = "ui") - private native void adjustScrollForSurfaceShift(float aX, float aY); - - @Override // PanZoomController - public ImmutableViewportMetrics adjustScrollForSurfaceShift(ImmutableViewportMetrics aMetrics, PointF aShift) { - adjustScrollForSurfaceShift(aShift.x, aShift.y); - return aMetrics.offsetViewportByAndClamp(aShift.x, aShift.y); - } - - @WrapForJNI - private void updateOverscrollVelocity(final float x, final float y) { - if (mOverscroll != null) { - if (ThreadUtils.isOnUiThread() == true) { - mOverscroll.setVelocity(x * 1000.0f, Overscroll.Axis.X); - mOverscroll.setVelocity(y * 1000.0f, Overscroll.Axis.Y); - } else { - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - // Multiply the velocity by 1000 to match what was done in JPZ. - mOverscroll.setVelocity(x * 1000.0f, Overscroll.Axis.X); - mOverscroll.setVelocity(y * 1000.0f, Overscroll.Axis.Y); - } - }); - } - } - } - - @WrapForJNI - private void updateOverscrollOffset(final float x, final float y) { - if (mOverscroll != null) { - if (ThreadUtils.isOnUiThread() == true) { - mOverscroll.setDistance(x, Overscroll.Axis.X); - mOverscroll.setDistance(y, Overscroll.Axis.Y); - } else { - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - mOverscroll.setDistance(x, Overscroll.Axis.X); - mOverscroll.setDistance(y, Overscroll.Axis.Y); - } - }); - } - } - } - - @WrapForJNI(calledFrom = "ui") - private void setScrollingRootContent(final boolean isRootContent) { - mTarget.setScrollingRootContent(isRootContent); - } - - /** - * Active SelectionCaretDrag requires DynamicToolbarAnimator to be pinned - * to avoid unwanted scroll interactions. - */ - @WrapForJNI(calledFrom = "gecko") - private void onSelectionDragState(boolean state) { - mView.getDynamicToolbarAnimator().setPinned(state, PinReason.CARET_DRAG); - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/Overscroll.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/Overscroll.java deleted file mode 100644 index e442444d5..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/Overscroll.java +++ /dev/null @@ -1,21 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.gfx; - -import android.graphics.Canvas; - -public interface Overscroll { - // The axis to show overscroll on. - public enum Axis { - X, - Y, - }; - - public void draw(final Canvas canvas, final ImmutableViewportMetrics metrics); - public void setSize(final int width, final int height); - public void setVelocity(final float velocity, final Axis axis); - public void setDistance(final float distance, final Axis axis); -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/OverscrollEdgeEffect.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/OverscrollEdgeEffect.java deleted file mode 100644 index 85e04d9f2..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/OverscrollEdgeEffect.java +++ /dev/null @@ -1,162 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.gfx; - -import org.mozilla.gecko.AppConstants.Versions; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.PointF; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; -import android.widget.EdgeEffect; - -import java.lang.reflect.Field; - -public class OverscrollEdgeEffect implements Overscroll { - // Used to index particular edges in the edges array - private static final int TOP = 0; - private static final int BOTTOM = 1; - private static final int LEFT = 2; - private static final int RIGHT = 3; - - // All four edges of the screen - private final EdgeEffect[] mEdges = new EdgeEffect[4]; - - // The view we're showing this overscroll on. - private final LayerView mView; - - public OverscrollEdgeEffect(final LayerView v) { - Field paintField = null; - if (Versions.feature21Plus) { - try { - paintField = EdgeEffect.class.getDeclaredField("mPaint"); - paintField.setAccessible(true); - } catch (NoSuchFieldException e) { - } - } - - mView = v; - Context context = v.getContext(); - for (int i = 0; i < 4; i++) { - mEdges[i] = new EdgeEffect(context); - - try { - if (paintField != null) { - final Paint p = (Paint) paintField.get(mEdges[i]); - - // The Android EdgeEffect class uses a mode of SRC_ATOP here, which means it will only - // draw the effect where there are non-transparent pixels in the destination. Since the LayerView - // itself is fully transparent, it doesn't display at all. We need to use SRC instead. - p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); - } - } catch (IllegalAccessException e) { - } - } - } - - @Override - public void setSize(final int width, final int height) { - mEdges[LEFT].setSize(height, width); - mEdges[RIGHT].setSize(height, width); - mEdges[TOP].setSize(width, height); - mEdges[BOTTOM].setSize(width, height); - } - - private EdgeEffect getEdgeForAxisAndSide(final Axis axis, final float side) { - if (axis == Axis.Y) { - if (side < 0) { - return mEdges[TOP]; - } else { - return mEdges[BOTTOM]; - } - } else { - if (side < 0) { - return mEdges[LEFT]; - } else { - return mEdges[RIGHT]; - } - } - } - - private void invalidate() { - if (Versions.feature16Plus) { - mView.postInvalidateOnAnimation(); - } else { - mView.postInvalidateDelayed(10); - } - } - - @Override - public void setVelocity(final float velocity, final Axis axis) { - final EdgeEffect edge = getEdgeForAxisAndSide(axis, velocity); - - // If we're showing overscroll already, start fading it out. - if (!edge.isFinished()) { - edge.onRelease(); - } else { - // Otherwise, show an absorb effect - edge.onAbsorb((int)velocity); - } - - invalidate(); - } - - @Override - public void setDistance(final float distance, final Axis axis) { - // The first overscroll event often has zero distance. Throw it out - if (distance == 0.0f) { - return; - } - - final EdgeEffect edge = getEdgeForAxisAndSide(axis, (int)distance); - edge.onPull(distance / (axis == Axis.X ? mView.getWidth() : mView.getHeight())); - invalidate(); - } - - @Override - public void draw(final Canvas canvas, final ImmutableViewportMetrics metrics) { - if (metrics == null) { - return; - } - - PointF visibleEnd = mView.getDynamicToolbarAnimator().getVisibleEndOfLayerView(); - - // If we're pulling an edge, or fading it out, draw! - boolean invalidate = false; - if (!mEdges[TOP].isFinished()) { - invalidate |= draw(mEdges[TOP], canvas, 0, 0, 0); - } - - if (!mEdges[BOTTOM].isFinished()) { - invalidate |= draw(mEdges[BOTTOM], canvas, visibleEnd.x, visibleEnd.y, 180); - } - - if (!mEdges[LEFT].isFinished()) { - invalidate |= draw(mEdges[LEFT], canvas, 0, visibleEnd.y, 270); - } - - if (!mEdges[RIGHT].isFinished()) { - invalidate |= draw(mEdges[RIGHT], canvas, visibleEnd.x, 0, 90); - } - - // If the edge effect is animating off screen, invalidate. - if (invalidate) { - invalidate(); - } - } - - private static boolean draw(final EdgeEffect edge, final Canvas canvas, final float translateX, final float translateY, final float rotation) { - final int state = canvas.save(); - canvas.translate(translateX, translateY); - canvas.rotate(rotation); - boolean invalidate = edge.draw(canvas); - canvas.restoreToCount(state); - - return invalidate; - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/PanZoomController.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/PanZoomController.java deleted file mode 100644 index fbd07c69b..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/PanZoomController.java +++ /dev/null @@ -1,38 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.gfx; - -import org.mozilla.gecko.GeckoAppShell; -import org.mozilla.gecko.EventDispatcher; - -import android.graphics.PointF; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.View; - -public interface PanZoomController { - // Threshold for sending touch move events to content - public static final float CLICK_THRESHOLD = 1 / 50f * GeckoAppShell.getDpi(); - - static class Factory { - static PanZoomController create(PanZoomTarget target, View view, EventDispatcher dispatcher) { - return new NativePanZoomController(target, view); - } - } - - public void destroy(); - public void attach(); - - public boolean onTouchEvent(MotionEvent event); - public boolean onMotionEvent(MotionEvent event); - public void onMotionEventVelocity(final long aEventTime, final float aSpeedY); - - public void setOverscrollHandler(final Overscroll controller); - - public void setIsLongpressEnabled(boolean isLongpressEnabled); - - public ImmutableViewportMetrics adjustScrollForSurfaceShift(ImmutableViewportMetrics aMetrics, PointF aShift); -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/PanZoomTarget.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/PanZoomTarget.java deleted file mode 100644 index 0896674fc..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/PanZoomTarget.java +++ /dev/null @@ -1,15 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.gfx; - -import android.graphics.Matrix; -import android.graphics.PointF; - -public interface PanZoomTarget { - public void panZoomStopped(); - public boolean isGeckoReady(); - public void setScrollingRootContent(boolean isRootContent); -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/PanningPerfAPI.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/PanningPerfAPI.java deleted file mode 100644 index 42eb2b88b..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/PanningPerfAPI.java +++ /dev/null @@ -1,73 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.gfx; - -import org.mozilla.gecko.annotation.RobocopTarget; - -import android.os.SystemClock; -import android.util.Log; - -import java.util.ArrayList; -import java.util.List; - -public class PanningPerfAPI { - private static final String LOGTAG = "GeckoPanningPerfAPI"; - - // make this large enough to avoid having to resize the frame time - // list, as that may be expensive and impact the thing we're trying - // to measure. - private static final int EXPECTED_FRAME_COUNT = 2048; - - private static boolean mRecordingFrames; - private static List<Long> mFrameTimes; - private static long mFrameStartTime; - - private static void initialiseRecordingArrays() { - if (mFrameTimes == null) { - mFrameTimes = new ArrayList<Long>(EXPECTED_FRAME_COUNT); - } else { - mFrameTimes.clear(); - } - } - - @RobocopTarget - public static void startFrameTimeRecording() { - if (mRecordingFrames) { - Log.e(LOGTAG, "Error: startFrameTimeRecording() called while already recording!"); - return; - } - mRecordingFrames = true; - initialiseRecordingArrays(); - mFrameStartTime = SystemClock.uptimeMillis(); - } - - @RobocopTarget - public static List<Long> stopFrameTimeRecording() { - if (!mRecordingFrames) { - Log.e(LOGTAG, "Error: stopFrameTimeRecording() called when not recording!"); - return null; - } - mRecordingFrames = false; - return mFrameTimes; - } - - public static void recordFrameTime() { - // this will be called often, so try to make it as quick as possible - if (mRecordingFrames) { - mFrameTimes.add(SystemClock.uptimeMillis() - mFrameStartTime); - } - } - - @RobocopTarget - public static void startCheckerboardRecording() { - throw new UnsupportedOperationException(); - } - - @RobocopTarget - public static List<Float> stopCheckerboardRecording() { - throw new UnsupportedOperationException(); - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/PointUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/PointUtils.java deleted file mode 100644 index 8db329c9f..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/PointUtils.java +++ /dev/null @@ -1,51 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.gfx; - -import org.json.JSONException; -import org.json.JSONObject; - -import android.graphics.Point; -import android.graphics.PointF; - -public final class PointUtils { - public static PointF add(PointF one, PointF two) { - return new PointF(one.x + two.x, one.y + two.y); - } - - public static PointF subtract(PointF one, PointF two) { - return new PointF(one.x - two.x, one.y - two.y); - } - - public static PointF scale(PointF point, float factor) { - return new PointF(point.x * factor, point.y * factor); - } - - public static Point round(PointF point) { - return new Point(Math.round(point.x), Math.round(point.y)); - } - - /* Computes the magnitude of the given vector. */ - public static float distance(PointF point) { - return (float)Math.sqrt(point.x * point.x + point.y * point.y); - } - - /** Computes the scalar distance between two points. */ - public static float distance(PointF one, PointF two) { - return PointF.length(one.x - two.x, one.y - two.y); - } - - public static JSONObject toJSON(PointF point) throws JSONException { - // Ensure we put ints, not longs, because Gecko message handlers call getInt(). - int x = Math.round(point.x); - int y = Math.round(point.y); - JSONObject json = new JSONObject(); - json.put("x", x); - json.put("y", y); - return json; - } -} - diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/ProgressiveUpdateData.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/ProgressiveUpdateData.java deleted file mode 100644 index d961a2569..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/ProgressiveUpdateData.java +++ /dev/null @@ -1,29 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.gfx; - -import org.mozilla.gecko.annotation.WrapForJNI; - -/** - * This is the data structure that's returned by the progressive tile update - * callback function. It encompasses the current viewport and a boolean value - * representing whether the front-end is interested in the current progressive - * update continuing. - */ -@WrapForJNI -public class ProgressiveUpdateData { - public float x; - public float y; - public float scale; - public boolean abort; - - public void setViewport(ImmutableViewportMetrics viewport) { - this.x = viewport.viewportRectLeft; - this.y = viewport.viewportRectTop; - this.scale = viewport.zoomFactor; - } -} - diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/RectUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/RectUtils.java deleted file mode 100644 index 22151db76..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/RectUtils.java +++ /dev/null @@ -1,126 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.gfx; - -import org.mozilla.gecko.util.FloatUtils; - -import org.json.JSONException; -import org.json.JSONObject; - -import android.graphics.Point; -import android.graphics.PointF; -import android.graphics.Rect; -import android.graphics.RectF; - -public final class RectUtils { - private RectUtils() {} - - public static Rect create(JSONObject json) { - try { - int x = json.getInt("x"); - int y = json.getInt("y"); - int width = json.getInt("width"); - int height = json.getInt("height"); - return new Rect(x, y, x + width, y + height); - } catch (JSONException e) { - throw new RuntimeException(e); - } - } - - public static String toJSON(RectF rect) { - StringBuilder sb = new StringBuilder(256); - sb.append("{ \"left\": ").append(rect.left) - .append(", \"top\": ").append(rect.top) - .append(", \"right\": ").append(rect.right) - .append(", \"bottom\": ").append(rect.bottom) - .append('}'); - return sb.toString(); - } - - public static RectF expand(RectF rect, float moreWidth, float moreHeight) { - float halfMoreWidth = moreWidth / 2; - float halfMoreHeight = moreHeight / 2; - return new RectF(rect.left - halfMoreWidth, - rect.top - halfMoreHeight, - rect.right + halfMoreWidth, - rect.bottom + halfMoreHeight); - } - - public static RectF contract(RectF rect, float lessWidth, float lessHeight) { - float halfLessWidth = lessWidth / 2.0f; - float halfLessHeight = lessHeight / 2.0f; - return new RectF(rect.left + halfLessWidth, - rect.top + halfLessHeight, - rect.right - halfLessWidth, - rect.bottom - halfLessHeight); - } - - public static RectF intersect(RectF one, RectF two) { - float left = Math.max(one.left, two.left); - float top = Math.max(one.top, two.top); - float right = Math.min(one.right, two.right); - float bottom = Math.min(one.bottom, two.bottom); - return new RectF(left, top, Math.max(right, left), Math.max(bottom, top)); - } - - public static RectF scale(RectF rect, float scale) { - float x = rect.left * scale; - float y = rect.top * scale; - return new RectF(x, y, - x + (rect.width() * scale), - y + (rect.height() * scale)); - } - - public static RectF scaleAndRound(RectF rect, float scale) { - float left = rect.left * scale; - float top = rect.top * scale; - return new RectF(Math.round(left), - Math.round(top), - Math.round(left + (rect.width() * scale)), - Math.round(top + (rect.height() * scale))); - } - - /** Returns the nearest integer rect of the given rect. */ - public static Rect round(RectF rect) { - Rect r = new Rect(); - round(rect, r); - return r; - } - - public static void round(RectF rect, Rect dest) { - dest.set(Math.round(rect.left), Math.round(rect.top), - Math.round(rect.right), Math.round(rect.bottom)); - } - - public static Rect roundIn(RectF rect) { - return new Rect((int)Math.ceil(rect.left), (int)Math.ceil(rect.top), - (int)Math.floor(rect.right), (int)Math.floor(rect.bottom)); - } - - public static IntSize getSize(Rect rect) { - return new IntSize(rect.width(), rect.height()); - } - - public static Point getOrigin(Rect rect) { - return new Point(rect.left, rect.top); - } - - public static PointF getOrigin(RectF rect) { - return new PointF(rect.left, rect.top); - } - - public static boolean fuzzyEquals(RectF a, RectF b) { - if (a == null && b == null) - return true; - else if ((a == null && b != null) || (a != null && b == null)) - return false; - else - return FloatUtils.fuzzyEquals(a.top, b.top) - && FloatUtils.fuzzyEquals(a.left, b.left) - && FloatUtils.fuzzyEquals(a.right, b.right) - && FloatUtils.fuzzyEquals(a.bottom, b.bottom); - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/RenderTask.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/RenderTask.java deleted file mode 100644 index 80cbf77f0..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/RenderTask.java +++ /dev/null @@ -1,80 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.gfx; - -/** - * A class used to schedule a callback to occur when the next frame is drawn. - * Subclasses must redefine the internalRun method, not the run method. - */ -public abstract class RenderTask { - /** - * Whether to run the task after the render, or before. - */ - public final boolean runAfter; - - /** - * Time when this task has first run, in ns. Useful for tasks which run for a specific duration. - */ - private long mStartTime; - - /** - * Whether we should initialise mStartTime on the next frame run. - */ - private boolean mResetStartTime = true; - - /** - * The callback to run on each frame. timeDelta is the time elapsed since - * the last call, in nanoseconds. Returns true if it should continue - * running, or false if it should be removed from the task queue. Returning - * true implicitly schedules a redraw. - * - * This method first initializes the start time if resetStartTime has been invoked, - * then calls internalRun. - * - * Note : subclasses should override internalRun. - * - * @param timeDelta the time between the beginning of last frame and the beginning of this frame, in ns. - * @param currentFrameStartTime the startTime of the current frame, in ns. - * @return true if animation should be run at the next frame, false otherwise - * @see RenderTask#internalRun(long, long) - */ - public final boolean run(long timeDelta, long currentFrameStartTime) { - if (mResetStartTime) { - mStartTime = currentFrameStartTime; - mResetStartTime = false; - } - return internalRun(timeDelta, currentFrameStartTime); - } - - /** - * Abstract method to be overridden by subclasses. - * @param timeDelta the time between the beginning of last frame and the beginning of this frame, in ns - * @param currentFrameStartTime the startTime of the current frame, in ns. - * @return true if animation should be run at the next frame, false otherwise - */ - protected abstract boolean internalRun(long timeDelta, long currentFrameStartTime); - - public RenderTask(boolean aRunAfter) { - runAfter = aRunAfter; - } - - /** - * Get the start time of this task. - * It is the start time of the first frame this task was run on. - * @return the start time in ns - */ - public long getStartTime() { - return mStartTime; - } - - /** - * Schedule a reset of the recorded start time next time {@link RenderTask#run(long, long)} is run. - * @see RenderTask#getStartTime() - */ - public void resetStartTime() { - mResetStartTime = true; - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/StackScroller.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/StackScroller.java deleted file mode 100644 index 293268cba..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/StackScroller.java +++ /dev/null @@ -1,695 +0,0 @@ -// Copyright 2015 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package org.mozilla.gecko.gfx; - -import android.content.Context; -import android.hardware.SensorManager; -import android.util.Log; -import android.view.ViewConfiguration; - -import org.mozilla.gecko.annotation.WrapForJNI; - -/** - * This class is vastly copied from {@link android.widget.OverScroller} but decouples the time - * from the app time so it can be specified manually. - */ -@WrapForJNI(exceptionMode = "nsresult") -public class StackScroller { - private int mMode; - - private final SplineStackScroller mScrollerX; - private final SplineStackScroller mScrollerY; - - private final boolean mFlywheel; - - private static final int SCROLL_MODE = 0; - private static final int FLING_MODE = 1; - - private static float sViscousFluidScale; - private static float sViscousFluidNormalize; - - /** - * Creates an StackScroller with a viscous fluid scroll interpolator and flywheel. - * @param context - */ - public StackScroller(Context context) { - mFlywheel = true; - mScrollerX = new SplineStackScroller(context); - mScrollerY = new SplineStackScroller(context); - initContants(); - } - - private static void initContants() { - // This controls the viscous fluid effect (how much of it) - sViscousFluidScale = 8.0f; - // must be set to 1.0 (used in viscousFluid()) - sViscousFluidNormalize = 1.0f; - sViscousFluidNormalize = 1.0f / viscousFluid(1.0f); - } - - /** - * - * Returns whether the scroller has finished scrolling. - * - * @return True if the scroller has finished scrolling, false otherwise. - */ - public final boolean isFinished() { - return mScrollerX.mFinished && mScrollerY.mFinished; - } - - /** - * Force the finished field to a particular value. Contrary to - * {@link #abortAnimation()}, forcing the animation to finished - * does NOT cause the scroller to move to the final x and y - * position. - * - * @param finished The new finished value. - */ - public final void forceFinished(boolean finished) { - mScrollerX.mFinished = mScrollerY.mFinished = finished; - } - - /** - * Returns the current X offset in the scroll. - * - * @return The new X offset as an absolute distance from the origin. - */ - public final int getCurrX() { - return mScrollerX.mCurrentPosition; - } - - /** - * Returns the current Y offset in the scroll. - * - * @return The new Y offset as an absolute distance from the origin. - */ - public final int getCurrY() { - return mScrollerY.mCurrentPosition; - } - - /** - * Returns where the scroll will end. Valid only for "fling" scrolls. - * - * @return The final X offset as an absolute distance from the origin. - */ - public final int getFinalX() { - return mScrollerX.mFinal; - } - - public final float getCurrSpeedX() { - return mScrollerX.mCurrVelocity; - } - - public final float getCurrSpeedY() { - return mScrollerY.mCurrVelocity; - } - - /** - * Returns where the scroll will end. Valid only for "fling" scrolls. - * - * @return The final Y offset as an absolute distance from the origin. - */ - public final int getFinalY() { - return mScrollerY.mFinal; - } - - /** - * Sets where the scroll will end. Valid only for "fling" scrolls. - * - * @param x The final X offset as an absolute distance from the origin. - */ - public final void setFinalX(int x) { - mScrollerX.setFinalPosition(x); - } - - private static float viscousFluid(float x) { - x *= sViscousFluidScale; - if (x < 1.0f) { - x -= (1.0f - (float) Math.exp(-x)); - } else { - float start = 0.36787944117f; // 1/e == exp(-1) - x = 1.0f - (float) Math.exp(1.0f - x); - x = start + x * (1.0f - start); - } - x *= sViscousFluidNormalize; - return x; - } - - /** - * Call this when you want to know the new location. If it returns true, the - * animation is not yet finished. - */ - public boolean computeScrollOffset(long time) { - if (isFinished()) { - return false; - } - - switch (mMode) { - case SCROLL_MODE: - // Any scroller can be used for time, since they were started - // together in scroll mode. We use X here. - final long elapsedTime = time - mScrollerX.mStartTime; - - final int duration = mScrollerX.mDuration; - if (elapsedTime < duration) { - float q = (float) (elapsedTime) / duration; - q = viscousFluid(q); - mScrollerX.updateScroll(q); - mScrollerY.updateScroll(q); - } else { - abortAnimation(); - } - break; - - case FLING_MODE: - if (!mScrollerX.mFinished) { - if (!mScrollerX.update(time)) { - if (!mScrollerX.continueWhenFinished(time)) { - mScrollerX.finish(); - } - } - } - - if (!mScrollerY.mFinished) { - if (!mScrollerY.update(time)) { - if (!mScrollerY.continueWhenFinished(time)) { - mScrollerY.finish(); - } - } - } - - break; - - default: - break; - } - - return true; - } - - /** - * Start scrolling by providing a starting point and the distance to travel. - * - * @param startX Starting horizontal scroll offset in pixels. Positive - * numbers will scroll the content to the left. - * @param startY Starting vertical scroll offset in pixels. Positive numbers - * will scroll the content up. - * @param dx Horizontal distance to travel. Positive numbers will scroll the - * content to the left. - * @param dy Vertical distance to travel. Positive numbers will scroll the - * content up. - * @param duration Duration of the scroll in milliseconds. - */ - public void startScroll(int startX, int startY, int dx, int dy, long startTime, int duration) { - mMode = SCROLL_MODE; - mScrollerX.startScroll(startX, dx, startTime, duration); - mScrollerY.startScroll(startY, dy, startTime, duration); - } - - /** - * Call this when you want to 'spring back' into a valid coordinate range. - * - * @param startX Starting X coordinate - * @param startY Starting Y coordinate - * @param minX Minimum valid X value - * @param maxX Maximum valid X value - * @param minY Minimum valid Y value - * @param maxY Minimum valid Y value - * @return true if a springback was initiated, false if startX and startY were - * already within the valid range. - */ - public boolean springBack( - int startX, int startY, int minX, int maxX, int minY, int maxY, long time) { - mMode = FLING_MODE; - - // Make sure both methods are called. - final boolean spingbackX = mScrollerX.springback(startX, minX, maxX, time); - final boolean spingbackY = mScrollerY.springback(startY, minY, maxY, time); - return spingbackX || spingbackY; - } - - /** - * Start scrolling based on a fling gesture. The distance traveled will - * depend on the initial velocity of the fling. - * - * @param startX Starting point of the scroll (X) - * @param startY Starting point of the scroll (Y) - * @param velocityX Initial velocity of the fling (X) measured in pixels per second. - * @param velocityY Initial velocity of the fling (Y) measured in pixels per second - * @param minX Minimum X value. The scroller will not scroll past this point - * unless overX > 0. If overfling is allowed, it will use minX as - * a springback boundary. - * @param maxX Maximum X value. The scroller will not scroll past this point - * unless overX > 0. If overfling is allowed, it will use maxX as - * a springback boundary. - * @param minY Minimum Y value. The scroller will not scroll past this point - * unless overY > 0. If overfling is allowed, it will use minY as - * a springback boundary. - * @param maxY Maximum Y value. The scroller will not scroll past this point - * unless overY > 0. If overfling is allowed, it will use maxY as - * a springback boundary. - * @param overX Overfling range. If > 0, horizontal overfling in either - * direction will be possible. - * @param overY Overfling range. If > 0, vertical overfling in either - * direction will be possible. - */ - public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, - int minY, int maxY, int overX, int overY, long time) { - // Continue a scroll or fling in progress - if (mFlywheel && !isFinished()) { - float oldVelocityX = mScrollerX.mCurrVelocity; - float oldVelocityY = mScrollerY.mCurrVelocity; - boolean sameXDirection = (velocityX == 0) || (oldVelocityX == 0) || - ((velocityX < 0) == (oldVelocityX < 0)); - boolean sameYDirection = (velocityY == 0) || (oldVelocityY == 0) || - ((velocityY < 0) == (oldVelocityY < 0)); - if (sameXDirection) { - velocityX += oldVelocityX; - } - if (sameYDirection) { - velocityY += oldVelocityY; - } - } - - mMode = FLING_MODE; - mScrollerX.fling(startX, velocityX, minX, maxX, overX, time); - mScrollerY.fling(startY, velocityY, minY, maxY, overY, time); - } - - /** - * Stops the animation. Contrary to {@link #forceFinished(boolean)}, - * aborting the animating causes the scroller to move to the final x and y - * positions. - * - * @see #forceFinished(boolean) - */ - public void abortAnimation() { - mScrollerX.finish(); - mScrollerY.finish(); - } - - static class SplineStackScroller { - // Initial position - private int mStart; - - // Current position - private int mCurrentPosition; - - // Final position - private int mFinal; - - // Initial velocity - private int mVelocity; - - // Current velocity - private float mCurrVelocity; - - // Constant current deceleration - private float mDeceleration; - - // Animation starting time, in system milliseconds - private long mStartTime; - - // Animation duration, in milliseconds - private int mDuration; - - // Duration to complete spline component of animation - private int mSplineDuration; - - // Distance to travel along spline animation - private int mSplineDistance; - - // Whether the animation is currently in progress - private boolean mFinished; - - // The allowed overshot distance before boundary is reached. - private int mOver; - - // Fling friction - private final float mFlingFriction = ViewConfiguration.getScrollFriction(); - - // Current state of the animation. - private int mState = SPLINE; - - // Constant gravity value, used in the deceleration phase. - private static final float GRAVITY = 2000.0f; - - // A context-specific coefficient adjusted to physical values. - private final float mPhysicalCoeff; - - private static final float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9)); - private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1) - private static final float START_TENSION = 0.5f; - private static final float END_TENSION = 1.0f; - private static final float P1 = START_TENSION * INFLEXION; - private static final float P2 = 1.0f - END_TENSION * (1.0f - INFLEXION); - - private static final int NB_SAMPLES = 100; - private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1]; - private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1]; - - private static final int SPLINE = 0; - private static final int CUBIC = 1; - private static final int BALLISTIC = 2; - - static { - float xMin = 0.0f; - float yMin = 0.0f; - for (int i = 0; i < NB_SAMPLES; i++) { - final float alpha = (float) i / NB_SAMPLES; - - float xMax = 1.0f; - float x, tx, coef; - while (true) { - x = xMin + (xMax - xMin) / 2.0f; - coef = 3.0f * x * (1.0f - x); - tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x; - if (Math.abs(tx - alpha) < 1E-5) break; - if (tx > alpha) { - xMax = x; - } else { - xMin = x; - } - } - SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x; - - float yMax = 1.0f; - float y, dy; - while (true) { - y = yMin + (yMax - yMin) / 2.0f; - coef = 3.0f * y * (1.0f - y); - dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y; - if (Math.abs(dy - alpha) < 1E-5) break; - if (dy > alpha) { - yMax = y; - } else { - yMin = y; - } - } - SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y; - } - SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f; - } - - SplineStackScroller(Context context) { - mFinished = true; - final float ppi = context.getResources().getDisplayMetrics().density * 160.0f; - mPhysicalCoeff = SensorManager.GRAVITY_EARTH // g (m/s^2) - * 39.37f // inch/meter - * ppi * 0.84f; // look and feel tuning - } - - void updateScroll(float q) { - mCurrentPosition = mStart + Math.round(q * (mFinal - mStart)); - } - - /* - * Get a signed deceleration that will reduce the velocity. - */ - private static float getDeceleration(int velocity) { - return velocity > 0 ? -GRAVITY : GRAVITY; - } - - /* - * Modifies mDuration to the duration it takes to get from start to newFinal using the - * spline interpolation. The previous duration was needed to get to oldFinal. - */ - private void adjustDuration(int start, int oldFinal, int newFinal) { - final int oldDistance = oldFinal - start; - final int newDistance = newFinal - start; - final float x = Math.abs((float) newDistance / oldDistance); - final int index = (int) (NB_SAMPLES * x); - if (index < NB_SAMPLES) { - final float xInf = (float) index / NB_SAMPLES; - final float xSup = (float) (index + 1) / NB_SAMPLES; - final float tInf = SPLINE_TIME[index]; - final float tSup = SPLINE_TIME[index + 1]; - final float timeCoef = tInf + (x - xInf) / (xSup - xInf) * (tSup - tInf); - mDuration *= timeCoef; - } - } - - void startScroll(int start, int distance, long startTime, int duration) { - mFinished = false; - - mStart = start; - mFinal = start + distance; - - mStartTime = startTime; - mDuration = duration; - - // Unused - mDeceleration = 0.0f; - mVelocity = 0; - } - - void finish() { - mCurrentPosition = mFinal; - // Not reset since WebView relies on this value for fast fling. - // TODO: restore when WebView uses the fast fling implemented in this class. - // mCurrVelocity = 0.0f; - mFinished = true; - } - - void setFinalPosition(int position) { - mFinal = position; - mFinished = false; - } - - boolean springback(int start, int min, int max, long time) { - mFinished = true; - - mStart = mFinal = start; - mVelocity = 0; - - mStartTime = time; - mDuration = 0; - - if (start < min) { - startSpringback(start, min, 0); - } else if (start > max) { - startSpringback(start, max, 0); - } - - return !mFinished; - } - - private void startSpringback(int start, int end, int velocity) { - // mStartTime has been set - mFinished = false; - mState = CUBIC; - mStart = start; - mFinal = end; - final int delta = start - end; - mDeceleration = getDeceleration(delta); - // TODO take velocity into account - mVelocity = -delta; // only sign is used - mOver = Math.abs(delta); - mDuration = (int) (1000.0 * Math.sqrt(-2.0 * delta / mDeceleration)); - } - - void fling(int start, int velocity, int min, int max, int over, long time) { - mOver = over; - mFinished = false; - mCurrVelocity = mVelocity = velocity; - mDuration = mSplineDuration = 0; - mStartTime = time; - mCurrentPosition = mStart = start; - - if (start > max || start < min) { - startAfterEdge(start, min, max, velocity, time); - return; - } - - mState = SPLINE; - double totalDistance = 0.0; - - if (velocity != 0) { - mDuration = mSplineDuration = getSplineFlingDuration(velocity); - totalDistance = getSplineFlingDistance(velocity); - } - - mSplineDistance = (int) (totalDistance * Math.signum(velocity)); - mFinal = start + mSplineDistance; - - // Clamp to a valid final position - if (mFinal < min) { - adjustDuration(mStart, mFinal, min); - mFinal = min; - } - - if (mFinal > max) { - adjustDuration(mStart, mFinal, max); - mFinal = max; - } - } - - private double getSplineDeceleration(int velocity) { - return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * mPhysicalCoeff)); - } - - private double getSplineFlingDistance(int velocity) { - final double l = getSplineDeceleration(velocity); - final double decelMinusOne = DECELERATION_RATE - 1.0; - return mFlingFriction * mPhysicalCoeff - * Math.exp(DECELERATION_RATE / decelMinusOne * l); - } - - /* Returns the duration, expressed in milliseconds */ - private int getSplineFlingDuration(int velocity) { - final double l = getSplineDeceleration(velocity); - final double decelMinusOne = DECELERATION_RATE - 1.0; - return (int) (1000.0 * Math.exp(l / decelMinusOne)); - } - - private void fitOnBounceCurve(int start, int end, int velocity) { - // Simulate a bounce that started from edge - final float durationToApex = -velocity / mDeceleration; - final float distanceToApex = velocity * velocity / 2.0f / Math.abs(mDeceleration); - final float distanceToEdge = Math.abs(end - start); - final float totalDuration = (float) Math.sqrt( - 2.0 * (distanceToApex + distanceToEdge) / Math.abs(mDeceleration)); - mStartTime -= (int) (1000.0f * (totalDuration - durationToApex)); - mStart = end; - mVelocity = (int) (-mDeceleration * totalDuration); - } - - private void startBounceAfterEdge(int start, int end, int velocity) { - mDeceleration = getDeceleration(velocity == 0 ? start - end : velocity); - fitOnBounceCurve(start, end, velocity); - onEdgeReached(); - } - - private void startAfterEdge(int start, int min, int max, int velocity, long time) { - if (start > min && start < max) { - Log.e("StackScroller", "startAfterEdge called from a valid position"); - mFinished = true; - return; - } - final boolean positive = start > max; - final int edge = positive ? max : min; - final int overDistance = start - edge; - boolean keepIncreasing = overDistance * velocity >= 0; - if (keepIncreasing) { - // Will result in a bounce or a to_boundary depending on velocity. - startBounceAfterEdge(start, edge, velocity); - } else { - final double totalDistance = getSplineFlingDistance(velocity); - if (totalDistance > Math.abs(overDistance)) { - fling(start, velocity, positive ? min : start, positive ? start : max, mOver, - time); - } else { - startSpringback(start, edge, velocity); - } - } - } - - private void onEdgeReached() { - // mStart, mVelocity and mStartTime were adjusted to their values when edge was reached. - float distance = mVelocity * mVelocity / (2.0f * Math.abs(mDeceleration)); - final float sign = Math.signum(mVelocity); - - if (distance > mOver) { - // Default deceleration is not sufficient to slow us down before boundary - mDeceleration = -sign * mVelocity * mVelocity / (2.0f * mOver); - distance = mOver; - } - - mOver = (int) distance; - mState = BALLISTIC; - mFinal = mStart + (int) (mVelocity > 0 ? distance : -distance); - mDuration = -(int) (1000.0f * mVelocity / mDeceleration); - } - - boolean continueWhenFinished(long time) { - switch (mState) { - case SPLINE: - // Duration from start to null velocity - if (mDuration < mSplineDuration) { - // If the animation was clamped, we reached the edge - mStart = mFinal; - // TODO Better compute speed when edge was reached - mVelocity = (int) mCurrVelocity; - mDeceleration = getDeceleration(mVelocity); - mStartTime += mDuration; - onEdgeReached(); - } else { - // Normal stop, no need to continue - return false; - } - break; - case BALLISTIC: - mStartTime += mDuration; - startSpringback(mFinal, mStart, 0); - break; - case CUBIC: - return false; - } - - update(time); - return true; - } - - /* - * Update the current position and velocity for current time. Returns - * true if update has been done and false if animation duration has been - * reached. - */ - boolean update(long time) { - final long currentTime = time - mStartTime; - - if (((mState == SPLINE) && (mSplineDuration <= 0)) || - ((mState == CUBIC) && (mDuration <= 0))) { - return false; - } - - if (currentTime > mDuration) { - return false; - } - - double distance = 0.0; - switch (mState) { - case SPLINE: { - final float t = (float) currentTime / mSplineDuration; - final int index = (int) (NB_SAMPLES * t); - float distanceCoef = 1.f; - float velocityCoef = 0.f; - if (index < NB_SAMPLES) { - final float tInf = (float) index / NB_SAMPLES; - final float tSup = (float) (index + 1) / NB_SAMPLES; - final float dInf = SPLINE_POSITION[index]; - final float dSup = SPLINE_POSITION[index + 1]; - velocityCoef = (dSup - dInf) / (tSup - tInf); - distanceCoef = dInf + (t - tInf) * velocityCoef; - } - - distance = distanceCoef * mSplineDistance; - mCurrVelocity = velocityCoef * mSplineDistance / mSplineDuration * 1000.0f; - break; - } - - case BALLISTIC: { - final float t = currentTime / 1000.0f; - mCurrVelocity = mVelocity + mDeceleration * t; - distance = mVelocity * t + mDeceleration * t * t / 2.0f; - break; - } - - case CUBIC: { - final float t = (float) (currentTime) / mDuration; - final float t2 = t * t; - final float sign = Math.signum(mVelocity); - distance = sign * mOver * (3.0f * t2 - 2.0f * t * t2); - mCurrVelocity = sign * mOver * 6.0f * (-t + t2); - break; - } - } - - mCurrentPosition = mStart + (int) Math.round(distance); - - return true; - } - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceTextureListener.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceTextureListener.java deleted file mode 100644 index 560674e4f..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceTextureListener.java +++ /dev/null @@ -1,38 +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; - -import org.mozilla.gecko.annotation.WrapForJNI; -import org.mozilla.gecko.mozglue.JNIObject; - -import android.graphics.SurfaceTexture; - -final class SurfaceTextureListener - extends JNIObject implements SurfaceTexture.OnFrameAvailableListener -{ - @WrapForJNI(calledFrom = "gecko") - private SurfaceTextureListener() { - } - - @Override - protected void disposeNative() { - // SurfaceTextureListener is disposed inside AndroidSurfaceTexture. - throw new IllegalStateException("unreachable code"); - } - - @WrapForJNI(stubName = "OnFrameAvailable") - private native void nativeOnFrameAvailable(); - - @Override // SurfaceTexture.OnFrameAvailableListener - public void onFrameAvailable(SurfaceTexture surfaceTexture) { - try { - nativeOnFrameAvailable(); - } catch (final NullPointerException e) { - // Ignore exceptions caused by a disposed object, i.e. - // getting a callback after this listener is no longer in use. - } - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/ViewTransform.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/ViewTransform.java deleted file mode 100644 index e6685f066..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/ViewTransform.java +++ /dev/null @@ -1,28 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.gfx; - -import org.mozilla.gecko.annotation.WrapForJNI; - -@WrapForJNI -public class ViewTransform { - public float x; - public float y; - public float width; - public float height; - public float scale; - public float fixedLayerMarginLeft; - public float fixedLayerMarginTop; - public float fixedLayerMarginRight; - public float fixedLayerMarginBottom; - - public ViewTransform(float inX, float inY, float inScale) { - x = inX; - y = inY; - scale = inScale; - } -} - diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/ByteBufferInputStream.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/ByteBufferInputStream.java deleted file mode 100644 index bc9e0a143..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/ByteBufferInputStream.java +++ /dev/null @@ -1,64 +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.mozglue; - -import java.io.InputStream; -import java.nio.ByteBuffer; - -class ByteBufferInputStream extends InputStream { - - protected ByteBuffer mBuf; - // Reference to a native object holding the data backing the ByteBuffer. - private final NativeReference mNativeRef; - - protected ByteBufferInputStream(ByteBuffer buffer, NativeReference ref) { - mBuf = buffer; - mNativeRef = ref; - } - - @Override - public int available() { - return mBuf.remaining(); - } - - @Override - public void close() { - // Do nothing, we need to keep the native references around for child - // buffers. - } - - @Override - public int read() { - if (!mBuf.hasRemaining() || mNativeRef.isReleased()) { - return -1; - } - - return mBuf.get() & 0xff; // Avoid sign extension - } - - @Override - public int read(byte[] buffer, int offset, int length) { - if (!mBuf.hasRemaining() || mNativeRef.isReleased()) { - return -1; - } - - length = Math.min(length, mBuf.remaining()); - mBuf.get(buffer, offset, length); - return length; - } - - @Override - public long skip(long byteCount) { - if (byteCount < 0 || mNativeRef.isReleased()) { - return 0; - } - - byteCount = Math.min(byteCount, mBuf.remaining()); - mBuf.position(mBuf.position() + (int)byteCount); - return byteCount; - } - -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/DirectBufferAllocator.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/DirectBufferAllocator.java deleted file mode 100644 index b3fb24291..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/DirectBufferAllocator.java +++ /dev/null @@ -1,52 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.mozglue; - -import java.nio.ByteBuffer; - -// -// We must manually allocate direct buffers in JNI to work around a bug where Honeycomb's -// ByteBuffer.allocateDirect() grossly overallocates the direct buffer size. -// https://code.google.com/p/android/issues/detail?id=16941 -// - -public final class DirectBufferAllocator { - private DirectBufferAllocator() {} - - public static ByteBuffer allocate(int size) { - if (size <= 0) { - throw new IllegalArgumentException("Invalid size " + size); - } - - ByteBuffer directBuffer = nativeAllocateDirectBuffer(size); - if (directBuffer == null) { - throw new OutOfMemoryError("allocateDirectBuffer() returned null"); - } - - if (!directBuffer.isDirect()) { - throw new AssertionError("allocateDirectBuffer() did not return a direct buffer"); - } - - return directBuffer; - } - - public static ByteBuffer free(ByteBuffer buffer) { - if (buffer == null) { - return null; - } - - if (!buffer.isDirect()) { - throw new IllegalArgumentException("buffer must be direct"); - } - - nativeFreeDirectBuffer(buffer); - return null; - } - - // These JNI methods are implemented in mozglue/android/nsGeckoUtils.cpp. - private static native ByteBuffer nativeAllocateDirectBuffer(long size); - private static native void nativeFreeDirectBuffer(ByteBuffer buf); -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java deleted file mode 100644 index 0bef2435b..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java +++ /dev/null @@ -1,549 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.mozglue; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; -import java.text.NumberFormat; -import java.util.Locale; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -import android.content.Context; -import android.os.Build; -import android.os.Environment; -import android.util.Log; - -import org.mozilla.gecko.annotation.JNITarget; -import org.mozilla.gecko.annotation.RobocopTarget; -import org.mozilla.gecko.AppConstants; - -public final class GeckoLoader { - private static final String LOGTAG = "GeckoLoader"; - - private static volatile SafeIntent sIntent; - private static File sCacheFile; - private static File sGREDir; - - /* Synchronized on GeckoLoader.class. */ - private static boolean sSQLiteLibsLoaded; - private static boolean sNSSLibsLoaded; - private static boolean sMozGlueLoaded; - - private GeckoLoader() { - // prevent instantiation - } - - public static File getCacheDir(Context context) { - if (sCacheFile == null) { - sCacheFile = context.getCacheDir(); - } - return sCacheFile; - } - - public static File getGREDir(Context context) { - if (sGREDir == null) { - sGREDir = new File(context.getApplicationInfo().dataDir); - } - return sGREDir; - } - - private static void setupPluginEnvironment(Context context, String[] pluginDirs) { - // setup plugin path directories - try { - // Check to see if plugins were blocked. - if (pluginDirs == null) { - putenv("MOZ_PLUGINS_BLOCKED=1"); - putenv("MOZ_PLUGIN_PATH="); - return; - } - - StringBuilder pluginSearchPath = new StringBuilder(); - for (int i = 0; i < pluginDirs.length; i++) { - pluginSearchPath.append(pluginDirs[i]); - pluginSearchPath.append(":"); - } - putenv("MOZ_PLUGIN_PATH=" + pluginSearchPath); - - File pluginDataDir = context.getDir("plugins", 0); - putenv("ANDROID_PLUGIN_DATADIR=" + pluginDataDir.getPath()); - - File pluginPrivateDataDir = context.getDir("plugins_private", 0); - putenv("ANDROID_PLUGIN_DATADIR_PRIVATE=" + pluginPrivateDataDir.getPath()); - - } catch (Exception ex) { - Log.w(LOGTAG, "Caught exception getting plugin dirs.", ex); - } - } - - private static void setupDownloadEnvironment(final Context context) { - try { - File downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); - File updatesDir = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS); - if (downloadDir == null) { - downloadDir = new File(Environment.getExternalStorageDirectory().getPath(), "download"); - } - if (updatesDir == null) { - updatesDir = downloadDir; - } - putenv("DOWNLOADS_DIRECTORY=" + downloadDir.getPath()); - putenv("UPDATES_DIRECTORY=" + updatesDir.getPath()); - } catch (Exception e) { - Log.w(LOGTAG, "No download directory found.", e); - } - } - - private static void delTree(File file) { - if (file.isDirectory()) { - File children[] = file.listFiles(); - for (File child : children) { - delTree(child); - } - } - file.delete(); - } - - private static File getTmpDir(Context context) { - File tmpDir = context.getDir("tmpdir", Context.MODE_PRIVATE); - // check if the old tmp dir is there - File oldDir = new File(tmpDir.getParentFile(), "app_tmp"); - if (oldDir.exists()) { - delTree(oldDir); - } - return tmpDir; - } - - public static void setLastIntent(SafeIntent intent) { - sIntent = intent; - } - - public static void setupGeckoEnvironment(Context context, String[] pluginDirs, String profilePath) { - // if we have an intent (we're being launched by an activity) - // read in any environmental variables from it here - final SafeIntent intent = sIntent; - if (intent != null) { - String env = intent.getStringExtra("env0"); - Log.d(LOGTAG, "Gecko environment env0: " + env); - for (int c = 1; env != null; c++) { - putenv(env); - env = intent.getStringExtra("env" + c); - Log.d(LOGTAG, "env" + c + ": " + env); - } - } - - putenv("MOZ_ANDROID_PACKAGE_NAME=" + context.getPackageName()); - - setupPluginEnvironment(context, pluginDirs); - setupDownloadEnvironment(context); - - // profile home path - putenv("HOME=" + profilePath); - - // setup the tmp path - File f = getTmpDir(context); - if (!f.exists()) { - f.mkdirs(); - } - putenv("TMPDIR=" + f.getPath()); - - // setup the downloads path - f = Environment.getDownloadCacheDirectory(); - putenv("EXTERNAL_STORAGE=" + f.getPath()); - - // setup the app-specific cache path - f = context.getCacheDir(); - putenv("CACHE_DIRECTORY=" + f.getPath()); - - if (AppConstants.Versions.feature17Plus) { - android.os.UserManager um = (android.os.UserManager)context.getSystemService(Context.USER_SERVICE); - if (um != null) { - putenv("MOZ_ANDROID_USER_SERIAL_NUMBER=" + um.getSerialNumberForUser(android.os.Process.myUserHandle())); - } else { - Log.d(LOGTAG, "Unable to obtain user manager service on a device with SDK version " + Build.VERSION.SDK_INT); - } - } - setupLocaleEnvironment(); - - // We don't need this any more. - sIntent = null; - } - - private static void loadLibsSetupLocked(Context context) { - // The package data lib directory isn't placed in ld.so's - // search path, so we have to manually load libraries that - // libxul will depend on. Not ideal. - - File cacheFile = getCacheDir(context); - putenv("GRE_HOME=" + getGREDir(context).getPath()); - - // setup the libs cache - String linkerCache = System.getenv("MOZ_LINKER_CACHE"); - if (linkerCache == null) { - linkerCache = cacheFile.getPath(); - putenv("MOZ_LINKER_CACHE=" + linkerCache); - } - - // Disable on-demand decompression of the linker on devices where it - // is known to cause crashes. - String forced_ondemand = System.getenv("MOZ_LINKER_ONDEMAND"); - if (forced_ondemand == null) { - if ("HTC".equals(android.os.Build.MANUFACTURER) && - "HTC Vision".equals(android.os.Build.MODEL)) { - putenv("MOZ_LINKER_ONDEMAND=0"); - } - } - - putenv("MOZ_LINKER_EXTRACT=1"); - } - - @RobocopTarget - public synchronized static void loadSQLiteLibs(final Context context, final String apkName) { - if (sSQLiteLibsLoaded) { - return; - } - - loadMozGlue(context); - loadLibsSetupLocked(context); - loadSQLiteLibsNative(apkName); - sSQLiteLibsLoaded = true; - } - - public synchronized static void loadNSSLibs(final Context context, final String apkName) { - if (sNSSLibsLoaded) { - return; - } - - loadMozGlue(context); - loadLibsSetupLocked(context); - loadNSSLibsNative(apkName); - sNSSLibsLoaded = true; - } - - @SuppressWarnings("deprecation") - private static final String getCPUABI() { - return android.os.Build.CPU_ABI; - } - - /** - * Copy a library out of our APK. - * - * @param context a Context. - * @param lib the name of the library; e.g., "mozglue". - * @param outDir the output directory for the .so. No trailing slash. - * @return true on success, false on failure. - */ - private static boolean extractLibrary(final Context context, final String lib, final String outDir) { - final String apkPath = context.getApplicationInfo().sourceDir; - - // Sanity check. - if (!apkPath.endsWith(".apk")) { - Log.w(LOGTAG, "sourceDir is not an APK."); - return false; - } - - // Try to extract the named library from the APK. - File outDirFile = new File(outDir); - if (!outDirFile.isDirectory()) { - if (!outDirFile.mkdirs()) { - Log.e(LOGTAG, "Couldn't create " + outDir); - return false; - } - } - - if (AppConstants.Versions.feature21Plus) { - String[] abis = Build.SUPPORTED_ABIS; - for (String abi : abis) { - if (tryLoadWithABI(lib, outDir, apkPath, abi)) { - return true; - } - } - return false; - } else { - final String abi = getCPUABI(); - return tryLoadWithABI(lib, outDir, apkPath, abi); - } - } - - private static boolean tryLoadWithABI(String lib, String outDir, String apkPath, String abi) { - try { - final ZipFile zipFile = new ZipFile(new File(apkPath)); - try { - final String libPath = "lib/" + abi + "/lib" + lib + ".so"; - final ZipEntry entry = zipFile.getEntry(libPath); - if (entry == null) { - Log.w(LOGTAG, libPath + " not found in APK " + apkPath); - return false; - } - - final InputStream in = zipFile.getInputStream(entry); - try { - final String outPath = outDir + "/lib" + lib + ".so"; - final FileOutputStream out = new FileOutputStream(outPath); - final byte[] bytes = new byte[1024]; - int read; - - Log.d(LOGTAG, "Copying " + libPath + " to " + outPath); - boolean failed = false; - try { - while ((read = in.read(bytes, 0, 1024)) != -1) { - out.write(bytes, 0, read); - } - } catch (Exception e) { - Log.w(LOGTAG, "Failing library copy.", e); - failed = true; - } finally { - out.close(); - } - - if (failed) { - // Delete the partial copy so we don't fail to load it. - // Don't bother to check the return value -- there's nothing - // we can do about a failure. - new File(outPath).delete(); - } else { - // Mark the file as executable. This doesn't seem to be - // necessary for the loader, but it's the normal state of - // affairs. - Log.d(LOGTAG, "Marking " + outPath + " as executable."); - new File(outPath).setExecutable(true); - } - - return !failed; - } finally { - in.close(); - } - } finally { - zipFile.close(); - } - } catch (Exception e) { - Log.e(LOGTAG, "Failed to extract lib from APK.", e); - return false; - } - } - - private static String getLoadDiagnostics(final Context context, final String lib) { - final String androidPackageName = context.getPackageName(); - - final StringBuilder message = new StringBuilder("LOAD "); - message.append(lib); - - // These might differ. If so, we know why the library won't load! - message.append(": ABI: " + AppConstants.MOZ_APP_ABI + ", " + getCPUABI()); - message.append(": Data: " + context.getApplicationInfo().dataDir); - try { - final boolean appLibExists = new File("/data/app-lib/" + androidPackageName + "/lib" + lib + ".so").exists(); - final boolean dataDataExists = new File("/data/data/" + androidPackageName + "/lib/lib" + lib + ".so").exists(); - message.append(", ax=" + appLibExists); - message.append(", ddx=" + dataDataExists); - } catch (Throwable e) { - message.append(": ax/ddx fail, "); - } - - try { - final String dashOne = "/data/data/" + androidPackageName + "-1"; - final String dashTwo = "/data/data/" + androidPackageName + "-2"; - final boolean dashOneExists = new File(dashOne).exists(); - final boolean dashTwoExists = new File(dashTwo).exists(); - message.append(", -1x=" + dashOneExists); - message.append(", -2x=" + dashTwoExists); - } catch (Throwable e) { - message.append(", dash fail, "); - } - - try { - if (Build.VERSION.SDK_INT >= 9) { - final String nativeLibPath = context.getApplicationInfo().nativeLibraryDir; - final boolean nativeLibDirExists = new File(nativeLibPath).exists(); - final boolean nativeLibLibExists = new File(nativeLibPath + "/lib" + lib + ".so").exists(); - - message.append(", nativeLib: " + nativeLibPath); - message.append(", dirx=" + nativeLibDirExists); - message.append(", libx=" + nativeLibLibExists); - } else { - message.append(", <pre-9>"); - } - } catch (Throwable e) { - message.append(", nativeLib fail."); - } - - return message.toString(); - } - - private static final boolean attemptLoad(final String path) { - try { - System.load(path); - return true; - } catch (Throwable e) { - Log.wtf(LOGTAG, "Couldn't load " + path + ": " + e); - } - - return false; - } - - /** - * The first two attempts at loading a library: directly, and - * then using the app library path. - * - * Returns null or the cause exception. - */ - private static final Throwable doLoadLibraryExpected(final Context context, final String lib) { - try { - // Attempt 1: the way that should work. - System.loadLibrary(lib); - return null; - } catch (Throwable e) { - Log.wtf(LOGTAG, "Couldn't load " + lib + ". Trying native library dir."); - - if (Build.VERSION.SDK_INT < 9) { - // We can't use nativeLibraryDir. - return e; - } - - // Attempt 2: use nativeLibraryDir, which should also work. - final String libDir = context.getApplicationInfo().nativeLibraryDir; - final String libPath = libDir + "/lib" + lib + ".so"; - - // Does it even exist? - if (new File(libPath).exists()) { - if (attemptLoad(libPath)) { - // Success! - return null; - } - Log.wtf(LOGTAG, "Library exists but couldn't load!"); - } else { - Log.wtf(LOGTAG, "Library doesn't exist when it should."); - } - - // We failed. Return the original cause. - return e; - } - } - - public static void doLoadLibrary(final Context context, final String lib) { - final Throwable e = doLoadLibraryExpected(context, lib); - if (e == null) { - // Success. - return; - } - - // If we're in a mismatched UID state (Bug 1042935 Comment 16) there's really - // nothing we can do. - if (Build.VERSION.SDK_INT >= 9) { - final String nativeLibPath = context.getApplicationInfo().nativeLibraryDir; - if (nativeLibPath.contains("mismatched_uid")) { - throw new RuntimeException("Fatal: mismatched UID: cannot load."); - } - } - - // Attempt 3: try finding the path the pseudo-supported way using .dataDir. - final String dataLibPath = context.getApplicationInfo().dataDir + "/lib/lib" + lib + ".so"; - if (attemptLoad(dataLibPath)) { - return; - } - - // Attempt 4: use /data/app-lib directly. This is a last-ditch effort. - final String androidPackageName = context.getPackageName(); - if (attemptLoad("/data/app-lib/" + androidPackageName + "/lib" + lib + ".so")) { - return; - } - - // Attempt 5: even more optimistic. - if (attemptLoad("/data/data/" + androidPackageName + "/lib/lib" + lib + ".so")) { - return; - } - - // Look in our files directory, copying from the APK first if necessary. - final String filesLibDir = context.getFilesDir() + "/lib"; - final String filesLibPath = filesLibDir + "/lib" + lib + ".so"; - if (new File(filesLibPath).exists()) { - if (attemptLoad(filesLibPath)) { - return; - } - } else { - // Try copying. - if (extractLibrary(context, lib, filesLibDir)) { - // Let's try it! - if (attemptLoad(filesLibPath)) { - return; - } - } - } - - // Give up loudly, leaking information to debug the failure. - final String message = getLoadDiagnostics(context, lib); - Log.e(LOGTAG, "Load diagnostics: " + message); - - // Throw the descriptive message, using the original library load - // failure as the cause. - throw new RuntimeException(message, e); - } - - public synchronized static void loadMozGlue(final Context context) { - if (sMozGlueLoaded) { - return; - } - - doLoadLibrary(context, "mozglue"); - sMozGlueLoaded = true; - } - - public synchronized static void loadGeckoLibs(final Context context, final String apkName) { - loadLibsSetupLocked(context); - loadGeckoLibsNative(apkName); - } - - public synchronized static void extractGeckoLibs(final Context context, final String apkName) { - loadLibsSetupLocked(context); - try { - extractGeckoLibsNative(apkName); - } catch (Exception e) { - Log.e(LOGTAG, "Failing library extraction.", e); - } - } - - private static void setupLocaleEnvironment() { - putenv("LANG=" + Locale.getDefault().toString()); - NumberFormat nf = NumberFormat.getInstance(); - if (nf instanceof DecimalFormat) { - DecimalFormat df = (DecimalFormat)nf; - DecimalFormatSymbols dfs = df.getDecimalFormatSymbols(); - - putenv("LOCALE_DECIMAL_POINT=" + dfs.getDecimalSeparator()); - putenv("LOCALE_THOUSANDS_SEP=" + dfs.getGroupingSeparator()); - putenv("LOCALE_GROUPING=" + (char)df.getGroupingSize()); - } - } - - @SuppressWarnings("serial") - public static class AbortException extends Exception { - public AbortException(String msg) { - super(msg); - } - } - - @JNITarget - public static void abort(final String msg) { - final Thread thread = Thread.currentThread(); - final Thread.UncaughtExceptionHandler uncaughtHandler = - thread.getUncaughtExceptionHandler(); - if (uncaughtHandler != null) { - uncaughtHandler.uncaughtException(thread, new AbortException(msg)); - } - } - - // These methods are implemented in mozglue/android/nsGeckoUtils.cpp - private static native void putenv(String map); - - // These methods are implemented in mozglue/android/APKOpen.cpp - public static native void nativeRun(String args); - private static native void loadGeckoLibsNative(String apkName); - private static native void loadSQLiteLibsNative(String apkName); - private static native void loadNSSLibsNative(String apkName); - private static native void extractGeckoLibsNative(String apkName); -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/JNIObject.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/JNIObject.java deleted file mode 100644 index a3a127a1a..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/JNIObject.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.mozilla.gecko.mozglue; - -// Class that all classes with native methods extend from. -public abstract class JNIObject -{ - // Pointer to a WeakPtr object that refers to the native object. - private long mHandle; - - // Dispose of any reference to a native object. - protected abstract void disposeNative(); -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/NativeReference.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/NativeReference.java deleted file mode 100644 index 9d897d384..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/NativeReference.java +++ /dev/null @@ -1,13 +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.mozglue; - -public interface NativeReference -{ - public void release(); - - public boolean isReleased(); -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/NativeZip.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/NativeZip.java deleted file mode 100644 index 11241c575..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/NativeZip.java +++ /dev/null @@ -1,84 +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.mozglue; - -import android.support.annotation.Keep; -import org.mozilla.gecko.annotation.JNITarget; - -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.util.zip.Inflater; -import java.util.zip.InflaterInputStream; - -public class NativeZip implements NativeReference { - private static final int DEFLATE = 8; - private static final int STORE = 0; - - private volatile long mObj; - @Keep - private InputStream mInput; - - public NativeZip(String path) { - mObj = getZip(path); - } - - public NativeZip(InputStream input) { - if (!(input instanceof ByteBufferInputStream)) { - throw new IllegalArgumentException("Got " + input.getClass() - + ", but expected ByteBufferInputStream!"); - } - ByteBufferInputStream bbinput = (ByteBufferInputStream)input; - mObj = getZipFromByteBuffer(bbinput.mBuf); - mInput = input; - } - - @Override - protected void finalize() { - release(); - } - - @Override - public void release() { - if (mObj != 0) { - _release(mObj); - mObj = 0; - } - mInput = null; - } - - @Override - public boolean isReleased() { - return (mObj == 0); - } - - public InputStream getInputStream(String path) { - if (isReleased()) { - throw new IllegalStateException("Can't get path \"" + path - + "\" because NativeZip is closed!"); - } - return _getInputStream(mObj, path); - } - - private static native long getZip(String path); - private static native long getZipFromByteBuffer(ByteBuffer buffer); - private static native void _release(long obj); - private native InputStream _getInputStream(long obj, String path); - - @JNITarget - private InputStream createInputStream(ByteBuffer buffer, int compression) { - if (compression != STORE && compression != DEFLATE) { - throw new IllegalArgumentException("Unexpected compression: " + compression); - } - - InputStream input = new ByteBufferInputStream(buffer, this); - if (compression == DEFLATE) { - Inflater inflater = new Inflater(true); - input = new InflaterInputStream(input, inflater); - } - - return input; - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/SafeIntent.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/SafeIntent.java deleted file mode 100644 index 6942962fe..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/SafeIntent.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * 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/. - */ - -// This should be in util/, but is here because of build dependency issues. -package org.mozilla.gecko.mozglue; - -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.util.Log; - -import java.util.ArrayList; - -/** - * External applications can pass values into Intents that can cause us to crash: in defense, - * we wrap {@link Intent} and catch the exceptions they may force us to throw. See bug 1090385 - * for more. - */ -public class SafeIntent { - private static final String LOGTAG = "Gecko" + SafeIntent.class.getSimpleName(); - - private final Intent intent; - - public SafeIntent(final Intent intent) { - this.intent = intent; - } - - public boolean hasExtra(String name) { - try { - return intent.hasExtra(name); - } catch (OutOfMemoryError e) { - Log.w(LOGTAG, "Couldn't determine if intent had an extra: OOM. Malformed?"); - return false; - } catch (RuntimeException e) { - Log.w(LOGTAG, "Couldn't determine if intent had an extra.", e); - return false; - } - } - - public boolean getBooleanExtra(final String name, final boolean defaultValue) { - try { - return intent.getBooleanExtra(name, defaultValue); - } catch (OutOfMemoryError e) { - Log.w(LOGTAG, "Couldn't get intent extras: OOM. Malformed?"); - return defaultValue; - } catch (RuntimeException e) { - Log.w(LOGTAG, "Couldn't get intent extras.", e); - return defaultValue; - } - } - - public int getIntExtra(final String name, final int defaultValue) { - try { - return intent.getIntExtra(name, defaultValue); - } catch (OutOfMemoryError e) { - Log.w(LOGTAG, "Couldn't get intent extras: OOM. Malformed?"); - return defaultValue; - } catch (RuntimeException e) { - Log.w(LOGTAG, "Couldn't get intent extras.", e); - return defaultValue; - } - } - - public String getStringExtra(final String name) { - try { - return intent.getStringExtra(name); - } catch (OutOfMemoryError e) { - Log.w(LOGTAG, "Couldn't get intent extras: OOM. Malformed?"); - return null; - } catch (RuntimeException e) { - Log.w(LOGTAG, "Couldn't get intent extras.", e); - return null; - } - } - - public Bundle getBundleExtra(final String name) { - try { - return intent.getBundleExtra(name); - } catch (OutOfMemoryError e) { - Log.w(LOGTAG, "Couldn't get intent extras: OOM. Malformed?"); - return null; - } catch (RuntimeException e) { - Log.w(LOGTAG, "Couldn't get intent extras.", e); - return null; - } - } - - public String getAction() { - return intent.getAction(); - } - - public String getDataString() { - try { - return intent.getDataString(); - } catch (OutOfMemoryError e) { - Log.w(LOGTAG, "Couldn't get intent data string: OOM. Malformed?"); - return null; - } catch (RuntimeException e) { - Log.w(LOGTAG, "Couldn't get intent data string.", e); - return null; - } - } - - public ArrayList<String> getStringArrayListExtra(final String name) { - try { - return intent.getStringArrayListExtra(name); - } catch (OutOfMemoryError e) { - Log.w(LOGTAG, "Couldn't get intent data string: OOM. Malformed?"); - return null; - } catch (RuntimeException e) { - Log.w(LOGTAG, "Couldn't get intent data string.", e); - return null; - } - } - - public Uri getData() { - try { - return intent.getData(); - } catch (OutOfMemoryError e) { - Log.w(LOGTAG, "Couldn't get intent data: OOM. Malformed?"); - return null; - } catch (RuntimeException e) { - Log.w(LOGTAG, "Couldn't get intent data.", e); - return null; - } - } - - public Intent getUnsafe() { - return intent; - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/permissions/PermissionBlock.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/permissions/PermissionBlock.java deleted file mode 100644 index a4d72f258..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/permissions/PermissionBlock.java +++ /dev/null @@ -1,133 +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.permissions; - -import org.mozilla.gecko.util.ThreadUtils; - -import android.app.Activity; -import android.content.Context; -import android.support.annotation.NonNull; - -/** - * Helper class to run code blocks depending on whether a user has granted or denied certain runtime permissions. - */ -public class PermissionBlock { - private final PermissionsHelper helper; - - private Context context; - private String[] permissions; - private boolean onUIThread; - private Runnable onPermissionsGranted; - private Runnable onPermissionsDenied; - private boolean doNotPrompt; - - /* package-private */ PermissionBlock(Context context, PermissionsHelper helper) { - this.context = context; - this.helper = helper; - } - - /** - * Determine whether the app has been granted the specified permissions. - */ - public PermissionBlock withPermissions(@NonNull String... permissions) { - this.permissions = permissions; - return this; - } - - /** - * Execute all callbacks on the UI thread. - */ - public PermissionBlock onUIThread() { - this.onUIThread = true; - return this; - } - - /** - * Do not prompt the user to accept the permission if it has not been granted yet. - */ - public PermissionBlock doNotPrompt() { - doNotPrompt = true; - return this; - } - - /** - * If the condition is true then do not prompt the user to accept the permission if it has not - * been granted yet. - */ - public PermissionBlock doNotPromptIf(boolean condition) { - if (condition) { - doNotPrompt(); - } - - return this; - } - - /** - * Execute this permission block. Calling this method will prompt the user if needed. - */ - public void run() { - run(null); - } - - /** - * Execute the specified runnable if the app has been granted all permissions. Calling this method will prompt the - * user if needed. - */ - public void run(Runnable onPermissionsGranted) { - if (!doNotPrompt && !(context instanceof Activity)) { - throw new IllegalStateException("You need to either specify doNotPrompt() or pass in an Activity context"); - } - - this.onPermissionsGranted = onPermissionsGranted; - - if (hasPermissions(context)) { - onPermissionsGranted(); - } else if (doNotPrompt) { - onPermissionsDenied(); - } else { - Permissions.prompt((Activity) context, this); - } - - // This reference is no longer needed. Let's clear it now to avoid memory leaks. - context = null; - } - - /** - * Execute this fallback if at least one permission has not been granted. - */ - public PermissionBlock andFallback(@NonNull Runnable onPermissionsDenied) { - this.onPermissionsDenied = onPermissionsDenied; - return this; - } - - /* package-private */ void onPermissionsGranted() { - executeRunnable(onPermissionsGranted); - } - - /* package-private */ void onPermissionsDenied() { - executeRunnable(onPermissionsDenied); - } - - private void executeRunnable(Runnable runnable) { - if (runnable == null) { - return; - } - - if (onUIThread) { - ThreadUtils.postToUiThread(runnable); - } else { - runnable.run(); - } - } - - /* package-private */ String[] getPermissions() { - return permissions; - } - - /* packacge-private */ boolean hasPermissions(Context context) { - return helper.hasPermissions(context, permissions); - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/permissions/Permissions.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/permissions/Permissions.java deleted file mode 100644 index c1b38f61c..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/permissions/Permissions.java +++ /dev/null @@ -1,210 +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.permissions; - -import android.app.Activity; -import android.content.Context; -import android.content.pm.PackageManager; -import android.support.annotation.NonNull; - -import org.mozilla.gecko.util.ThreadUtils; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Queue; -import java.util.concurrent.Callable; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.FutureTask; - -/** - * Convenience class for checking and prompting for runtime permissions. - * - * Example: - * - * Permissions.from(activity) - * .withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE) - * .onUiThread() - * .andFallback(onPermissionDenied()) - * .run(onPermissionGranted()) - * - * This example will run the runnable returned by onPermissionGranted() if the WRITE_EXTERNAL_STORAGE permission is - * already granted. Otherwise it will prompt the user and run the runnable returned by onPermissionGranted() or - * onPermissionDenied() depending on whether the user accepted or not. If onUiThread() is specified then all callbacks - * will be run on the UI thread. - */ -public class Permissions { - private static final Queue<PermissionBlock> waiting = new LinkedList<>(); - private static final Queue<PermissionBlock> prompt = new LinkedList<>(); - - private static PermissionsHelper permissionHelper = new PermissionsHelper(); - - /** - * Entry point for checking (and optionally prompting for) runtime permissions. - * - * Note: The provided context needs to be an Activity context in order to prompt. Use doNotPrompt() - * for all other contexts. - */ - public static PermissionBlock from(@NonNull Context context) { - return new PermissionBlock(context, permissionHelper); - } - - /** - * This method will block until the specified permissions have been granted or denied by the user. - * If needed the user will be prompted. - * - * @return true if all of the permissions have been granted. False if any of the permissions have been denied. - */ - public static boolean waitFor(@NonNull Activity activity, String... permissions) { - ThreadUtils.assertNotOnUiThread(); // We do not want to block the UI thread. - - // This task will block until all of the permissions have been granted - final FutureTask<Boolean> blockingTask = new FutureTask<>(new Callable<Boolean>() { - @Override - public Boolean call() throws Exception { - return true; - } - }); - - // This runnable will cancel the task if any of the permissions have been denied - Runnable cancelBlockingTask = new Runnable() { - @Override - public void run() { - blockingTask.cancel(true); - } - }; - - Permissions.from(activity) - .withPermissions(permissions) - .andFallback(cancelBlockingTask) - .run(blockingTask); - - try { - return blockingTask.get(); - } catch (InterruptedException | ExecutionException | CancellationException e) { - return false; - } - } - - /** - * Determine whether you have been granted particular permissions. - */ - public static boolean has(Context context, String... permissions) { - return permissionHelper.hasPermissions(context, permissions); - } - - /* package-private */ static void setPermissionHelper(PermissionsHelper permissionHelper) { - Permissions.permissionHelper = permissionHelper; - } - - /** - * Callback for Activity.onRequestPermissionsResult(). All activities that prompt for permissions using this class - * should implement onRequestPermissionsResult() and call this method. - */ - public static synchronized void onRequestPermissionsResult(@NonNull Activity activity, @NonNull String[] permissions, @NonNull int[] grantResults) { - processGrantResults(permissions, grantResults); - - processQueue(activity, permissions, grantResults); - } - - /* package-private */ static synchronized void prompt(Activity activity, PermissionBlock block) { - if (prompt.isEmpty()) { - prompt.add(block); - showPrompt(activity); - } else { - waiting.add(block); - } - } - - private static synchronized void processGrantResults(@NonNull String[] permissions, @NonNull int[] grantResults) { - final HashSet<String> grantedPermissions = collectGrantedPermissions(permissions, grantResults); - - while (!prompt.isEmpty()) { - final PermissionBlock block = prompt.poll(); - - if (allPermissionsGranted(block, grantedPermissions)) { - block.onPermissionsGranted(); - } else { - block.onPermissionsDenied(); - } - } - } - - private static synchronized void processQueue(Activity activity, String[] permissions, int[] grantResults) { - final HashSet<String> deniedPermissions = collectDeniedPermissions(permissions, grantResults); - - while (!waiting.isEmpty()) { - final PermissionBlock block = waiting.poll(); - - if (block.hasPermissions(activity)) { - block.onPermissionsGranted(); - } else { - if (atLeastOnePermissionDenied(block, deniedPermissions)) { - // We just prompted the user and one of the permissions of this block has been denied: - // There's no reason to instantly prompt again; Just reject without prompting. - block.onPermissionsDenied(); - } else { - prompt.add(block); - } - } - } - - if (!prompt.isEmpty()) { - showPrompt(activity); - } - } - - private static synchronized void showPrompt(Activity activity) { - HashSet<String> permissions = new HashSet<>(); - - for (PermissionBlock block : prompt) { - Collections.addAll(permissions, block.getPermissions()); - } - - permissionHelper.prompt(activity, permissions.toArray(new String[permissions.size()])); - } - - private static HashSet<String> collectGrantedPermissions(@NonNull String[] permissions, @NonNull int[] grantResults) { - return filterPermissionsByResult(permissions, grantResults, PackageManager.PERMISSION_GRANTED); - } - - private static HashSet<String> collectDeniedPermissions(@NonNull String[] permissions, @NonNull int[] grantResults) { - return filterPermissionsByResult(permissions, grantResults, PackageManager.PERMISSION_DENIED); - } - - private static HashSet<String> filterPermissionsByResult(@NonNull String[] permissions, @NonNull int[] grantResults, int result) { - HashSet<String> grantedPermissions = new HashSet<>(permissions.length); - for (int i = 0; i < permissions.length; i++) { - if (grantResults[i] == result) { - grantedPermissions.add(permissions[i]); - } - } - return grantedPermissions; - } - - private static boolean allPermissionsGranted(PermissionBlock block, HashSet<String> grantedPermissions) { - for (String permission : block.getPermissions()) { - if (!grantedPermissions.contains(permission)) { - return false; - } - } - - return true; - } - - private static boolean atLeastOnePermissionDenied(PermissionBlock block, HashSet<String> deniedPermissions) { - for (String permission : block.getPermissions()) { - if (deniedPermissions.contains(permission)) { - return true; - } - } - - return false; - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/permissions/PermissionsHelper.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/permissions/PermissionsHelper.java deleted file mode 100644 index 945a81f43..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/permissions/PermissionsHelper.java +++ /dev/null @@ -1,32 +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.permissions; - -import android.app.Activity; -import android.content.Context; -import android.content.pm.PackageManager; -import android.support.v4.app.ActivityCompat; -import android.support.v4.content.ContextCompat; - -/* package-private */ class PermissionsHelper { - private static final int PERMISSIONS_REQUEST_CODE = 212; - - public boolean hasPermissions(Context context, String... permissions) { - for (String permission : permissions) { - final int permissionCheck = ContextCompat.checkSelfPermission(context, permission); - - if (permissionCheck != PackageManager.PERMISSION_GRANTED) { - return false; - } - } - - return true; - } - - public void prompt(Activity activity, String[] permissions) { - ActivityCompat.requestPermissions(activity, permissions, PERMISSIONS_REQUEST_CODE); - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/sqlite/ByteBufferInputStream.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/sqlite/ByteBufferInputStream.java deleted file mode 100644 index f6b16619f..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/sqlite/ByteBufferInputStream.java +++ /dev/null @@ -1,38 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.sqlite; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; - -/* - * Helper class to make the ByteBuffers returned by SQLite BLOB - * easier to use. - */ -public class ByteBufferInputStream extends InputStream { - private final ByteBuffer mByteBuffer; - - public ByteBufferInputStream(ByteBuffer aByteBuffer) { - mByteBuffer = aByteBuffer; - } - - @Override - public synchronized int read() throws IOException { - if (!mByteBuffer.hasRemaining()) { - return -1; - } - return mByteBuffer.get(); - } - - @Override - public synchronized int read(byte[] aBytes, int aOffset, int aLen) - throws IOException { - int toRead = Math.min(aLen, mByteBuffer.remaining()); - mByteBuffer.get(aBytes, aOffset, toRead); - return toRead; - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/sqlite/MatrixBlobCursor.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/sqlite/MatrixBlobCursor.java deleted file mode 100644 index 3e2023c86..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/sqlite/MatrixBlobCursor.java +++ /dev/null @@ -1,366 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.mozilla.gecko.sqlite; - -import java.nio.ByteBuffer; -import java.util.ArrayList; - -import org.mozilla.gecko.annotation.JNITarget; -import org.mozilla.gecko.AppConstants; - -import android.database.AbstractCursor; -import android.database.CursorIndexOutOfBoundsException; -import android.util.Log; - -/** - * A mutable cursor implementation backed by an array of {@code Object}s. Use - * {@link #newRow()} to add rows. Automatically expands internal capacity - * as needed. - * - * This class provides one missing feature from Android's MatrixCursor: - * the implementation of getBlob that was inadvertently omitted from API 9 (and - * perhaps later; it's present in 14). - * - * MatrixCursor is all private, so we entirely duplicate it here. - */ -public class MatrixBlobCursor extends AbstractCursor { - private static final String LOGTAG = "GeckoMatrixCursor"; - - private final String[] columnNames; - private final int columnCount; - - private int rowCount; - private Throwable allocationStack; - - Object[] data; - - /** - * Constructs a new cursor with the given initial capacity. - * - * @param columnNames names of the columns, the ordering of which - * determines column ordering elsewhere in this cursor - * @param initialCapacity in rows - */ - @JNITarget - public MatrixBlobCursor(String[] columnNames, int initialCapacity) { - this.columnNames = columnNames; - this.columnCount = columnNames.length; - - if (initialCapacity < 1) { - initialCapacity = 1; - } - - this.data = new Object[columnCount * initialCapacity]; - if (AppConstants.DEBUG_BUILD) { - this.allocationStack = new Throwable("allocationStack"); - } - } - - /** - * Constructs a new cursor. - * - * @param columnNames names of the columns, the ordering of which - * determines column ordering elsewhere in this cursor - */ - @JNITarget - public MatrixBlobCursor(String[] columnNames) { - this(columnNames, 16); - } - - /** - * Closes the Cursor, releasing all of its resources. - */ - public void close() { - this.allocationStack = null; - this.data = null; - super.close(); - } - - /** - * Gets value at the given column for the current row. - */ - protected Object get(int column) { - if (column < 0 || column >= columnCount) { - throw new CursorIndexOutOfBoundsException("Requested column: " - + column + ", # of columns: " + columnCount); - } - if (mPos < 0) { - throw new CursorIndexOutOfBoundsException("Before first row."); - } - if (mPos >= rowCount) { - throw new CursorIndexOutOfBoundsException("After last row."); - } - return data[mPos * columnCount + column]; - } - - /** - * Adds a new row to the end and returns a builder for that row. Not safe - * for concurrent use. - * - * @return builder which can be used to set the column values for the new - * row - */ - public RowBuilder newRow() { - rowCount++; - int endIndex = rowCount * columnCount; - ensureCapacity(endIndex); - int start = endIndex - columnCount; - return new RowBuilder(start, endIndex); - } - - /** - * Adds a new row to the end with the given column values. Not safe - * for concurrent use. - * - * @throws IllegalArgumentException if {@code columnValues.length != - * columnNames.length} - * @param columnValues in the same order as the the column names specified - * at cursor construction time - */ - @JNITarget - public void addRow(Object[] columnValues) { - if (columnValues.length != columnCount) { - throw new IllegalArgumentException("columnNames.length = " - + columnCount + ", columnValues.length = " - + columnValues.length); - } - - int start = rowCount++ * columnCount; - ensureCapacity(start + columnCount); - System.arraycopy(columnValues, 0, data, start, columnCount); - } - - /** - * Adds a new row to the end with the given column values. Not safe - * for concurrent use. - * - * @throws IllegalArgumentException if {@code columnValues.size() != - * columnNames.length} - * @param columnValues in the same order as the the column names specified - * at cursor construction time - */ - @JNITarget - public void addRow(Iterable<?> columnValues) { - final int start = rowCount * columnCount; - - if (columnValues instanceof ArrayList<?>) { - addRow((ArrayList<?>) columnValues, start); - return; - } - - final int end = start + columnCount; - int current = start; - - ensureCapacity(end); - final Object[] localData = data; - for (Object columnValue : columnValues) { - if (current == end) { - // TODO: null out row? - throw new IllegalArgumentException( - "columnValues.size() > columnNames.length"); - } - localData[current++] = columnValue; - } - - if (current != end) { - // TODO: null out row? - throw new IllegalArgumentException( - "columnValues.size() < columnNames.length"); - } - - // Increase row count here in case we encounter an exception. - rowCount++; - } - - /** Optimization for {@link ArrayList}. */ - @JNITarget - private void addRow(ArrayList<?> columnValues, int start) { - final int size = columnValues.size(); - if (size != columnCount) { - throw new IllegalArgumentException("columnNames.length = " - + columnCount + ", columnValues.size() = " + size); - } - - final int end = start + columnCount; - ensureCapacity(end); - - // Take a reference just in case someone calls ensureCapacity - // and `data` gets replaced by a new array! - final Object[] localData = data; - for (int i = 0; i < size; i++) { - localData[start + i] = columnValues.get(i); - } - - rowCount++; - } - - /** - * Ensures that this cursor has enough capacity. If it needs to allocate - * a new array, the existing capacity will be at least doubled. - */ - private void ensureCapacity(final int size) { - if (size <= data.length) { - return; - } - - final Object[] oldData = this.data; - this.data = new Object[Math.max(size, data.length * 2)]; - System.arraycopy(oldData, 0, this.data, 0, oldData.length); - } - - /** - * Builds a row, starting from the left-most column and adding one column - * value at a time. Follows the same ordering as the column names specified - * at cursor construction time. - * - * Not thread-safe. - */ - public class RowBuilder { - private int index; - private final int endIndex; - - RowBuilder(int index, int endIndex) { - this.index = index; - this.endIndex = endIndex; - } - - /** - * Sets the next column value in this row. - * - * @throws CursorIndexOutOfBoundsException if you try to add too many - * values - * @return this builder to support chaining - */ - public RowBuilder add(final Object columnValue) { - if (index == endIndex) { - throw new CursorIndexOutOfBoundsException("No more columns left."); - } - - data[index++] = columnValue; - return this; - } - } - - /** - * Not thread safe. - */ - public void set(int column, Object value) { - if (column < 0 || column >= columnCount) { - throw new CursorIndexOutOfBoundsException("Requested column: " - + column + ", # of columns: " + columnCount); - } - if (mPos < 0) { - throw new CursorIndexOutOfBoundsException("Before first row."); - } - if (mPos >= rowCount) { - throw new CursorIndexOutOfBoundsException("After last row."); - } - data[mPos * columnCount + column] = value; - } - - // AbstractCursor implementation. - @Override - public int getCount() { - return rowCount; - } - - @Override - public String[] getColumnNames() { - return columnNames; - } - - @Override - public String getString(int column) { - Object value = get(column); - if (value == null) return null; - return value.toString(); - } - - @Override - public short getShort(int column) { - final Object value = get(column); - if (value == null) return 0; - if (value instanceof Number) return ((Number) value).shortValue(); - return Short.parseShort(value.toString()); - } - - @Override - public int getInt(int column) { - Object value = get(column); - if (value == null) return 0; - if (value instanceof Number) return ((Number) value).intValue(); - return Integer.parseInt(value.toString()); - } - - @Override - public long getLong(int column) { - Object value = get(column); - if (value == null) return 0; - if (value instanceof Number) return ((Number) value).longValue(); - return Long.parseLong(value.toString()); - } - - @Override - public float getFloat(int column) { - Object value = get(column); - if (value == null) return 0.0f; - if (value instanceof Number) return ((Number) value).floatValue(); - return Float.parseFloat(value.toString()); - } - - @Override - public double getDouble(int column) { - Object value = get(column); - if (value == null) return 0.0d; - if (value instanceof Number) return ((Number) value).doubleValue(); - return Double.parseDouble(value.toString()); - } - - @Override - public byte[] getBlob(int column) { - Object value = get(column); - if (value == null) return null; - if (value instanceof byte[]) { - return (byte[]) value; - } - - if (value instanceof ByteBuffer) { - final ByteBuffer bytes = (ByteBuffer) value; - byte[] byteArray = new byte[bytes.remaining()]; - bytes.get(byteArray); - return byteArray; - } - throw new UnsupportedOperationException("BLOB Object not of known type"); - } - - @Override - public boolean isNull(int column) { - return get(column) == null; - } - - @Override - protected void finalize() { - if (AppConstants.DEBUG_BUILD) { - if (!isClosed()) { - Log.e(LOGTAG, "Cursor finalized without being closed", this.allocationStack); - } - } - - super.finalize(); - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/sqlite/SQLiteBridge.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/sqlite/SQLiteBridge.java deleted file mode 100644 index 866b9e286..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/sqlite/SQLiteBridge.java +++ /dev/null @@ -1,387 +0,0 @@ -/* 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.sqlite; - -import android.content.ContentValues; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteException; -import android.text.TextUtils; -import android.util.Log; - -import org.mozilla.gecko.annotation.RobocopTarget; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Map.Entry; - -/* - * This class allows using the mozsqlite3 library included with Firefox - * to read SQLite databases, instead of the Android SQLiteDataBase API, - * which might use whatever outdated DB is present on the Android system. - */ -public class SQLiteBridge { - private static final String LOGTAG = "SQLiteBridge"; - - // Path to the database. If this database was not opened with openDatabase, we reopen it every query. - private final String mDb; - - // Pointer to the database if it was opened with openDatabase. 0 implies closed. - protected volatile long mDbPointer; - - // Values remembered after a query. - private long[] mQueryResults; - - private boolean mTransactionSuccess; - private boolean mInTransaction; - - private static final int RESULT_INSERT_ROW_ID = 0; - private static final int RESULT_ROWS_CHANGED = 1; - - // Shamelessly cribbed from db/sqlite3/src/moz.build. - private static final int DEFAULT_PAGE_SIZE_BYTES = 32768; - - // The same size we use elsewhere. - private static final int MAX_WAL_SIZE_BYTES = 524288; - - // JNI code in $(topdir)/mozglue/android/.. - private static native MatrixBlobCursor sqliteCall(String aDb, String aQuery, - String[] aParams, - long[] aUpdateResult) - throws SQLiteBridgeException; - private static native MatrixBlobCursor sqliteCallWithDb(long aDb, String aQuery, - String[] aParams, - long[] aUpdateResult) - throws SQLiteBridgeException; - private static native long openDatabase(String aDb) - throws SQLiteBridgeException; - private static native void closeDatabase(long aDb); - - // Takes the path to the database we want to access. - @RobocopTarget - public SQLiteBridge(String aDb) throws SQLiteBridgeException { - mDb = aDb; - } - - // Executes a simple line of sql. - public void execSQL(String sql) - throws SQLiteBridgeException { - Cursor cursor = internalQuery(sql, null); - cursor.close(); - } - - // Executes a simple line of sql. Allow you to bind arguments - public void execSQL(String sql, String[] bindArgs) - throws SQLiteBridgeException { - Cursor cursor = internalQuery(sql, bindArgs); - cursor.close(); - } - - // Executes a DELETE statement on the database - public int delete(String table, String whereClause, String[] whereArgs) - throws SQLiteBridgeException { - StringBuilder sb = new StringBuilder("DELETE from "); - sb.append(table); - if (whereClause != null) { - sb.append(" WHERE " + whereClause); - } - - execSQL(sb.toString(), whereArgs); - return (int)mQueryResults[RESULT_ROWS_CHANGED]; - } - - public Cursor query(String table, - String[] columns, - String selection, - String[] selectionArgs, - String groupBy, - String having, - String orderBy, - String limit) - throws SQLiteBridgeException { - StringBuilder sb = new StringBuilder("SELECT "); - if (columns != null) - sb.append(TextUtils.join(", ", columns)); - else - sb.append(" * "); - - sb.append(" FROM "); - sb.append(table); - - if (selection != null) { - sb.append(" WHERE " + selection); - } - - if (groupBy != null) { - sb.append(" GROUP BY " + groupBy); - } - - if (having != null) { - sb.append(" HAVING " + having); - } - - if (orderBy != null) { - sb.append(" ORDER BY " + orderBy); - } - - if (limit != null) { - sb.append(" " + limit); - } - - return rawQuery(sb.toString(), selectionArgs); - } - - @RobocopTarget - public Cursor rawQuery(String sql, String[] selectionArgs) - throws SQLiteBridgeException { - return internalQuery(sql, selectionArgs); - } - - public long insert(String table, String nullColumnHack, ContentValues values) - throws SQLiteBridgeException { - if (values == null) - return 0; - - ArrayList<String> valueNames = new ArrayList<String>(); - ArrayList<String> valueBinds = new ArrayList<String>(); - ArrayList<String> keyNames = new ArrayList<String>(); - - for (Entry<String, Object> value : values.valueSet()) { - keyNames.add(value.getKey()); - - Object val = value.getValue(); - if (val == null) { - valueNames.add("NULL"); - } else { - valueNames.add("?"); - valueBinds.add(val.toString()); - } - } - - StringBuilder sb = new StringBuilder("INSERT into "); - sb.append(table); - - sb.append(" ("); - sb.append(TextUtils.join(", ", keyNames)); - sb.append(")"); - - // XXX - Do we need to bind these values? - sb.append(" VALUES ("); - sb.append(TextUtils.join(", ", valueNames)); - sb.append(") "); - - String[] binds = new String[valueBinds.size()]; - valueBinds.toArray(binds); - execSQL(sb.toString(), binds); - return mQueryResults[RESULT_INSERT_ROW_ID]; - } - - public int update(String table, ContentValues values, String whereClause, String[] whereArgs) - throws SQLiteBridgeException { - if (values == null) - return 0; - - ArrayList<String> valueNames = new ArrayList<String>(); - - StringBuilder sb = new StringBuilder("UPDATE "); - sb.append(table); - sb.append(" SET "); - - boolean isFirst = true; - - for (Entry<String, Object> value : values.valueSet()) { - if (isFirst) - isFirst = false; - else - sb.append(", "); - - sb.append(value.getKey()); - - Object val = value.getValue(); - if (val == null) { - sb.append(" = NULL"); - } else { - sb.append(" = ?"); - valueNames.add(val.toString()); - } - } - - if (!TextUtils.isEmpty(whereClause)) { - sb.append(" WHERE "); - sb.append(whereClause); - valueNames.addAll(Arrays.asList(whereArgs)); - } - - String[] binds = new String[valueNames.size()]; - valueNames.toArray(binds); - - execSQL(sb.toString(), binds); - return (int)mQueryResults[RESULT_ROWS_CHANGED]; - } - - public int getVersion() - throws SQLiteBridgeException { - Cursor cursor = internalQuery("PRAGMA user_version", null); - int ret = -1; - if (cursor != null) { - cursor.moveToFirst(); - String version = cursor.getString(0); - ret = Integer.parseInt(version); - cursor.close(); - } - return ret; - } - - // Do an SQL query, substituting the parameters in the query with the passed - // parameters. The parameters are substituted in order: named parameters - // are not supported. - private Cursor internalQuery(String aQuery, String[] aParams) - throws SQLiteBridgeException { - - mQueryResults = new long[2]; - if (isOpen()) { - return sqliteCallWithDb(mDbPointer, aQuery, aParams, mQueryResults); - } - return sqliteCall(mDb, aQuery, aParams, mQueryResults); - } - - /* - * The second two parameters here are just provided for compatibility with SQLiteDatabase - * Support for them is not currently implemented. - */ - public static SQLiteBridge openDatabase(String path, SQLiteDatabase.CursorFactory factory, int flags) - throws SQLiteException { - if (factory != null) { - throw new RuntimeException("factory not supported."); - } - if (flags != 0) { - throw new RuntimeException("flags not supported."); - } - - SQLiteBridge bridge = null; - try { - bridge = new SQLiteBridge(path); - bridge.mDbPointer = SQLiteBridge.openDatabase(path); - } catch (SQLiteBridgeException ex) { - // Catch and rethrow as a SQLiteException to match SQLiteDatabase. - throw new SQLiteException(ex.getMessage()); - } - - prepareWAL(bridge); - - return bridge; - } - - public void close() { - if (isOpen()) { - closeDatabase(mDbPointer); - } - mDbPointer = 0L; - } - - public boolean isOpen() { - return mDbPointer != 0; - } - - public void beginTransaction() throws SQLiteBridgeException { - if (inTransaction()) { - throw new SQLiteBridgeException("Nested transactions are not supported"); - } - execSQL("BEGIN EXCLUSIVE"); - mTransactionSuccess = false; - mInTransaction = true; - } - - public void beginTransactionNonExclusive() throws SQLiteBridgeException { - if (inTransaction()) { - throw new SQLiteBridgeException("Nested transactions are not supported"); - } - execSQL("BEGIN IMMEDIATE"); - mTransactionSuccess = false; - mInTransaction = true; - } - - public void endTransaction() { - if (!inTransaction()) - return; - - try { - if (mTransactionSuccess) { - execSQL("COMMIT TRANSACTION"); - } else { - execSQL("ROLLBACK TRANSACTION"); - } - } catch (SQLiteBridgeException ex) { - Log.e(LOGTAG, "Error ending transaction", ex); - } - mInTransaction = false; - mTransactionSuccess = false; - } - - public void setTransactionSuccessful() throws SQLiteBridgeException { - if (!inTransaction()) { - throw new SQLiteBridgeException("setTransactionSuccessful called outside a transaction"); - } - mTransactionSuccess = true; - } - - public boolean inTransaction() { - return mInTransaction; - } - - @Override - public void finalize() { - if (isOpen()) { - Log.e(LOGTAG, "Bridge finalized without closing the database"); - close(); - } - } - - private static void prepareWAL(final SQLiteBridge bridge) { - // Prepare for WAL mode. If we can, we switch to journal_mode=WAL, then - // set the checkpoint size appropriately. If we can't, then we fall back - // to truncating and synchronous writes. - final Cursor cursor = bridge.internalQuery("PRAGMA journal_mode=WAL", null); - try { - if (cursor.moveToFirst()) { - String journalMode = cursor.getString(0); - Log.d(LOGTAG, "Journal mode: " + journalMode); - if ("wal".equals(journalMode)) { - // Success! Let's make sure we autocheckpoint at a reasonable interval. - final int pageSizeBytes = bridge.getPageSizeBytes(); - final int checkpointPageCount = MAX_WAL_SIZE_BYTES / pageSizeBytes; - bridge.execSQL("PRAGMA wal_autocheckpoint=" + checkpointPageCount); - } else { - if (!"truncate".equals(journalMode)) { - Log.w(LOGTAG, "Unable to activate WAL journal mode. Using truncate instead."); - bridge.execSQL("PRAGMA journal_mode=TRUNCATE"); - } - Log.w(LOGTAG, "Not using WAL mode: using synchronous=FULL instead."); - bridge.execSQL("PRAGMA synchronous=FULL"); - } - } - } finally { - cursor.close(); - } - } - - private int getPageSizeBytes() { - if (!isOpen()) { - throw new IllegalStateException("Database not open."); - } - - final Cursor cursor = internalQuery("PRAGMA page_size", null); - try { - if (!cursor.moveToFirst()) { - Log.w(LOGTAG, "Unable to retrieve page size."); - return DEFAULT_PAGE_SIZE_BYTES; - } - - return cursor.getInt(0); - } finally { - cursor.close(); - } - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/sqlite/SQLiteBridgeException.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/sqlite/SQLiteBridgeException.java deleted file mode 100644 index c7999fc5c..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/sqlite/SQLiteBridgeException.java +++ /dev/null @@ -1,18 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.sqlite; - -import org.mozilla.gecko.annotation.JNITarget; - -@JNITarget -public class SQLiteBridgeException extends RuntimeException { - static final long serialVersionUID = 1L; - - public SQLiteBridgeException() {} - public SQLiteBridgeException(String msg) { - super(msg); - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ActivityResultHandler.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ActivityResultHandler.java deleted file mode 100644 index 6f40ee96b..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ActivityResultHandler.java +++ /dev/null @@ -1,11 +0,0 @@ -/* 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.util; - -import android.content.Intent; - -public interface ActivityResultHandler { - void onActivityResult(int resultCode, Intent data); -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ActivityResultHandlerMap.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ActivityResultHandlerMap.java deleted file mode 100644 index dc1d26cec..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ActivityResultHandlerMap.java +++ /dev/null @@ -1,24 +0,0 @@ -/* 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.util; - -import android.util.SparseArray; - -public final class ActivityResultHandlerMap { - private final SparseArray<ActivityResultHandler> mMap = new SparseArray<ActivityResultHandler>(); - private int mCounter; - - public synchronized int put(ActivityResultHandler handler) { - mMap.put(mCounter, handler); - return mCounter++; - } - - public synchronized ActivityResultHandler getAndRemove(int i) { - ActivityResultHandler handler = mMap.get(i); - mMap.delete(i); - - return handler; - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ActivityUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ActivityUtils.java deleted file mode 100644 index 2f15e7868..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ActivityUtils.java +++ /dev/null @@ -1,72 +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.util; - -import android.app.Activity; -import android.content.Intent; -import android.view.View; -import android.view.Window; -import android.view.WindowManager; - -import org.mozilla.gecko.AppConstants.Versions; - -public class ActivityUtils { - private ActivityUtils() { - } - - public static void setFullScreen(Activity activity, boolean fullscreen) { - // Hide/show the system notification bar - Window window = activity.getWindow(); - - if (Versions.feature16Plus) { - int newVis; - if (fullscreen) { - newVis = View.SYSTEM_UI_FLAG_FULLSCREEN; - if (Versions.feature19Plus) { - newVis |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_LAYOUT_STABLE | - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; - } else { - newVis |= View.SYSTEM_UI_FLAG_LOW_PROFILE; - } - } else { - newVis = View.SYSTEM_UI_FLAG_VISIBLE; - } - - window.getDecorView().setSystemUiVisibility(newVis); - } else { - window.setFlags(fullscreen ? - WindowManager.LayoutParams.FLAG_FULLSCREEN : 0, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - } - } - - public static boolean isFullScreen(final Activity activity) { - final Window window = activity.getWindow(); - - if (Versions.feature16Plus) { - final int vis = window.getDecorView().getSystemUiVisibility(); - return (vis & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0; - } - - final int flags = window.getAttributes().flags; - return ((flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0); - } - - /** - * Finish this activity and launch the default home screen activity. - */ - public static void goToHomeScreen(Activity activity) { - Intent intent = new Intent(Intent.ACTION_MAIN); - - intent.addCategory(Intent.CATEGORY_HOME); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - activity.startActivity(intent); - - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/BundleEventListener.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/BundleEventListener.java deleted file mode 100644 index 9e9bb5a9e..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/BundleEventListener.java +++ /dev/null @@ -1,25 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.util; - -import org.mozilla.gecko.annotation.RobocopTarget; - -import android.os.Bundle; - -@RobocopTarget -public interface BundleEventListener { - /** - * Handles a message sent from Gecko. - * - * @param event The name of the event being sent. - * @param message The message data. - * @param callback The callback interface for this message. A callback is provided only if the - * originating Messaging.sendRequest call included a callback argument; - * otherwise, callback will be null. All listeners for a given event are given - * the same callback object, and exactly one listener must handle the callback. - */ - void handleMessage(String event, Bundle message, EventCallback callback); -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/Clipboard.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/Clipboard.java deleted file mode 100644 index 02b07674f..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/Clipboard.java +++ /dev/null @@ -1,117 +0,0 @@ -/* 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.util; - -import java.util.concurrent.SynchronousQueue; - -import org.mozilla.gecko.annotation.WrapForJNI; -import org.mozilla.gecko.AppConstants.Versions; - -import android.content.ClipData; -import android.content.Context; -import android.util.Log; - -public final class Clipboard { - // Volatile but not synchronized: we don't care about the race condition in - // init, because both app contexts will be the same, but we do care about a - // thread having a stale null value of mContext. - volatile static Context mContext; - private final static String LOGTAG = "GeckoClipboard"; - private final static SynchronousQueue<String> sClipboardQueue = new SynchronousQueue<String>(); - - private Clipboard() { - } - - public static void init(final Context c) { - if (mContext != null) { - Log.w(LOGTAG, "Clipboard.init() called twice!"); - return; - } - mContext = c.getApplicationContext(); - } - - @WrapForJNI(calledFrom = "gecko") - public static String getText() { - // If we're on the UI thread or the background thread, we have a looper on the thread - // and can just call this directly. For any other threads, post the call to the - // background thread. - - if (ThreadUtils.isOnUiThread() || ThreadUtils.isOnBackgroundThread()) { - return getClipboardTextImpl(); - } - - ThreadUtils.postToBackgroundThread(new Runnable() { - @Override - public void run() { - String text = getClipboardTextImpl(); - try { - sClipboardQueue.put(text != null ? text : ""); - } catch (InterruptedException ie) { } - } - }); - - try { - return sClipboardQueue.take(); - } catch (InterruptedException ie) { - return ""; - } - } - - @WrapForJNI(calledFrom = "gecko") - public static void setText(final CharSequence text) { - ThreadUtils.postToBackgroundThread(new Runnable() { - @Override - public void run() { - // In API Level 11 and above, CLIPBOARD_SERVICE returns android.content.ClipboardManager, - // which is a subclass of android.text.ClipboardManager. - final android.content.ClipboardManager cm = (android.content.ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE); - final ClipData clip = ClipData.newPlainText("Text", text); - try { - cm.setPrimaryClip(clip); - } catch (NullPointerException e) { - // Bug 776223: This is a Samsung clipboard bug. setPrimaryClip() can throw - // a NullPointerException if Samsung's /data/clipboard directory is full. - // Fortunately, the text is still successfully copied to the clipboard. - } - return; - } - }); - } - - /** - * @return true if the clipboard is nonempty, false otherwise. - */ - @WrapForJNI(calledFrom = "gecko") - public static boolean hasText() { - android.content.ClipboardManager cm = (android.content.ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE); - return cm.hasPrimaryClip(); - } - - /** - * Deletes all text from the clipboard. - */ - @WrapForJNI(calledFrom = "gecko") - public static void clearText() { - setText(null); - } - - /** - * On some devices, access to the clipboard service needs to happen - * on a thread with a looper, so this function requires a looper is - * present on the thread. - */ - @SuppressWarnings("deprecation") - static String getClipboardTextImpl() { - android.content.ClipboardManager cm = (android.content.ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE); - if (cm.hasPrimaryClip()) { - ClipData clip = cm.getPrimaryClip(); - if (clip != null) { - ClipData.Item item = clip.getItemAt(0); - return item.coerceToText(mContext).toString(); - } - } - return null; - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ContextUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ContextUtils.java deleted file mode 100644 index 3a37911b0..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ContextUtils.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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.util; - -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.text.TextUtils; - -public class ContextUtils { - private static final String INSTALLER_GOOGLE_PLAY = "com.android.vending"; - - private ContextUtils() {} - - /** - * @return {@link android.content.pm.PackageInfo#firstInstallTime} for the context's package. - * @throws PackageManager.NameNotFoundException Unexpected - we get the package name from the context so - * it's expected to be found. - */ - public static PackageInfo getCurrentPackageInfo(final Context context) { - try { - return context.getPackageManager().getPackageInfo(context.getPackageName(), 0); - } catch (PackageManager.NameNotFoundException e) { - throw new AssertionError("Should not happen: Can't get package info of own package"); - } - } - - public static boolean isPackageInstalled(final Context context, String packageName) { - try { - PackageManager pm = context.getPackageManager(); - pm.getPackageInfo(packageName, 0); - return true; - } catch (PackageManager.NameNotFoundException e) { - return false; - } - } - - public static boolean isInstalledFromGooglePlay(final Context context) { - final String installerPackageName = context.getPackageManager().getInstallerPackageName(context.getPackageName()); - - if (TextUtils.isEmpty(installerPackageName)) { - return false; - } - - return INSTALLER_GOOGLE_PLAY.equals(installerPackageName); - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/DateUtil.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/DateUtil.java deleted file mode 100644 index 9d34a0fe8..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/DateUtil.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.util; - -import android.support.annotation.NonNull; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.Locale; -import java.util.TimeZone; -import java.util.concurrent.TimeUnit; - -/** - * Utilities to help with manipulating Java's dates and calendars. - */ -public class DateUtil { - private DateUtil() {} - - /** - * @param date the date to convert to HTTP format - * @return the date as specified in rfc 1123, e.g. "Tue, 01 Feb 2011 14:00:00 GMT" - */ - public static String getDateInHTTPFormat(@NonNull final Date date) { - final DateFormat df = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z", Locale.US); - df.setTimeZone(TimeZone.getTimeZone("GMT")); - return df.format(date); - } - - /** - * Returns the timezone offset for the current date in minutes. See - * {@link #getTimezoneOffsetInMinutesForGivenDate(Calendar)} for more details. - */ - public static int getTimezoneOffsetInMinutes(@NonNull final TimeZone timezone) { - return getTimezoneOffsetInMinutesForGivenDate(Calendar.getInstance(timezone)); - } - - /** - * Returns the time zone offset for the given date in minutes. The date makes a difference due to daylight - * savings time in some regions. We return minutes because we can accurately represent time zones that are - * offset by non-integer hour values, e.g. parts of New Zealand at UTC+12:45. - * - * @param calendar A calendar with the appropriate time zone & date already set. - */ - public static int getTimezoneOffsetInMinutesForGivenDate(@NonNull final Calendar calendar) { - // via Date.getTimezoneOffset deprecated docs (note: it had incorrect order of operations). - // Also, we cast to int because we should never overflow here - the max should be GMT+14 = 840. - return (int) TimeUnit.MILLISECONDS.toMinutes(calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET)); - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/EventCallback.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/EventCallback.java deleted file mode 100644 index 099542666..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/EventCallback.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.mozilla.gecko.util; - -import org.mozilla.gecko.annotation.RobocopTarget; - -/** - * Callback interface for Gecko requests. - * - * For each instance of EventCallback, exactly one of sendResponse, sendError, or sendCancel - * must be called to prevent observer leaks. If more than one send* method is called, or if a - * single send method is called multiple times, an {@link IllegalStateException} will be thrown. - */ -@RobocopTarget -public interface EventCallback { - /** - * Sends a success response with the given data. - * - * @param response The response data to send to Gecko. Can be any of the types accepted by - * JSONObject#put(String, Object). - */ - public void sendSuccess(Object response); - - /** - * Sends an error response with the given data. - * - * @param response The response data to send to Gecko. Can be any of the types accepted by - * JSONObject#put(String, Object). - */ - public void sendError(Object response); -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/FileUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/FileUtils.java deleted file mode 100644 index 01cdd42bb..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/FileUtils.java +++ /dev/null @@ -1,259 +0,0 @@ -/* 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.util; - -import android.util.Log; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.FilenameFilter; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.nio.charset.Charset; -import java.util.Comparator; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.json.JSONException; -import org.json.JSONObject; -import org.mozilla.gecko.annotation.RobocopTarget; - -public class FileUtils { - private static final String LOGTAG = "GeckoFileUtils"; - - /* - * A basic Filter for checking a filename and age. - **/ - static public class NameAndAgeFilter implements FilenameFilter { - final private String mName; - final private double mMaxAge; - - public NameAndAgeFilter(String name, double age) { - mName = name; - mMaxAge = age; - } - - @Override - public boolean accept(File dir, String filename) { - if (mName == null || mName.matches(filename)) { - File f = new File(dir, filename); - - if (mMaxAge < 0 || System.currentTimeMillis() - f.lastModified() > mMaxAge) { - return true; - } - } - - return false; - } - } - - @RobocopTarget - public static void delTree(File dir, FilenameFilter filter, boolean recurse) { - String[] files = null; - - if (filter != null) { - files = dir.list(filter); - } else { - files = dir.list(); - } - - if (files == null) { - return; - } - - for (String file : files) { - File f = new File(dir, file); - delete(f, recurse); - } - } - - public static boolean delete(File file) throws IOException { - return delete(file, true); - } - - public static boolean delete(File file, boolean recurse) { - if (file.isDirectory() && recurse) { - // If the quick delete failed and this is a dir, recursively delete the contents of the dir - String files[] = file.list(); - for (String temp : files) { - File fileDelete = new File(file, temp); - try { - delete(fileDelete); - } catch (IOException ex) { - Log.i(LOGTAG, "Error deleting " + fileDelete.getPath(), ex); - } - } - } - - // Even if this is a dir, it should now be empty and delete should work - return file.delete(); - } - - /** - * A generic solution to read a JSONObject from a file. See - * {@link #readStringFromFile(File)} for more details. - * - * @throws IOException if the file is empty, or another IOException occurs - * @throws JSONException if the file could not be converted to a JSONObject. - */ - public static JSONObject readJSONObjectFromFile(final File file) throws IOException, JSONException { - if (file.length() == 0) { - // Redirect this exception so it's clearer than when the JSON parser catches it. - throw new IOException("Given file is empty - the JSON parser cannot create an object from an empty file"); - } - return new JSONObject(readStringFromFile(file)); - } - - /** - * A generic solution to read from a file. For more details, - * see {@link #readStringFromInputStreamAndCloseStream(InputStream, int)}. - * - * This method loads the entire file into memory so will have the expected performance impact. - * If you're trying to read a large file, you should be handling your own reading to avoid - * out-of-memory errors. - */ - public static String readStringFromFile(final File file) throws IOException { - // FileInputStream will throw FileNotFoundException if the file does not exist, but - // File.length will return 0 if the file does not exist so we catch it sooner. - if (!file.exists()) { - throw new FileNotFoundException("Given file, " + file + ", does not exist"); - } else if (file.length() == 0) { - return ""; - } - final int len = (int) file.length(); // includes potential EOF character. - return readStringFromInputStreamAndCloseStream(new FileInputStream(file), len); - } - - /** - * A generic solution to read from an input stream in UTF-8. This function will read from the stream until it - * is finished and close the stream - this is necessary to close the wrapping resources. - * - * For a higher-level method, see {@link #readStringFromFile(File)}. - * - * Since this is generic, it may not be the most performant for your use case. - * - * @param bufferSize Size of the underlying buffer for read optimizations - must be > 0. - */ - public static String readStringFromInputStreamAndCloseStream(final InputStream inputStream, final int bufferSize) - throws IOException { - if (bufferSize <= 0) { - // Safe close: it's more important to alert the programmer of - // their error than to let them catch and continue on their way. - IOUtils.safeStreamClose(inputStream); - throw new IllegalArgumentException("Expected buffer size larger than 0. Got: " + bufferSize); - } - - final StringBuilder stringBuilder = new StringBuilder(bufferSize); - final InputStreamReader reader = new InputStreamReader(inputStream, Charset.forName("UTF-8")); - try { - int charsRead; - final char[] buffer = new char[bufferSize]; - while ((charsRead = reader.read(buffer, 0, bufferSize)) != -1) { - stringBuilder.append(buffer, 0, charsRead); - } - } finally { - reader.close(); - } - return stringBuilder.toString(); - } - - /** - * A generic solution to write a JSONObject to a file. - * See {@link #writeStringToFile(File, String)} for more details. - */ - public static void writeJSONObjectToFile(final File file, final JSONObject obj) throws IOException { - writeStringToFile(file, obj.toString()); - } - - /** - * A generic solution to write to a File - the given file will be overwritten. If it does not exist yet, it will - * be created. See {@link #writeStringToOutputStreamAndCloseStream(OutputStream, String)} for more details. - */ - public static void writeStringToFile(final File file, final String str) throws IOException { - writeStringToOutputStreamAndCloseStream(new FileOutputStream(file, false), str); - } - - /** - * A generic solution to write to an output stream in UTF-8. The stream will be closed at the - * completion of this method - it's necessary in order to close the wrapping resources. - * - * For a higher-level method, see {@link #writeStringToFile(File, String)}. - * - * Since this is generic, it may not be the most performant for your use case. - */ - public static void writeStringToOutputStreamAndCloseStream(final OutputStream outputStream, final String str) - throws IOException { - try { - final OutputStreamWriter writer = new OutputStreamWriter(outputStream, Charset.forName("UTF-8")); - try { - writer.write(str); - } finally { - writer.close(); - } - } finally { - // OutputStreamWriter.close can throw before closing the - // underlying stream. For safety, we close here too. - outputStream.close(); - } - } - - public static class FilenameWhitelistFilter implements FilenameFilter { - private final Set<String> mFilenameWhitelist; - - public FilenameWhitelistFilter(final Set<String> filenameWhitelist) { - mFilenameWhitelist = filenameWhitelist; - } - - @Override - public boolean accept(final File dir, final String filename) { - return mFilenameWhitelist.contains(filename); - } - } - - public static class FilenameRegexFilter implements FilenameFilter { - private final Pattern mPattern; - - // Each time `Pattern.matcher` is called, a new matcher is created. We can avoid the excessive object creation - // by caching the returned matcher and calling `Matcher.reset` on it. Since Matcher's are not thread safe, - // this assumes `FilenameFilter.accept` is not run in parallel (which, according to the source, it is not). - private Matcher mCachedMatcher; - - public FilenameRegexFilter(final Pattern pattern) { - mPattern = pattern; - } - - @Override - public boolean accept(final File dir, final String filename) { - if (mCachedMatcher == null) { - mCachedMatcher = mPattern.matcher(filename); - } else { - mCachedMatcher.reset(filename); - } - return mCachedMatcher.matches(); - } - } - - public static class FileLastModifiedComparator implements Comparator<File> { - @Override - public int compare(final File lhs, final File rhs) { - // Long.compare is API 19+. - final long lhsModified = lhs.lastModified(); - final long rhsModified = rhs.lastModified(); - if (lhsModified < rhsModified) { - return -1; - } else if (lhsModified == rhsModified) { - return 0; - } else { - return 1; - } - } - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/FloatUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/FloatUtils.java deleted file mode 100644 index fbcd7254f..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/FloatUtils.java +++ /dev/null @@ -1,43 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.util; - -import android.graphics.PointF; - -import java.lang.IllegalArgumentException; - -public final class FloatUtils { - private FloatUtils() {} - - public static boolean fuzzyEquals(float a, float b) { - return (Math.abs(a - b) < 1e-6); - } - - public static boolean fuzzyEquals(PointF a, PointF b) { - return fuzzyEquals(a.x, b.x) && fuzzyEquals(a.y, b.y); - } - - /* - * Returns the value that represents a linear transition between `from` and `to` at time `t`, - * which is on the scale [0, 1). Thus with t = 0.0f, this returns `from`; with t = 1.0f, this - * returns `to`; with t = 0.5f, this returns the value halfway from `from` to `to`. - */ - public static float interpolate(float from, float to, float t) { - return from + (to - from) * t; - } - - /** - * Returns 'value', clamped so that it isn't any lower than 'low', and it - * isn't any higher than 'high'. - */ - public static float clamp(float value, float low, float high) { - if (high < low) { - throw new IllegalArgumentException( - "clamp called with invalid parameters (" + high + " < " + low + ")" ); - } - return Math.max(low, Math.min(high, value)); - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/GamepadUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/GamepadUtils.java deleted file mode 100644 index e22be8fd8..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/GamepadUtils.java +++ /dev/null @@ -1,140 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.util; - -import android.annotation.TargetApi; -import android.os.Build; -import android.view.InputDevice; -import android.view.KeyCharacterMap; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.View; - -public final class GamepadUtils { - private static final int SONY_XPERIA_GAMEPAD_DEVICE_ID = 196611; - - private static View.OnKeyListener sClickDispatcher; - private static float sDeadZoneThresholdOverride = 1e-2f; - - private GamepadUtils() { - } - - @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) - private static boolean isGamepadKey(KeyEvent event) { - if (Build.VERSION.SDK_INT < 12) { - return false; - } - return (event.getSource() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD; - } - - public static boolean isActionKey(KeyEvent event) { - return (isGamepadKey(event) && (event.getKeyCode() == KeyEvent.KEYCODE_BUTTON_A)); - } - - public static boolean isActionKeyDown(KeyEvent event) { - return isActionKey(event) && event.getAction() == KeyEvent.ACTION_DOWN; - } - - public static boolean isBackKey(KeyEvent event) { - return (isGamepadKey(event) && (event.getKeyCode() == KeyEvent.KEYCODE_BUTTON_B)); - } - - public static void overrideDeadZoneThreshold(float threshold) { - sDeadZoneThresholdOverride = threshold; - } - - public static boolean isValueInDeadZone(MotionEvent event, int axis) { - float threshold; - if (sDeadZoneThresholdOverride >= 0) { - threshold = sDeadZoneThresholdOverride; - } else { - InputDevice.MotionRange range = event.getDevice().getMotionRange(axis); - threshold = range.getFlat() + range.getFuzz(); - } - float value = event.getAxisValue(axis); - return (Math.abs(value) < threshold); - } - - public static boolean isPanningControl(MotionEvent event) { - if (Build.VERSION.SDK_INT < 12) { - return false; - } - if ((event.getSource() & InputDevice.SOURCE_CLASS_MASK) != InputDevice.SOURCE_CLASS_JOYSTICK) { - return false; - } - if (isValueInDeadZone(event, MotionEvent.AXIS_X) - && isValueInDeadZone(event, MotionEvent.AXIS_Y) - && isValueInDeadZone(event, MotionEvent.AXIS_Z) - && isValueInDeadZone(event, MotionEvent.AXIS_RZ)) { - return false; - } - return true; - } - - public static View.OnKeyListener getClickDispatcher() { - if (sClickDispatcher == null) { - sClickDispatcher = new View.OnKeyListener() { - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - if (isActionKeyDown(event)) { - return v.performClick(); - } - return false; - } - }; - } - return sClickDispatcher; - } - - public static KeyEvent translateSonyXperiaGamepadKeys(int keyCode, KeyEvent event) { - // The cross and circle button mappings may be swapped in the different regions so - // determine if they are swapped so the proper key codes can be mapped to the keys - boolean areKeysSwapped = areSonyXperiaGamepadKeysSwapped(); - - // If a Sony Xperia, remap the cross and circle buttons to buttons - // A and B for the gamepad API - switch (keyCode) { - case KeyEvent.KEYCODE_BACK: - keyCode = (areKeysSwapped ? KeyEvent.KEYCODE_BUTTON_A : KeyEvent.KEYCODE_BUTTON_B); - break; - - case KeyEvent.KEYCODE_DPAD_CENTER: - keyCode = (areKeysSwapped ? KeyEvent.KEYCODE_BUTTON_B : KeyEvent.KEYCODE_BUTTON_A); - break; - - default: - return event; - } - - return new KeyEvent(event.getAction(), keyCode); - } - - public static boolean isSonyXperiaGamepadKeyEvent(KeyEvent event) { - return (event.getDeviceId() == SONY_XPERIA_GAMEPAD_DEVICE_ID && - "Sony Ericsson".equals(Build.MANUFACTURER) && - ("R800".equals(Build.MODEL) || "R800i".equals(Build.MODEL))); - } - - private static boolean areSonyXperiaGamepadKeysSwapped() { - // The cross and circle buttons on Sony Xperia phones are swapped - // in different regions - // http://developer.sonymobile.com/2011/02/13/xperia-play-game-keys/ - final char DEFAULT_O_BUTTON_LABEL = 0x25CB; - - boolean swapped = false; - int[] deviceIds = InputDevice.getDeviceIds(); - - for (int i = 0; deviceIds != null && i < deviceIds.length; i++) { - KeyCharacterMap keyCharacterMap = KeyCharacterMap.load(deviceIds[i]); - if (keyCharacterMap != null && DEFAULT_O_BUTTON_LABEL == - keyCharacterMap.getDisplayLabel(KeyEvent.KEYCODE_DPAD_CENTER)) { - swapped = true; - break; - } - } - return swapped; - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/GeckoBackgroundThread.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/GeckoBackgroundThread.java deleted file mode 100644 index 442f782e2..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/GeckoBackgroundThread.java +++ /dev/null @@ -1,76 +0,0 @@ -/* 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.util; - -import android.os.Handler; -import android.os.Looper; - -import java.util.concurrent.SynchronousQueue; - -final class GeckoBackgroundThread extends Thread { - private static final String LOOPER_NAME = "GeckoBackgroundThread"; - - // Guarded by 'GeckoBackgroundThread.class'. - private static Handler handler; - private static Thread thread; - - // The initial Runnable to run on the new thread. Its purpose - // is to avoid us having to wait for the new thread to start. - private Runnable initialRunnable; - - // Singleton, so private constructor. - private GeckoBackgroundThread(final Runnable initialRunnable) { - this.initialRunnable = initialRunnable; - } - - @Override - public void run() { - setName(LOOPER_NAME); - Looper.prepare(); - - synchronized (GeckoBackgroundThread.class) { - handler = new Handler(); - GeckoBackgroundThread.class.notify(); - } - - if (initialRunnable != null) { - initialRunnable.run(); - initialRunnable = null; - } - - Looper.loop(); - } - - private static void startThread(final Runnable initialRunnable) { - thread = new GeckoBackgroundThread(initialRunnable); - ThreadUtils.setBackgroundThread(thread); - - thread.setDaemon(true); - thread.start(); - } - - // Get a Handler for a looper thread, or create one if it doesn't yet exist. - /*package*/ static synchronized Handler getHandler() { - if (thread == null) { - startThread(null); - } - - while (handler == null) { - try { - GeckoBackgroundThread.class.wait(); - } catch (final InterruptedException e) { - } - } - return handler; - } - - /*package*/ static synchronized void post(final Runnable runnable) { - if (thread == null) { - startThread(runnable); - return; - } - getHandler().post(runnable); - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/GeckoEventListener.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/GeckoEventListener.java deleted file mode 100644 index 10336490b..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/GeckoEventListener.java +++ /dev/null @@ -1,14 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.util; - -import org.json.JSONObject; -import org.mozilla.gecko.annotation.RobocopTarget; - -@RobocopTarget -public interface GeckoEventListener { - void handleMessage(String event, JSONObject message); -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/GeckoJarReader.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/GeckoJarReader.java deleted file mode 100644 index 4e11592a4..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/GeckoJarReader.java +++ /dev/null @@ -1,261 +0,0 @@ -/* 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.util; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable; -import android.util.Log; -import org.mozilla.gecko.annotation.RobocopTarget; -import org.mozilla.gecko.AppConstants; -import org.mozilla.gecko.mozglue.GeckoLoader; -import org.mozilla.gecko.mozglue.NativeZip; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Stack; - -/* Reads out of a multiple level deep jar file such as - * jar:jar:file:///data/app/org.mozilla.fennec.apk!/omni.ja!/chrome/chrome/content/branding/favicon32.png - */ -public final class GeckoJarReader { - private static final String LOGTAG = "GeckoJarReader"; - - private GeckoJarReader() {} - - public static Bitmap getBitmap(Context context, Resources resources, String url) { - BitmapDrawable drawable = getBitmapDrawable(context, resources, url); - return (drawable != null) ? drawable.getBitmap() : null; - } - - public static BitmapDrawable getBitmapDrawable(Context context, Resources resources, - String url) { - Stack<String> jarUrls = parseUrl(url); - InputStream inputStream = null; - BitmapDrawable bitmap = null; - - NativeZip zip = null; - try { - // Load the initial jar file as a zip - zip = getZipFile(context, jarUrls.pop()); - inputStream = getStream(zip, jarUrls, url); - if (inputStream != null) { - bitmap = new BitmapDrawable(resources, inputStream); - // BitmapDrawable created from a stream does not set the correct target density from resources. - // In fact it discards the resources https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/graphics/java/android/graphics/drawable/BitmapDrawable.java#191 - bitmap.setTargetDensity(resources.getDisplayMetrics()); - } - } catch (IOException | URISyntaxException ex) { - Log.e(LOGTAG, "Exception ", ex); - } finally { - if (inputStream != null) { - try { - inputStream.close(); - } catch (IOException ex) { - Log.e(LOGTAG, "Error closing stream", ex); - } - } - } - - return bitmap; - } - - public static String getText(Context context, String url) { - Stack<String> jarUrls = parseUrl(url); - - NativeZip zip = null; - BufferedReader reader = null; - String text = null; - try { - zip = getZipFile(context, jarUrls.pop()); - InputStream input = getStream(zip, jarUrls, url); - if (input != null) { - reader = new BufferedReader(new InputStreamReader(input)); - text = reader.readLine(); - } - } catch (IOException | URISyntaxException ex) { - Log.e(LOGTAG, "Exception ", ex); - } finally { - if (reader != null) { - try { - reader.close(); - } catch (IOException ex) { - Log.e(LOGTAG, "Error closing reader", ex); - } - } - } - - return text; - } - - private static NativeZip getZipFile(Context context, String url) - throws IOException, URISyntaxException { - URI fileUrl = new URI(url); - GeckoLoader.loadMozGlue(context); - return new NativeZip(fileUrl.getPath()); - } - - @RobocopTarget - /** - * Extract a (possibly nested) file from an archive and write it to a temporary file. - * - * @param context Android context. - * @param url to open. Can include jar: to "reach into" nested archives. - * @param dir to write temporary file to. - * @return a <code>File</code>, if one could be written; otherwise null. - * @throws IOException if an error occured. - */ - public static File extractStream(Context context, String url, File dir, String suffix) throws IOException { - InputStream input = null; - try { - try { - final URI fileURI = new URI(url); - // We don't check the scheme because we want to catch bare files, not just file:// URIs. - // If we let bare files through, we'd try to open them as ZIP files later -- and crash in native code. - if (fileURI != null && fileURI.getPath() != null) { - final File inputFile = new File(fileURI.getPath()); - if (inputFile != null && inputFile.exists()) { - input = new FileInputStream(inputFile); - } - } - } catch (URISyntaxException e) { - // Not a file:// URI. - } - if (input == null) { - // No luck with file:// URI; maybe some other URI? - input = getStream(context, url); - } - if (input == null) { - // Not found! - return null; - } - - // n.b.: createTempFile does not in fact delete the file. - final File file = File.createTempFile("extractStream", suffix, dir); - OutputStream output = null; - try { - output = new FileOutputStream(file); - byte[] buf = new byte[8192]; - int len; - while ((len = input.read(buf)) >= 0) { - output.write(buf, 0, len); - } - return file; - } finally { - if (output != null) { - output.close(); - } - } - } finally { - if (input != null) { - try { - input.close(); - } catch (IOException e) { - Log.w(LOGTAG, "Got exception closing stream; ignoring.", e); - } - } - } - } - - @RobocopTarget - public static InputStream getStream(Context context, String url) { - Stack<String> jarUrls = parseUrl(url); - try { - NativeZip zip = getZipFile(context, jarUrls.pop()); - return getStream(zip, jarUrls, url); - } catch (Exception ex) { - // Some JNI code throws IllegalArgumentException on a bad file name; - // swallow the error and return null. We could also see legitimate - // IOExceptions here. - Log.e(LOGTAG, "Exception getting input stream from jar URL: " + url, ex); - return null; - } - } - - private static InputStream getStream(NativeZip zip, Stack<String> jarUrls, String origUrl) { - InputStream inputStream = null; - - // loop through children jar files until we reach the innermost one - while (!jarUrls.empty()) { - String fileName = jarUrls.pop(); - - if (inputStream != null) { - // intermediate NativeZips and InputStreams will be garbage collected. - try { - zip = new NativeZip(inputStream); - } catch (IllegalArgumentException e) { - String description = "!!! BUG 849589 !!! origUrl=" + origUrl; - Log.e(LOGTAG, description, e); - throw new IllegalArgumentException(description); - } - } - - inputStream = zip.getInputStream(fileName); - if (inputStream == null) { - Log.d(LOGTAG, "No Entry for " + fileName); - return null; - } - } - - return inputStream; - } - - /* Returns a stack of strings breaking the url up into pieces. Each piece - * is assumed to point to a jar file except for the final one. Callers should - * pass in the url to parse, and null for the parent parameter (used for recursion) - * For example, jar:jar:file:///data/app/org.mozilla.fennec.apk!/omni.ja!/chrome/chrome/content/branding/favicon32.png - * will return: - * file:///data/app/org.mozilla.fennec.apk - * omni.ja - * chrome/chrome/content/branding/favicon32.png - */ - private static Stack<String> parseUrl(String url) { - return parseUrl(url, null); - } - - private static Stack<String> parseUrl(String url, Stack<String> results) { - if (results == null) { - results = new Stack<String>(); - } - - if (url.startsWith("jar:")) { - int jarEnd = url.lastIndexOf("!"); - String subStr = url.substring(4, jarEnd); - results.push(url.substring(jarEnd + 2)); // remove the !/ characters - return parseUrl(subStr, results); - } else { - results.push(url); - return results; - } - } - - public static String getJarURL(Context context, String pathInsideJAR) { - // We need to encode the package resource path, because it might contain illegal characters. For example: - // /mnt/asec2/[2]org.mozilla.fennec-1/pkg.apk - // The round-trip through a URI does this for us. - final String resourcePath = context.getPackageResourcePath(); - return computeJarURI(resourcePath, pathInsideJAR); - } - - /** - * Encodes its resource path correctly. - */ - @RobocopTarget - public static String computeJarURI(String resourcePath, String pathInsideJAR) { - final String resURI = new File(resourcePath).toURI().toString(); - - // TODO: do we need to encode the file path, too? - return "jar:jar:" + resURI + "!/" + AppConstants.OMNIJAR_NAME + "!/" + pathInsideJAR; - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/GeckoRequest.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/GeckoRequest.java deleted file mode 100644 index a57ed7f08..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/GeckoRequest.java +++ /dev/null @@ -1,94 +0,0 @@ -/* 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.util; - -import java.util.concurrent.atomic.AtomicInteger; - -import org.json.JSONException; -import org.json.JSONObject; - -import org.mozilla.gecko.annotation.RobocopTarget; - -import android.util.Log; - -public abstract class GeckoRequest { - private static final String LOGTAG = "GeckoRequest"; - private static final AtomicInteger currentId = new AtomicInteger(0); - - private final int id = currentId.getAndIncrement(); - private final String name; - private final String data; - - /** - * Creates a request that can be dispatched using - * {@link GeckoAppShell#sendRequestToGecko(GeckoRequest)}. - * - * @param name The name of the event associated with this request, which must have a - * Gecko-side listener registered to respond to this request. - * @param data Data to send with this request, which can be any object serializable by - * {@link JSONObject#put(String, Object)}. - */ - @RobocopTarget - public GeckoRequest(String name, Object data) { - this.name = name; - final JSONObject message = new JSONObject(); - try { - message.put("id", id); - message.put("data", data); - } catch (JSONException e) { - Log.e(LOGTAG, "JSON error", e); - } - this.data = message.toString(); - } - - /** - * Gets the ID for this request. - * - * @return The request ID - */ - public int getId() { - return id; - } - - /** - * Gets the event name associated with this request. - * - * @return The name of the event sent to Gecko - */ - public String getName() { - return name; - } - - /** - * Gets the stringified data associated with this request. - * - * @return The data being sent with the request - */ - public String getData() { - return data; - } - - /** - * Callback executed when the request succeeds. - * - * @param nativeJSObject The response data from Gecko - */ - @RobocopTarget - public abstract void onResponse(NativeJSObject nativeJSObject); - - /** - * Callback executed when the request fails. - * - * By default, an exception is thrown. This should be overridden if the - * GeckoRequest is able to recover from the error. - * - * @throws RuntimeException - */ - @RobocopTarget - public void onError(NativeJSObject error) { - final String message = error.optString("message", "<no message>"); - final String stack = error.optString("stack", "<no stack>"); - throw new RuntimeException("Unhandled error for GeckoRequest " + name + ": " + message + "\nJS stack:\n" + stack); - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/HardwareCodecCapabilityUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/HardwareCodecCapabilityUtils.java deleted file mode 100644 index 864462d9b..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/HardwareCodecCapabilityUtils.java +++ /dev/null @@ -1,169 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.util; - -import org.mozilla.gecko.annotation.WrapForJNI; -import org.mozilla.gecko.AppConstants.Versions; - -import android.media.MediaCodecInfo; -import android.media.MediaCodecInfo.CodecCapabilities; -import android.media.MediaCodecList; -import android.util.Log; - -public final class HardwareCodecCapabilityUtils { - private static final String LOGTAG = "GeckoHardwareCodecCapabilityUtils"; - - // List of supported HW VP8 encoders. - private static final String[] supportedVp8HwEncCodecPrefixes = - {"OMX.qcom.", "OMX.Intel." }; - // List of supported HW VP8 decoders. - private static final String[] supportedVp8HwDecCodecPrefixes = - {"OMX.qcom.", "OMX.Nvidia.", "OMX.Exynos.", "OMX.Intel." }; - private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8"; - private static final String VP9_MIME_TYPE = "video/x-vnd.on2.vp9"; - // NV12 color format supported by QCOM codec, but not declared in MediaCodec - - // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h - private static final int - COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04; - // Allowable color formats supported by codec - in order of preference. - private static final int[] supportedColorList = { - CodecCapabilities.COLOR_FormatYUV420Planar, - CodecCapabilities.COLOR_FormatYUV420SemiPlanar, - CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar, - COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m - }; - - @WrapForJNI - public static boolean findDecoderCodecInfoForMimeType(String aMimeType) { - for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) { - MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i); - if (info.isEncoder()) { - continue; - } - for (String mimeType : info.getSupportedTypes()) { - if (mimeType.equals(aMimeType)) { - return true; - } - } - } - return false; - } - - public static boolean getHWEncoderCapability() { - if (Versions.feature20Plus) { - for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) { - MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i); - if (!info.isEncoder()) { - continue; - } - String name = null; - for (String mimeType : info.getSupportedTypes()) { - if (mimeType.equals(VP8_MIME_TYPE)) { - name = info.getName(); - break; - } - } - if (name == null) { - continue; // No HW support in this codec; try the next one. - } - Log.e(LOGTAG, "Found candidate encoder " + name); - - // Check if this is supported encoder. - boolean supportedCodec = false; - for (String codecPrefix : supportedVp8HwEncCodecPrefixes) { - if (name.startsWith(codecPrefix)) { - supportedCodec = true; - break; - } - } - if (!supportedCodec) { - continue; - } - - // Check if codec supports either yuv420 or nv12. - CodecCapabilities capabilities = - info.getCapabilitiesForType(VP8_MIME_TYPE); - for (int colorFormat : capabilities.colorFormats) { - Log.v(LOGTAG, " Color: 0x" + Integer.toHexString(colorFormat)); - } - for (int supportedColorFormat : supportedColorList) { - for (int codecColorFormat : capabilities.colorFormats) { - if (codecColorFormat == supportedColorFormat) { - // Found supported HW Encoder. - Log.e(LOGTAG, "Found target encoder " + name + - ". Color: 0x" + Integer.toHexString(codecColorFormat)); - return true; - } - } - } - } - } - // No HW encoder. - return false; - } - - public static boolean getHWDecoderCapability() { - return getHWDecoderCapability(VP8_MIME_TYPE); - } - - @WrapForJNI - public static boolean HasHWVP9() { - return getHWDecoderCapability(VP9_MIME_TYPE); - } - - public static boolean getHWDecoderCapability(String aMimeType) { - if (Versions.feature20Plus) { - for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) { - MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i); - if (info.isEncoder()) { - continue; - } - String name = null; - for (String mimeType : info.getSupportedTypes()) { - if (mimeType.equals(aMimeType)) { - name = info.getName(); - break; - } - } - if (name == null) { - continue; // No HW support in this codec; try the next one. - } - Log.e(LOGTAG, "Found candidate decoder " + name); - - // Check if this is supported decoder. - boolean supportedCodec = false; - for (String codecPrefix : supportedVp8HwDecCodecPrefixes) { - if (name.startsWith(codecPrefix)) { - supportedCodec = true; - break; - } - } - if (!supportedCodec) { - continue; - } - - // Check if codec supports either yuv420 or nv12. - CodecCapabilities capabilities = - info.getCapabilitiesForType(aMimeType); - for (int colorFormat : capabilities.colorFormats) { - Log.v(LOGTAG, " Color: 0x" + Integer.toHexString(colorFormat)); - } - for (int supportedColorFormat : supportedColorList) { - for (int codecColorFormat : capabilities.colorFormats) { - if (codecColorFormat == supportedColorFormat) { - // Found supported HW decoder. - Log.e(LOGTAG, "Found target decoder " + name + - ". Color: 0x" + Integer.toHexString(codecColorFormat)); - return true; - } - } - } - } - } - return false; // No HW decoder. - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/HardwareUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/HardwareUtils.java deleted file mode 100644 index ba92d08cb..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/HardwareUtils.java +++ /dev/null @@ -1,117 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.util; - -import org.mozilla.gecko.AppConstants; -import org.mozilla.gecko.SysInfo; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.res.Configuration; -import android.os.Build; -import android.util.Log; -import android.view.ViewConfiguration; - -public final class HardwareUtils { - private static final String LOGTAG = "GeckoHardwareUtils"; - - private static final boolean IS_AMAZON_DEVICE = Build.MANUFACTURER.equalsIgnoreCase("Amazon"); - public static final boolean IS_KINDLE_DEVICE = IS_AMAZON_DEVICE && - (Build.MODEL.equals("Kindle Fire") || - Build.MODEL.startsWith("KF")); - - private static volatile boolean sInited; - - // These are all set once, during init. - private static volatile boolean sIsLargeTablet; - private static volatile boolean sIsSmallTablet; - private static volatile boolean sIsTelevision; - - private HardwareUtils() { - } - - public static void init(Context context) { - if (sInited) { - // This is unavoidable, given that HardwareUtils is called from background services. - Log.d(LOGTAG, "HardwareUtils already inited."); - return; - } - - // Pre-populate common flags from the context. - final int screenLayoutSize = context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK; - if (Build.VERSION.SDK_INT >= 11) { - if (screenLayoutSize == Configuration.SCREENLAYOUT_SIZE_XLARGE) { - sIsLargeTablet = true; - } else if (screenLayoutSize == Configuration.SCREENLAYOUT_SIZE_LARGE) { - sIsSmallTablet = true; - } - if (Build.VERSION.SDK_INT >= 16) { - if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION)) { - sIsTelevision = true; - } - } - } - - sInited = true; - } - - public static boolean isTablet() { - return sIsLargeTablet || sIsSmallTablet; - } - - public static boolean isLargeTablet() { - return sIsLargeTablet; - } - - public static boolean isSmallTablet() { - return sIsSmallTablet; - } - - public static boolean isTelevision() { - return sIsTelevision; - } - - public static int getMemSize() { - return SysInfo.getMemSize(); - } - - public static boolean isARMSystem() { - return Build.CPU_ABI != null && Build.CPU_ABI.equals("armeabi-v7a"); - } - - public static boolean isX86System() { - return Build.CPU_ABI != null && Build.CPU_ABI.equals("x86"); - } - - /** - * @return false if the current system is not supported (e.g. APK/system ABI mismatch). - */ - public static boolean isSupportedSystem() { - if (Build.VERSION.SDK_INT < AppConstants.Versions.MIN_SDK_VERSION || - Build.VERSION.SDK_INT > AppConstants.Versions.MAX_SDK_VERSION) { - return false; - } - - // See http://developer.android.com/ndk/guides/abis.html - final boolean isSystemARM = isARMSystem(); - final boolean isSystemX86 = isX86System(); - - final boolean isAppARM = AppConstants.ANDROID_CPU_ARCH.startsWith("armeabi-v7a"); - final boolean isAppX86 = AppConstants.ANDROID_CPU_ARCH.startsWith("x86"); - - // Only reject known incompatible ABIs. Better safe than sorry. - if ((isSystemX86 && isAppARM) || (isSystemARM && isAppX86)) { - return false; - } - - if ((isSystemX86 && isAppX86) || (isSystemARM && isAppARM)) { - return true; - } - - Log.w(LOGTAG, "Unknown app/system ABI combination: " + AppConstants.MOZ_APP_ABI + " / " + Build.CPU_ABI); - return true; - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/INIParser.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/INIParser.java deleted file mode 100644 index ed0706320..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/INIParser.java +++ /dev/null @@ -1,176 +0,0 @@ -/* 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.util; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.util.Enumeration; -import java.util.Hashtable; - -public final class INIParser extends INISection { - // default file to read and write to - private final File mFile; - - // List of sections in the current iniFile. null if the file has not been parsed yet - private Hashtable<String, INISection> mSections; - - // create a parser. The file will not be read until you attempt to - // access sections or properties inside it. At that point its read synchronously - public INIParser(File iniFile) { - super(""); - mFile = iniFile; - } - - // write ini data to the default file. Will overwrite anything current inside - public void write() { - writeTo(mFile); - } - - // write to the specified file. Will overwrite anything current inside - public void writeTo(File f) { - if (f == null) - return; - - FileWriter outputStream = null; - try { - outputStream = new FileWriter(f); - } catch (IOException e1) { - e1.printStackTrace(); - } - - BufferedWriter writer = new BufferedWriter(outputStream); - try { - write(writer); - writer.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - @Override - public void write(BufferedWriter writer) throws IOException { - super.write(writer); - - if (mSections != null) { - for (Enumeration<INISection> e = mSections.elements(); e.hasMoreElements();) { - INISection section = e.nextElement(); - section.write(writer); - writer.newLine(); - } - } - } - - // return all of the sections inside this file - public Hashtable<String, INISection> getSections() { - if (mSections == null) { - try { - parse(); - } catch (IOException e) { - debug("Error parsing: " + e); - } - } - return mSections; - } - - // parse the default file - @Override - protected void parse() throws IOException { - super.parse(); - parse(mFile); - } - - // parse a passed in file - private void parse(File f) throws IOException { - // Set up internal data members - mSections = new Hashtable<String, INISection>(); - - if (f == null || !f.exists()) - return; - - FileReader inputStream = null; - try { - inputStream = new FileReader(f); - } catch (FileNotFoundException e1) { - // If the file doesn't exist. Just return; - return; - } - - BufferedReader buf = new BufferedReader(inputStream); - String line = null; // current line of text we are parsing - INISection currentSection = null; // section we are currently parsing - - while ((line = buf.readLine()) != null) { - - if (line != null) - line = line.trim(); - - // blank line or a comment. ignore it - if (line == null || line.length() == 0 || line.charAt(0) == ';') { - debug("Ignore line: " + line); - } else if (line.charAt(0) == '[') { - debug("Parse as section: " + line); - currentSection = new INISection(line.substring(1, line.length() - 1)); - mSections.put(currentSection.getName(), currentSection); - } else { - debug("Parse as property: " + line); - - String[] pieces = line.split("="); - if (pieces.length != 2) - continue; - - String key = pieces[0].trim(); - String value = pieces[1].trim(); - if (currentSection != null) { - currentSection.setProperty(key, value); - } else { - mProperties.put(key, value); - } - } - } - buf.close(); - } - - // add a section to the file - public void addSection(INISection sect) { - // ensure that we have parsed the file - getSections(); - mSections.put(sect.getName(), sect); - } - - // get a section from the file. will return null if the section doesn't exist - public INISection getSection(String key) { - // ensure that we have parsed the file - getSections(); - return mSections.get(key); - } - - // remove an entire section from the file - public void removeSection(String name) { - // ensure that we have parsed the file - getSections(); - mSections.remove(name); - } - - // rename a section; nuking any previous section with the new - // name in the process - public void renameSection(String oldName, String newName) { - // ensure that we have parsed the file - getSections(); - - mSections.remove(newName); - INISection section = mSections.get(oldName); - if (section == null) - return; - - section.setName(newName); - mSections.remove(oldName); - mSections.put(newName, section); - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/INISection.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/INISection.java deleted file mode 100644 index af91ad410..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/INISection.java +++ /dev/null @@ -1,123 +0,0 @@ -/* 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.util; - -import android.text.TextUtils; -import android.util.Log; - -import java.io.BufferedWriter; -import java.io.IOException; -import java.util.Enumeration; -import java.util.Hashtable; - -public class INISection { - private static final String LOGTAG = "INIParser"; - - // default file to read and write to - private String mName; - public String getName() { return mName; } - public void setName(String name) { mName = name; } - - // show or hide debug logging - private boolean mDebug; - - // Global properties that aren't inside a section in the file - protected Hashtable<String, Object> mProperties; - - // create a parser. The file will not be read until you attempt to - // access sections or properties inside it. At that point its read synchronously - public INISection(String name) { - mName = name; - } - - // log a debug string to the console - protected void debug(String msg) { - if (mDebug) { - Log.i(LOGTAG, msg); - } - } - - // get a global property out of the hash table. will return null if the property doesn't exist - public Object getProperty(String key) { - getProperties(); // ensure that we have parsed the file - return mProperties.get(key); - } - - // get a global property out of the hash table. will return null if the property doesn't exist - public int getIntProperty(String key) { - Object val = getProperty(key); - if (val == null) - return -1; - - return Integer.parseInt(val.toString()); - } - - // get a global property out of the hash table. will return null if the property doesn't exist - public String getStringProperty(String key) { - Object val = getProperty(key); - if (val == null) - return null; - - return val.toString(); - } - - // get a hashtable of all the global properties in this file - public Hashtable<String, Object> getProperties() { - if (mProperties == null) { - try { - parse(); - } catch (IOException e) { - debug("Error parsing: " + e); - } - } - return mProperties; - } - - // do nothing for generic sections - protected void parse() throws IOException { - mProperties = new Hashtable<String, Object>(); - } - - // set a property. Will erase the property if value = null - public void setProperty(String key, Object value) { - getProperties(); // ensure that we have parsed the file - if (value == null) - removeProperty(key); - else - mProperties.put(key.trim(), value); - } - - // remove a property - public void removeProperty(String name) { - // ensure that we have parsed the file - getProperties(); - mProperties.remove(name); - } - - public void write(BufferedWriter writer) throws IOException { - if (!TextUtils.isEmpty(mName)) { - writer.write("[" + mName + "]"); - writer.newLine(); - } - - if (mProperties != null) { - for (Enumeration<String> e = mProperties.keys(); e.hasMoreElements();) { - String key = e.nextElement(); - writeProperty(writer, key, mProperties.get(key)); - } - } - writer.newLine(); - } - - // Helper function to write out a property - private void writeProperty(BufferedWriter writer, String key, Object value) { - try { - writer.write(key + "=" + value); - writer.newLine(); - } catch (IOException e) { - e.printStackTrace(); - } - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/IOUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/IOUtils.java deleted file mode 100644 index 62eee5192..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/IOUtils.java +++ /dev/null @@ -1,129 +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.util; - -import android.util.Log; - -import java.io.Closeable; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * Static helper class containing useful methods for manipulating IO objects. - */ -public class IOUtils { - private static final String LOGTAG = "GeckoIOUtils"; - - /** - * Represents the result of consuming an input stream, holding the returned data as well - * as the length of the data returned. - * The byte[] is not guaranteed to be trimmed to the size of the data acquired from the stream: - * hence the need for the length field. This strategy avoids the need to copy the data into a - * trimmed buffer after consumption. - */ - public static class ConsumedInputStream { - public final int consumedLength; - // Only reassigned in getTruncatedData. - private byte[] consumedData; - - public ConsumedInputStream(int consumedLength, byte[] consumedData) { - this.consumedLength = consumedLength; - this.consumedData = consumedData; - } - - /** - * Get the data trimmed to the length of the actual payload read, caching the result. - */ - public byte[] getTruncatedData() { - if (consumedData.length == consumedLength) { - return consumedData; - } - - consumedData = truncateBytes(consumedData, consumedLength); - return consumedData; - } - - public byte[] getData() { - return consumedData; - } - } - - /** - * Fully read an InputStream into a byte array. - * @param iStream the InputStream to consume. - * @param bufferSize The initial size of the buffer to allocate. It will be grown as - * needed, but if the caller knows something about the InputStream then - * passing a good value here can improve performance. - */ - public static ConsumedInputStream readFully(InputStream iStream, int bufferSize) { - // Allocate a buffer to hold the raw data downloaded. - byte[] buffer = new byte[bufferSize]; - - // The offset of the start of the buffer's free space. - int bPointer = 0; - - // The quantity of bytes the last call to read yielded. - int lastRead = 0; - try { - // Fully read the data into the buffer. - while (lastRead != -1) { - // Read as many bytes as are currently available into the buffer. - lastRead = iStream.read(buffer, bPointer, buffer.length - bPointer); - bPointer += lastRead; - - // If buffer has overflowed, double its size and carry on. - if (bPointer == buffer.length) { - bufferSize *= 2; - byte[] newBuffer = new byte[bufferSize]; - - // Copy the contents of the old buffer into the new buffer. - System.arraycopy(buffer, 0, newBuffer, 0, buffer.length); - buffer = newBuffer; - } - } - - return new ConsumedInputStream(bPointer + 1, buffer); - } catch (IOException e) { - Log.e(LOGTAG, "Error consuming input stream.", e); - } finally { - try { - iStream.close(); - } catch (IOException e) { - Log.e(LOGTAG, "Error closing input stream.", e); - } - } - - return null; - } - - /** - * Truncate a given byte[] to a given length. Returns a new byte[] with the first length many - * bytes of the input. - */ - public static byte[] truncateBytes(byte[] bytes, int length) { - byte[] newBytes = new byte[length]; - System.arraycopy(bytes, 0, newBytes, 0, length); - - return newBytes; - } - - public static void safeStreamClose(Closeable stream) { - try { - if (stream != null) - stream.close(); - } catch (IOException e) { } - } - - public static void copy(InputStream in, OutputStream out) throws IOException { - byte[] buffer = new byte[4096]; - int len; - - while ((len = in.read(buffer)) != -1) { - out.write(buffer, 0, len); - } - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/InputOptionsUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/InputOptionsUtils.java deleted file mode 100644 index 55c02e4da..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/InputOptionsUtils.java +++ /dev/null @@ -1,45 +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.util; - -import android.content.Context; -import android.content.Intent; -import android.speech.RecognizerIntent; - -public class InputOptionsUtils { - public static boolean supportsVoiceRecognizer(Context context, String prompt) { - final Intent intent = createVoiceRecognizerIntent(prompt); - return intent.resolveActivity(context.getPackageManager()) != null; - } - - public static Intent createVoiceRecognizerIntent(String prompt) { - final Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); - intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH); - intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1); - intent.putExtra(RecognizerIntent.EXTRA_PROMPT, prompt); - return intent; - } - - public static boolean supportsIntent(Intent intent, Context context) { - return intent.resolveActivity(context.getPackageManager()) != null; - } - - public static boolean supportsQrCodeReader(Context context) { - final Intent intent = createQRCodeReaderIntent(); - return supportsIntent(intent, context); - } - - public static Intent createQRCodeReaderIntent() { - // Bug 602818 enables QR code input if you have the particular app below installed in your device - final String appPackage = "com.google.zxing.client.android"; - - Intent intent = new Intent(appPackage + ".SCAN"); - intent.setPackage(appPackage); - intent.putExtra("SCAN_MODE", "QR_CODE_MODE"); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - return intent; - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/IntentUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/IntentUtils.java deleted file mode 100644 index d4fe297da..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/IntentUtils.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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.util; - -import android.content.Intent; -import android.os.Bundle; -import android.support.annotation.CheckResult; -import android.support.annotation.NonNull; -import android.text.TextUtils; - -import org.mozilla.gecko.mozglue.SafeIntent; - -import java.util.HashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Utilities for Intents. - */ -public class IntentUtils { - public static final String ENV_VAR_IN_AUTOMATION = "MOZ_IN_AUTOMATION"; - - private static final String ENV_VAR_REGEX = "(.+)=(.*)"; - - private IntentUtils() {} - - /** - * Returns a list of environment variables and their values. These are parsed from an Intent extra - * with the key -> value format: - * env# -> ENV_VAR=VALUE - * - * # in env# is expected to be increasing from 0. - * - * @return A Map of environment variable name to value, e.g. ENV_VAR -> VALUE - */ - public static HashMap<String, String> getEnvVarMap(@NonNull final SafeIntent intent) { - // Optimization: get matcher for re-use. Pattern.matcher creates a new object every time so it'd be great - // to avoid the unnecessary allocation, particularly because we expect to be called on the startup path. - final Pattern envVarPattern = Pattern.compile(ENV_VAR_REGEX); - final Matcher matcher = envVarPattern.matcher(""); // argument does not matter here. - - // This is expected to be an external intent so we should use SafeIntent to prevent crashing. - final HashMap<String, String> out = new HashMap<>(); - int i = 0; - while (true) { - final String envKey = "env" + i; - i += 1; - if (!intent.hasExtra(envKey)) { - break; - } - - maybeAddEnvVarToEnvVarMap(out, intent, envKey, matcher); - } - return out; - } - - /** - * @param envVarMap the map to add the env var to - * @param intent the intent from which to extract the env var - * @param envKey the key at which the env var resides - * @param envVarMatcher a matcher initialized with the env var pattern to extract - */ - private static void maybeAddEnvVarToEnvVarMap(@NonNull final HashMap<String, String> envVarMap, - @NonNull final SafeIntent intent, @NonNull final String envKey, @NonNull final Matcher envVarMatcher) { - final String envValue = intent.getStringExtra(envKey); - if (envValue == null) { - return; // nothing to do here! - } - - envVarMatcher.reset(envValue); - if (envVarMatcher.matches()) { - final String envVarName = envVarMatcher.group(1); - final String envVarValue = envVarMatcher.group(2); - envVarMap.put(envVarName, envVarValue); - } - } - - public static Bundle getBundleExtraSafe(final Intent intent, final String name) { - return new SafeIntent(intent).getBundleExtra(name); - } - - public static String getStringExtraSafe(final Intent intent, final String name) { - return new SafeIntent(intent).getStringExtra(name); - } - - public static boolean getBooleanExtraSafe(final Intent intent, final String name, final boolean defaultValue) { - return new SafeIntent(intent).getBooleanExtra(name, defaultValue); - } - - /** - * Gets whether or not we're in automation from the passed in environment variables. - * - * We need to read environment variables from the intent string - * extra because environment variables from our test harness aren't set - * until Gecko is loaded, and we need to know this before then. - * - * The return value of this method should be used early since other - * initialization may depend on its results. - */ - @CheckResult - public static boolean getIsInAutomationFromEnvironment(final SafeIntent intent) { - final HashMap<String, String> envVars = IntentUtils.getEnvVarMap(intent); - return !TextUtils.isEmpty(envVars.get(IntentUtils.ENV_VAR_IN_AUTOMATION)); - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/JSONUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/JSONUtils.java deleted file mode 100644 index 4ec98ec9e..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/JSONUtils.java +++ /dev/null @@ -1,69 +0,0 @@ -/* 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.util; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import android.os.Bundle; -import android.util.Log; - -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; - -public final class JSONUtils { - private static final String LOGTAG = "GeckoJSONUtils"; - - private JSONUtils() {} - - public static UUID getUUID(String name, JSONObject json) { - String uuid = json.optString(name, null); - return (uuid != null) ? UUID.fromString(uuid) : null; - } - - public static void putUUID(String name, UUID uuid, JSONObject json) { - String uuidString = uuid.toString(); - try { - json.put(name, uuidString); - } catch (JSONException e) { - throw new IllegalArgumentException(name + "=" + uuidString, e); - } - } - - public static JSONObject bundleToJSON(Bundle bundle) { - if (bundle == null || bundle.isEmpty()) { - return null; - } - - JSONObject json = new JSONObject(); - for (String key : bundle.keySet()) { - try { - json.put(key, bundle.get(key)); - } catch (JSONException e) { - Log.w(LOGTAG, "Error building JSON response.", e); - } - } - - return json; - } - - // Handles conversions between a JSONArray and a Set<String> - public static Set<String> parseStringSet(JSONArray json) { - final Set<String> ret = new HashSet<String>(); - - for (int i = 0; i < json.length(); i++) { - try { - ret.add(json.getString(i)); - } catch (JSONException ex) { - Log.i(LOGTAG, "Error parsing json", ex); - } - } - - return ret; - } - -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/MenuUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/MenuUtils.java deleted file mode 100644 index e44fdd541..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/MenuUtils.java +++ /dev/null @@ -1,33 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.util; - -import android.view.Menu; -import android.view.MenuItem; - -public class MenuUtils { - /* - * This method looks for a menuitem and sets it's visible state, if - * it exists. - */ - public static void safeSetVisible(Menu menu, int id, boolean visible) { - MenuItem item = menu.findItem(id); - if (item != null) { - item.setVisible(visible); - } - } - - /* - * This method looks for a menuitem and sets it's enabled state, if - * it exists. - */ - public static void safeSetEnabled(Menu menu, int id, boolean enabled) { - MenuItem item = menu.findItem(id); - if (item != null) { - item.setEnabled(enabled); - } - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/NativeEventListener.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/NativeEventListener.java deleted file mode 100644 index 2a1b6e89a..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/NativeEventListener.java +++ /dev/null @@ -1,23 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.util; - -import org.mozilla.gecko.annotation.RobocopTarget; - -@RobocopTarget -public interface NativeEventListener { - /** - * Handles a message sent from Gecko. - * - * @param event The name of the event being sent. - * @param message The message data. - * @param callback The callback interface for this message. A callback is provided only if the - * originating Messaging.sendRequest call included a callback argument; otherwise, - * callback will be null. All listeners for a given event are given the same - * callback object, and exactly one listener must handle the callback. - */ - void handleMessage(String event, NativeJSObject message, EventCallback callback); -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/NativeJSContainer.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/NativeJSContainer.java deleted file mode 100644 index daefe6de0..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/NativeJSContainer.java +++ /dev/null @@ -1,37 +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.util; - -import org.mozilla.gecko.annotation.WrapForJNI; - -/** - * NativeJSContainer is a wrapper around the SpiderMonkey JSAPI to make it possible to - * access Javascript objects in Java. - * - * A container must only be used on the thread it is attached to. To use it on another - * thread, call {@link #clone()} to make a copy, and use the copy on the other thread. - * When a copy is first used, it becomes attached to the thread using it. - */ -@WrapForJNI(calledFrom = "gecko") -public final class NativeJSContainer extends NativeJSObject -{ - private NativeJSContainer() { - } - - /** - * Make a copy of this container for use by another thread. When the copy is first used, - * it becomes attached to the thread using it. - */ - @Override - public native NativeJSContainer clone(); - - /** - * Dispose all associated native objects. Subsequent use of any objects derived from - * this container will throw a NullPointerException. - */ - @Override - public native void disposeNative(); -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/NativeJSObject.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/NativeJSObject.java deleted file mode 100644 index 0d1f0a037..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/NativeJSObject.java +++ /dev/null @@ -1,533 +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.util; - -import org.mozilla.gecko.annotation.JNITarget; -import org.mozilla.gecko.annotation.WrapForJNI; -import org.mozilla.gecko.mozglue.JNIObject; - -import android.os.Bundle; - -/** - * NativeJSObject is a wrapper around the SpiderMonkey JSAPI to make it possible to - * access Javascript objects in Java. - */ -@WrapForJNI(calledFrom = "gecko") -public class NativeJSObject extends JNIObject -{ - @SuppressWarnings("serial") - @JNITarget - public static final class InvalidPropertyException extends RuntimeException { - public InvalidPropertyException(final String msg) { - super(msg); - } - } - - protected NativeJSObject() { - } - - @Override - protected void disposeNative() { - // NativeJSObject is disposed as part of NativeJSContainer disposal. - } - - /** - * Returns the value of a boolean property. - * - * @param name - * Property name - * @throws IllegalArgumentException - * If name is null - * @throws InvalidPropertyException - * If the property does not exist or if its type does not match the return type - * @throws NullPointerException - * If this JS object has been disposed - * @throws IllegalThreadStateException - * If not called on the thread this object is attached to - * @throws UnsupportedOperationException - * If an internal JSAPI call failed - */ - public native boolean getBoolean(String name); - - /** - * Returns the value of a boolean property. - * - * @param name - * Property name - * @param fallback - * Value to return if property does not exist - * @throws IllegalArgumentException - * If name is null - * @throws InvalidPropertyException - * If the property exists and its type does not match the return type - * @throws NullPointerException - * If this JS object has been disposed - * @throws IllegalThreadStateException - * If not called on the thread this object is attached to - * @throws UnsupportedOperationException - * If an internal JSAPI call failed - */ - public native boolean optBoolean(String name, boolean fallback); - - /** - * Returns the value of a boolean array property. - * - * @param name - * Property name - * @throws IllegalArgumentException - * If name is null - * @throws InvalidPropertyException - * If the property does not exist or if its type does not match the return type - * @throws NullPointerException - * If this JS object has been disposed - * @throws IllegalThreadStateException - * If not called on the thread this object is attached to - * @throws UnsupportedOperationException - * If an internal JSAPI call failed - */ - public native boolean[] getBooleanArray(String name); - - /** - * Returns the value of a boolean array property. - * - * @param name - * Property name - * @param fallback - * Value to return if property does not exist - * @throws IllegalArgumentException - * If name is null - * @throws InvalidPropertyException - * If the property exists and its type does not match the return type - * @throws NullPointerException - * If this JS object has been disposed - * @throws IllegalThreadStateException - * If not called on the thread this object is attached to - * @throws UnsupportedOperationException - * If an internal JSAPI call failed - */ - public native boolean[] optBooleanArray(String name, boolean[] fallback); - - /** - * Returns the value of an object property as a Bundle. - * - * @param name - * Property name - * @throws IllegalArgumentException - * If name is null - * @throws InvalidPropertyException - * If the property does not exist or if its type does not match the return type - * @throws NullPointerException - * If this JS object has been disposed - * @throws IllegalThreadStateException - * If not called on the thread this object is attached to - * @throws UnsupportedOperationException - * If an internal JSAPI call failed - */ - public native Bundle getBundle(String name); - - /** - * Returns the value of an object property as a Bundle. - * - * @param name - * Property name - * @param fallback - * Value to return if property does not exist - * @throws IllegalArgumentException - * If name is null - * @throws InvalidPropertyException - * If the property exists and its type does not match the return type - * @throws NullPointerException - * If this JS object has been disposed - * @throws IllegalThreadStateException - * If not called on the thread this object is attached to - * @throws UnsupportedOperationException - * If an internal JSAPI call failed - */ - public native Bundle optBundle(String name, Bundle fallback); - - /** - * Returns the value of an object array property as a Bundle array. - * - * @param name - * Property name - * @throws IllegalArgumentException - * If name is null - * @throws InvalidPropertyException - * If the property does not exist or if its type does not match the return type - * @throws NullPointerException - * If this JS object has been disposed - * @throws IllegalThreadStateException - * If not called on the thread this object is attached to - * @throws UnsupportedOperationException - * If an internal JSAPI call failed - */ - public native Bundle[] getBundleArray(String name); - - /** - * Returns the value of an object array property as a Bundle array. - * - * @param name - * Property name - * @param fallback - * Value to return if property does not exist - * @throws IllegalArgumentException - * If name is null - * @throws InvalidPropertyException - * If the property exists and its type does not match the return type - * @throws NullPointerException - * If this JS object has been disposed - * @throws IllegalThreadStateException - * If not called on the thread this object is attached to - * @throws UnsupportedOperationException - * If an internal JSAPI call failed - */ - public native Bundle[] optBundleArray(String name, Bundle[] fallback); - - /** - * Returns the value of a double property. - * - * @param name - * Property name - * @throws IllegalArgumentException - * If name is null - * @throws InvalidPropertyException - * If the property does not exist or if its type does not match the return type - * @throws NullPointerException - * If this JS object has been disposed - * @throws IllegalThreadStateException - * If not called on the thread this object is attached to - * @throws UnsupportedOperationException - * If an internal JSAPI call failed - */ - public native double getDouble(String name); - - /** - * Returns the value of a double property. - * - * @param name - * Property name - * @param fallback - * Value to return if property does not exist - * @throws IllegalArgumentException - * If name is null - * @throws InvalidPropertyException - * If the property exists and its type does not match the return type - * @throws NullPointerException - * If this JS object has been disposed - * @throws IllegalThreadStateException - * If not called on the thread this object is attached to - * @throws UnsupportedOperationException - * If an internal JSAPI call failed - */ - public native double optDouble(String name, double fallback); - - /** - * Returns the value of a double array property. - * - * @param name - * Property name - * @throws IllegalArgumentException - * If name is null - * @throws InvalidPropertyException - * If the property does not exist or if its type does not match the return type - * @throws NullPointerException - * If this JS object has been disposed - * @throws IllegalThreadStateException - * If not called on the thread this object is attached to - * @throws UnsupportedOperationException - * If an internal JSAPI call failed - */ - public native double[] getDoubleArray(String name); - - /** - * Returns the value of a double array property. - * - * @param name - * Property name - * @param fallback - * Value to return if property does not exist - * @throws IllegalArgumentException - * If name is null - * @throws InvalidPropertyException - * If the property exists and its type does not match the return type - * @throws NullPointerException - * If this JS object has been disposed - * @throws IllegalThreadStateException - * If not called on the thread this object is attached to - * @throws UnsupportedOperationException - * If an internal JSAPI call failed - */ - public native double[] optDoubleArray(String name, double[] fallback); - - /** - * Returns the value of an int property. - * - * @param name - * Property name - * @throws IllegalArgumentException - * If name is null - * @throws InvalidPropertyException - * If the property does not exist or if its type does not match the return type - * @throws NullPointerException - * If this JS object has been disposed - * @throws IllegalThreadStateException - * If not called on the thread this object is attached to - * @throws UnsupportedOperationException - * If an internal JSAPI call failed - */ - public native int getInt(String name); - - /** - * Returns the value of an int property. - * - * @param name - * Property name - * @param fallback - * Value to return if property does not exist - * @throws IllegalArgumentException - * If name is null - * @throws InvalidPropertyException - * If the property exists and its type does not match the return type - * @throws NullPointerException - * If this JS object has been disposed - * @throws IllegalThreadStateException - * If not called on the thread this object is attached to - * @throws UnsupportedOperationException - * If an internal JSAPI call failed - */ - public native int optInt(String name, int fallback); - - /** - * Returns the value of an int array property. - * - * @param name - * Property name - * @throws IllegalArgumentException - * If name is null - * @throws InvalidPropertyException - * If the property does not exist or if its type does not match the return type - * @throws NullPointerException - * If this JS object has been disposed - * @throws IllegalThreadStateException - * If not called on the thread this object is attached to - * @throws UnsupportedOperationException - * If an internal JSAPI call failed - */ - public native int[] getIntArray(String name); - - /** - * Returns the value of an int array property. - * - * @param name - * Property name - * @param fallback - * Value to return if property does not exist - * @throws IllegalArgumentException - * If name is null - * @throws InvalidPropertyException - * If the property exists and its type does not match the return type - * @throws NullPointerException - * If this JS object has been disposed - * @throws IllegalThreadStateException - * If not called on the thread this object is attached to - * @throws UnsupportedOperationException - * If an internal JSAPI call failed - */ - public native int[] optIntArray(String name, int[] fallback); - - /** - * Returns the value of an object property. - * - * @param name - * Property name - * @throws IllegalArgumentException - * If name is null - * @throws InvalidPropertyException - * If the property does not exist or if its type does not match the return type - * @throws NullPointerException - * If this JS object has been disposed - * @throws IllegalThreadStateException - * If not called on the thread this object is attached to - * @throws UnsupportedOperationException - * If an internal JSAPI call failed - */ - public native NativeJSObject getObject(String name); - - /** - * Returns the value of an object property. - * - * @param name - * Property name - * @param fallback - * Value to return if property does not exist - * @throws IllegalArgumentException - * If name is null - * @throws InvalidPropertyException - * If the property exists and its type does not match the return type - * @throws NullPointerException - * If this JS object has been disposed - * @throws IllegalThreadStateException - * If not called on the thread this object is attached to - * @throws UnsupportedOperationException - * If an internal JSAPI call failed - */ - public native NativeJSObject optObject(String name, NativeJSObject fallback); - - /** - * Returns the value of an object array property. - * - * @param name - * Property name - * @throws IllegalArgumentException - * If name is null - * @throws InvalidPropertyException - * If the property does not exist or if its type does not match the return type - * @throws NullPointerException - * If this JS object has been disposed - * @throws IllegalThreadStateException - * If not called on the thread this object is attached to - * @throws UnsupportedOperationException - * If an internal JSAPI call failed - */ - public native NativeJSObject[] getObjectArray(String name); - - /** - * Returns the value of an object array property. - * - * @param name - * Property name - * @param fallback - * Value to return if property does not exist - * @throws IllegalArgumentException - * If name is null - * @throws InvalidPropertyException - * If the property exists and its type does not match the return type - * @throws NullPointerException - * If this JS object has been disposed - * @throws IllegalThreadStateException - * If not called on the thread this object is attached to - * @throws UnsupportedOperationException - * If an internal JSAPI call failed - */ - public native NativeJSObject[] optObjectArray(String name, NativeJSObject[] fallback); - - /** - * Returns the value of a string property. - * - * @param name - * Property name - * @throws IllegalArgumentException - * If name is null - * @throws InvalidPropertyException - * If the property does not exist or if its type does not match the return type - * @throws NullPointerException - * If this JS object has been disposed - * @throws IllegalThreadStateException - * If not called on the thread this object is attached to - * @throws UnsupportedOperationException - * If an internal JSAPI call failed - */ - public native String getString(String name); - - /** - * Returns the value of a string property. - * - * @param name - * Property name - * @param fallback - * Value to return if property does not exist - * @throws IllegalArgumentException - * If name is null - * @throws InvalidPropertyException - * If the property exists and its type does not match the return type - * @throws NullPointerException - * If this JS object has been disposed - * @throws IllegalThreadStateException - * If not called on the thread this object is attached to - * @throws UnsupportedOperationException - * If an internal JSAPI call failed - */ - public native String optString(String name, String fallback); - - /** - * Returns the value of a string array property. - * - * @param name - * Property name - * @throws IllegalArgumentException - * If name is null - * @throws InvalidPropertyException - * If the property does not exist or if its type does not match the return type - * @throws NullPointerException - * If this JS object has been disposed - * @throws IllegalThreadStateException - * If not called on the thread this object is attached to - * @throws UnsupportedOperationException - * If an internal JSAPI call failed - */ - public native String[] getStringArray(String name); - - /** - * Returns the value of a string array property. - * - * @param name - * Property name - * @param fallback - * Value to return if property does not exist - * @throws IllegalArgumentException - * If name is null - * @throws InvalidPropertyException - * If the property exists and its type does not match the return type - * @throws NullPointerException - * If this JS object has been disposed - * @throws IllegalThreadStateException - * If not called on the thread this object is attached to - * @throws UnsupportedOperationException - * If an internal JSAPI call failed - */ - public native String[] optStringArray(String name, String[] fallback); - - /** - * Returns whether a property exists in this object - * - * @param name - * Property name - * @throws IllegalArgumentException - * If name is null - * @throws NullPointerException - * If this JS object has been disposed - * @throws IllegalThreadStateException - * If not called on the thread this object is attached to - * @throws UnsupportedOperationException - * If an internal JSAPI call failed - */ - public native boolean has(String name); - - /** - * Returns the Bundle representation of this object. - * - * @throws NullPointerException - * If this JS object has been disposed - * @throws IllegalThreadStateException - * If not called on the thread this object is attached to - * @throws UnsupportedOperationException - * If an internal JSAPI call failed - */ - public native Bundle toBundle(); - - /** - * Returns the JSON representation of this object. - * - * @throws NullPointerException - * If this JS object has been disposed - * @throws IllegalThreadStateException - * If not called on the thread this object is attached to - * @throws UnsupportedOperationException - * If an internal JSAPI call failed - */ - @Override - public native String toString(); -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/NetworkUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/NetworkUtils.java deleted file mode 100644 index 2210e43ed..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/NetworkUtils.java +++ /dev/null @@ -1,177 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.util; - -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.support.annotation.Nullable; -import android.support.annotation.NonNull; -import android.telephony.TelephonyManager; - -public class NetworkUtils { - /* - * Keep the below constants in sync with - * http://dxr.mozilla.org/mozilla-central/source/netwerk/base/nsINetworkLinkService.idl - */ - public enum ConnectionSubType { - CELL_2G("2g"), - CELL_3G("3g"), - CELL_4G("4g"), - ETHERNET("ethernet"), - WIFI("wifi"), - WIMAX("wimax"), - UNKNOWN("unknown"); - - public final String value; - ConnectionSubType(String value) { - this.value = value; - } - } - - /* - * Keep the below constants in sync with - * http://dxr.mozilla.org/mozilla-central/source/netwerk/base/nsINetworkLinkService.idl - */ - public enum NetworkStatus { - UP("up"), - DOWN("down"), - UNKNOWN("unknown"); - - public final String value; - - NetworkStatus(String value) { - this.value = value; - } - } - - // Connection Type defined in Network Information API v3. - // See Bug 1270401 - current W3C Spec (Editor's Draft) is different, it also contains wimax, mixed, unknown. - // W3C spec: http://w3c.github.io/netinfo/#the-connectiontype-enum - public enum ConnectionType { - CELLULAR(0), - BLUETOOTH(1), - ETHERNET(2), - WIFI(3), - OTHER(4), - NONE(5); - - public final int value; - - ConnectionType(int value) { - this.value = value; - } - } - - /** - * Indicates whether network connectivity exists and it is possible to establish connections and pass data. - */ - public static boolean isConnected(@NonNull Context context) { - return isConnected((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE)); - } - - public static boolean isConnected(ConnectivityManager connectivityManager) { - if (connectivityManager == null) { - return false; - } - - final NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); - return networkInfo != null && networkInfo.isConnected(); - } - - /** - * For mobile connections, maps particular connection subtype to a general 2G, 3G, 4G bucket. - */ - public static ConnectionSubType getConnectionSubType(ConnectivityManager connectivityManager) { - if (connectivityManager == null) { - return ConnectionSubType.UNKNOWN; - } - - final NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); - - if (networkInfo == null) { - return ConnectionSubType.UNKNOWN; - } - - switch (networkInfo.getType()) { - case ConnectivityManager.TYPE_ETHERNET: - return ConnectionSubType.ETHERNET; - case ConnectivityManager.TYPE_MOBILE: - return getGenericMobileSubtype(networkInfo.getSubtype()); - case ConnectivityManager.TYPE_WIMAX: - return ConnectionSubType.WIMAX; - case ConnectivityManager.TYPE_WIFI: - return ConnectionSubType.WIFI; - default: - return ConnectionSubType.UNKNOWN; - } - } - - public static ConnectionType getConnectionType(ConnectivityManager connectivityManager) { - if (connectivityManager == null) { - return ConnectionType.NONE; - } - - final NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); - if (networkInfo == null) { - return ConnectionType.NONE; - } - - switch (networkInfo.getType()) { - case ConnectivityManager.TYPE_BLUETOOTH: - return ConnectionType.BLUETOOTH; - case ConnectivityManager.TYPE_ETHERNET: - return ConnectionType.ETHERNET; - // Fallthrough, MOBILE and WIMAX both map to CELLULAR. - case ConnectivityManager.TYPE_MOBILE: - case ConnectivityManager.TYPE_WIMAX: - return ConnectionType.CELLULAR; - case ConnectivityManager.TYPE_WIFI: - return ConnectionType.WIFI; - default: - return ConnectionType.OTHER; - } - } - - public static NetworkStatus getNetworkStatus(ConnectivityManager connectivityManager) { - if (connectivityManager == null) { - return NetworkStatus.UNKNOWN; - } - - if (isConnected(connectivityManager)) { - return NetworkStatus.UP; - } - return NetworkStatus.DOWN; - } - - private static ConnectionSubType getGenericMobileSubtype(int subtype) { - switch (subtype) { - // 2G types: fallthrough 5x - case TelephonyManager.NETWORK_TYPE_GPRS: - case TelephonyManager.NETWORK_TYPE_EDGE: - case TelephonyManager.NETWORK_TYPE_CDMA: - case TelephonyManager.NETWORK_TYPE_1xRTT: - case TelephonyManager.NETWORK_TYPE_IDEN: - return ConnectionSubType.CELL_2G; - // 3G types: fallthrough 9x - case TelephonyManager.NETWORK_TYPE_UMTS: - case TelephonyManager.NETWORK_TYPE_EVDO_0: - case TelephonyManager.NETWORK_TYPE_EVDO_A: - case TelephonyManager.NETWORK_TYPE_HSDPA: - case TelephonyManager.NETWORK_TYPE_HSUPA: - case TelephonyManager.NETWORK_TYPE_HSPA: - case TelephonyManager.NETWORK_TYPE_EVDO_B: - case TelephonyManager.NETWORK_TYPE_EHRPD: - case TelephonyManager.NETWORK_TYPE_HSPAP: - return ConnectionSubType.CELL_3G; - // 4G - just one type! - case TelephonyManager.NETWORK_TYPE_LTE: - return ConnectionSubType.CELL_4G; - default: - return ConnectionSubType.UNKNOWN; - } - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/NonEvictingLruCache.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/NonEvictingLruCache.java deleted file mode 100644 index 793b39b81..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/NonEvictingLruCache.java +++ /dev/null @@ -1,44 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.util; - -import android.util.LruCache; - -import java.util.concurrent.ConcurrentHashMap; - -/** - * An LruCache that also supports a set of items that will never be evicted. - * - * Alas, LruCache is final, so we compose rather than inherit. - */ -public class NonEvictingLruCache<K, V> { - private final ConcurrentHashMap<K, V> permanent = new ConcurrentHashMap<K, V>(); - private final LruCache<K, V> evictable; - - public NonEvictingLruCache(final int evictableSize) { - evictable = new LruCache<K, V>(evictableSize); - } - - public V get(K key) { - V val = permanent.get(key); - if (val == null) { - return evictable.get(key); - } - return val; - } - - public void putWithoutEviction(K key, V value) { - permanent.put(key, value); - } - - public void put(K key, V value) { - evictable.put(key, value); - } - - public void evictAll() { - evictable.evictAll(); - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/PrefUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/PrefUtils.java deleted file mode 100644 index 217e40b91..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/PrefUtils.java +++ /dev/null @@ -1,70 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.util; - -import java.util.HashSet; -import java.util.Set; - -import org.json.JSONArray; -import org.json.JSONException; -import org.mozilla.gecko.AppConstants.Versions; - -import android.content.SharedPreferences; -import android.util.Log; - - -public class PrefUtils { - private static final String LOGTAG = "GeckoPrefUtils"; - - // Cross version compatible way to get a string set from a pref - public static Set<String> getStringSet(final SharedPreferences prefs, - final String key, - final Set<String> defaultVal) { - if (!prefs.contains(key)) { - return defaultVal; - } - - // If this is Android version >= 11, try to use a Set<String>. - try { - return prefs.getStringSet(key, new HashSet<String>()); - } catch (ClassCastException ex) { - // A ClassCastException means we've upgraded from a pre-v11 Android to a new one - final Set<String> val = getFromJSON(prefs, key); - SharedPreferences.Editor edit = prefs.edit(); - putStringSet(edit, key, val).apply(); - return val; - } - } - - private static Set<String> getFromJSON(SharedPreferences prefs, String key) { - try { - final String val = prefs.getString(key, "[]"); - return JSONUtils.parseStringSet(new JSONArray(val)); - } catch (JSONException ex) { - Log.i(LOGTAG, "Unable to parse JSON", ex); - } - - return new HashSet<String>(); - } - - /** - * Cross version compatible way to save a set of strings. - * <p> - * This method <b>does not commit</b> any transaction. It is up to callers - * to commit. - * - * @param editor to write to. - * @param key to write. - * @param vals comprising string set. - * @return - */ - public static SharedPreferences.Editor putStringSet(final SharedPreferences.Editor editor, - final String key, - final Set<String> vals) { - editor.putStringSet(key, vals); - return editor; - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ProxySelector.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ProxySelector.java deleted file mode 100644 index 35010242b..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ProxySelector.java +++ /dev/null @@ -1,155 +0,0 @@ -/* Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// This code is based on AOSP /libcore/luni/src/main/java/java/net/ProxySelectorImpl.java - -package org.mozilla.gecko.util; - -import android.support.annotation.Nullable; -import android.text.TextUtils; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.Proxy; -import java.net.URI; -import java.net.URLConnection; -import java.util.List; - -public class ProxySelector { - public static URLConnection openConnectionWithProxy(URI uri) throws IOException { - java.net.ProxySelector ps = java.net.ProxySelector.getDefault(); - Proxy proxy = Proxy.NO_PROXY; - if (ps != null) { - List<Proxy> proxies = ps.select(uri); - if (proxies != null && !proxies.isEmpty()) { - proxy = proxies.get(0); - } - } - - return uri.toURL().openConnection(proxy); - } - - public ProxySelector() { - } - - public Proxy select(String scheme, String host) { - int port = -1; - Proxy proxy = null; - String nonProxyHostsKey = null; - boolean httpProxyOkay = true; - if ("http".equalsIgnoreCase(scheme)) { - port = 80; - nonProxyHostsKey = "http.nonProxyHosts"; - proxy = lookupProxy("http.proxyHost", "http.proxyPort", Proxy.Type.HTTP, port); - } else if ("https".equalsIgnoreCase(scheme)) { - port = 443; - nonProxyHostsKey = "https.nonProxyHosts"; // RI doesn't support this - proxy = lookupProxy("https.proxyHost", "https.proxyPort", Proxy.Type.HTTP, port); - } else if ("ftp".equalsIgnoreCase(scheme)) { - port = 80; // not 21 as you might guess - nonProxyHostsKey = "ftp.nonProxyHosts"; - proxy = lookupProxy("ftp.proxyHost", "ftp.proxyPort", Proxy.Type.HTTP, port); - } else if ("socket".equalsIgnoreCase(scheme)) { - httpProxyOkay = false; - } else { - return Proxy.NO_PROXY; - } - - if (nonProxyHostsKey != null - && isNonProxyHost(host, System.getProperty(nonProxyHostsKey))) { - return Proxy.NO_PROXY; - } - - if (proxy != null) { - return proxy; - } - - if (httpProxyOkay) { - proxy = lookupProxy("proxyHost", "proxyPort", Proxy.Type.HTTP, port); - if (proxy != null) { - return proxy; - } - } - - proxy = lookupProxy("socksProxyHost", "socksProxyPort", Proxy.Type.SOCKS, 1080); - if (proxy != null) { - return proxy; - } - - return Proxy.NO_PROXY; - } - - /** - * Returns the proxy identified by the {@code hostKey} system property, or - * null. - */ - @Nullable - private Proxy lookupProxy(String hostKey, String portKey, Proxy.Type type, int defaultPort) { - final String host = System.getProperty(hostKey); - if (TextUtils.isEmpty(host)) { - return null; - } - - final int port = getSystemPropertyInt(portKey, defaultPort); - if (port == -1) { - // Port can be -1. See bug 1270529. - return null; - } - - return new Proxy(type, InetSocketAddress.createUnresolved(host, port)); - } - - private int getSystemPropertyInt(String key, int defaultValue) { - String string = System.getProperty(key); - if (string != null) { - try { - return Integer.parseInt(string); - } catch (NumberFormatException ignored) { - } - } - return defaultValue; - } - - /** - * Returns true if the {@code nonProxyHosts} system property pattern exists - * and matches {@code host}. - */ - private boolean isNonProxyHost(String host, String nonProxyHosts) { - if (host == null || nonProxyHosts == null) { - return false; - } - - // construct pattern - StringBuilder patternBuilder = new StringBuilder(); - for (int i = 0; i < nonProxyHosts.length(); i++) { - char c = nonProxyHosts.charAt(i); - switch (c) { - case '.': - patternBuilder.append("\\."); - break; - case '*': - patternBuilder.append(".*"); - break; - default: - patternBuilder.append(c); - } - } - // check whether the host is the nonProxyHosts. - String pattern = patternBuilder.toString(); - return host.matches(pattern); - } -} - diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/RawResource.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/RawResource.java deleted file mode 100644 index 5bcad1c60..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/RawResource.java +++ /dev/null @@ -1,52 +0,0 @@ -/* 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.util; - -import android.content.Context; -import android.content.res.Resources; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.StringWriter; - -/** - * {@code RawResource} provides API to load raw resources in different - * forms. For now, we only load them as strings. We're using raw resources - * as localizable 'assets' as opposed to a string that can be directly - * translatable e.g. JSON file vs string. - * - * This is just a utility class to avoid code duplication for the different - * cases where need to read such assets. - */ -public final class RawResource { - public static String getAsString(Context context, int id) throws IOException { - InputStreamReader reader = null; - - try { - final Resources res = context.getResources(); - final InputStream is = res.openRawResource(id); - if (is == null) { - return null; - } - - reader = new InputStreamReader(is); - - final char[] buffer = new char[1024]; - final StringWriter s = new StringWriter(); - - int n; - while ((n = reader.read(buffer, 0, buffer.length)) != -1) { - s.write(buffer, 0, n); - } - - return s.toString(); - } finally { - if (reader != null) { - reader.close(); - } - } - } -}
\ No newline at end of file diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/StringUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/StringUtils.java deleted file mode 100644 index 308168f43..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/StringUtils.java +++ /dev/null @@ -1,293 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.util; - -import android.net.Uri; -import android.support.annotation.NonNull; -import android.text.TextUtils; - -import org.mozilla.gecko.AppConstants.Versions; - -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -public class StringUtils { - private static final String LOGTAG = "GeckoStringUtils"; - - private static final String FILTER_URL_PREFIX = "filter://"; - private static final String USER_ENTERED_URL_PREFIX = "user-entered:"; - - /* - * This method tries to guess if the given string could be a search query or URL, - * and returns a previous result if there is ambiguity - * - * Search examples: - * foo - * foo bar.com - * foo http://bar.com - * - * URL examples - * foo.com - * foo.c - * :foo - * http://foo.com bar - * - * wasSearchQuery specifies whether text was a search query before the latest change - * in text. In ambiguous cases where the new text can be either a search or a URL, - * wasSearchQuery is returned - */ - public static boolean isSearchQuery(String text, boolean wasSearchQuery) { - // We remove leading and trailing white spaces when decoding URLs - text = text.trim(); - if (text.length() == 0) - return wasSearchQuery; - - int colon = text.indexOf(':'); - int dot = text.indexOf('.'); - int space = text.indexOf(' '); - - // If a space is found before any dot and colon, we assume this is a search query - if (space > -1 && (colon == -1 || space < colon) && (dot == -1 || space < dot)) { - return true; - } - // Otherwise, if a dot or a colon is found, we assume this is a URL - if (dot > -1 || colon > -1) { - return false; - } - // Otherwise, text is ambiguous, and we keep its status unchanged - return wasSearchQuery; - } - - /** - * Strip the ref from a URL, if present - * - * @return The base URL, without the ref. The original String is returned if it has no ref, - * of if the input is malformed. - */ - public static String stripRef(final String inputURL) { - if (inputURL == null) { - return null; - } - - final int refIndex = inputURL.indexOf('#'); - - if (refIndex >= 0) { - return inputURL.substring(0, refIndex); - } - - return inputURL; - } - - public static class UrlFlags { - public static final int NONE = 0; - public static final int STRIP_HTTPS = 1; - } - - public static String stripScheme(String url) { - return stripScheme(url, UrlFlags.NONE); - } - - public static String stripScheme(String url, int flags) { - if (url == null) { - return url; - } - - String newURL = url; - - if (newURL.startsWith("http://")) { - newURL = newURL.replace("http://", ""); - } else if (newURL.startsWith("https://") && flags == UrlFlags.STRIP_HTTPS) { - newURL = newURL.replace("https://", ""); - } - - if (newURL.endsWith("/")) { - newURL = newURL.substring(0, newURL.length()-1); - } - - return newURL; - } - - public static boolean isHttpOrHttps(String url) { - if (TextUtils.isEmpty(url)) { - return false; - } - - return url.startsWith("http://") || url.startsWith("https://"); - } - - public static String stripCommonSubdomains(String host) { - if (host == null) { - return host; - } - - // In contrast to desktop, we also strip mobile subdomains, - // since its unlikely users are intentionally typing them - int start = 0; - - if (host.startsWith("www.")) { - start = 4; - } else if (host.startsWith("mobile.")) { - start = 7; - } else if (host.startsWith("m.")) { - start = 2; - } - - return host.substring(start); - } - - /** - * Searches the url query string for the first value with the given key. - */ - public static String getQueryParameter(String url, String desiredKey) { - if (TextUtils.isEmpty(url) || TextUtils.isEmpty(desiredKey)) { - return null; - } - - final String[] urlParts = url.split("\\?"); - if (urlParts.length < 2) { - return null; - } - - final String query = urlParts[1]; - for (final String param : query.split("&")) { - final String pair[] = param.split("="); - final String key = Uri.decode(pair[0]); - - // Key is empty or does not match the key we're looking for, discard - if (TextUtils.isEmpty(key) || !key.equals(desiredKey)) { - continue; - } - // No value associated with key, discard - if (pair.length < 2) { - continue; - } - final String value = Uri.decode(pair[1]); - if (TextUtils.isEmpty(value)) { - return null; - } - return value; - } - - return null; - } - - public static boolean isFilterUrl(String url) { - if (TextUtils.isEmpty(url)) { - return false; - } - - return url.startsWith(FILTER_URL_PREFIX); - } - - public static String getFilterFromUrl(String url) { - if (TextUtils.isEmpty(url)) { - return null; - } - - return url.substring(FILTER_URL_PREFIX.length()); - } - - public static boolean isShareableUrl(final String url) { - final String scheme = Uri.parse(url).getScheme(); - return !("about".equals(scheme) || "chrome".equals(scheme) || - "file".equals(scheme) || "resource".equals(scheme)); - } - - public static boolean isUserEnteredUrl(String url) { - return (url != null && url.startsWith(USER_ENTERED_URL_PREFIX)); - } - - /** - * Given a url with a user-entered scheme, extract the - * scheme-specific component. For e.g, given "user-entered://www.google.com", - * this method returns "//www.google.com". If the passed url - * does not have a user-entered scheme, the same url will be returned. - * - * @param url to be decoded - * @return url component entered by user - */ - public static String decodeUserEnteredUrl(String url) { - Uri uri = Uri.parse(url); - if ("user-entered".equals(uri.getScheme())) { - return uri.getSchemeSpecificPart(); - } - return url; - } - - public static String encodeUserEnteredUrl(String url) { - return Uri.fromParts("user-entered", url, null).toString(); - } - - /** - * Compatibility layer for API < 11. - * - * Returns a set of the unique names of all query parameters. Iterating - * over the set will return the names in order of their first occurrence. - * - * @param uri - * @throws UnsupportedOperationException if this isn't a hierarchical URI - * - * @return a set of decoded names - */ - public static Set<String> getQueryParameterNames(Uri uri) { - return uri.getQueryParameterNames(); - } - - public static String safeSubstring(@NonNull final String str, final int start, final int end) { - return str.substring( - Math.max(0, start), - Math.min(end, str.length())); - } - - /** - * Check if this might be a RTL (right-to-left) text by looking at the first character. - */ - public static boolean isRTL(String text) { - if (TextUtils.isEmpty(text)) { - return false; - } - - final char character = text.charAt(0); - final byte directionality = Character.getDirectionality(character); - - return directionality == Character.DIRECTIONALITY_RIGHT_TO_LEFT - || directionality == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC - || directionality == Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING - || directionality == Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE; - } - - /** - * Force LTR (left-to-right) by prepending the text with the "left-to-right mark" (U+200E) if needed. - */ - public static String forceLTR(String text) { - if (!isRTL(text)) { - return text; - } - - return "\u200E" + text; - } - - /** - * Joining together a sequence of strings with a separator. - */ - public static String join(@NonNull String separator, @NonNull List<String> parts) { - if (parts.size() == 0) { - return ""; - } - - final StringBuilder builder = new StringBuilder(); - builder.append(parts.get(0)); - - for (int i = 1; i < parts.size(); i++) { - builder.append(separator); - builder.append(parts.get(i)); - } - - return builder.toString(); - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ThreadUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ThreadUtils.java deleted file mode 100644 index 884a56dc4..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ThreadUtils.java +++ /dev/null @@ -1,247 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.util; - -import org.mozilla.gecko.annotation.RobocopTarget; - -import java.util.Map; - -import android.os.Handler; -import android.os.Looper; -import android.os.MessageQueue; -import android.util.Log; - -public final class ThreadUtils { - private static final String LOGTAG = "ThreadUtils"; - - /** - * Controls the action taken when a method like - * {@link ThreadUtils#assertOnUiThread(AssertBehavior)} detects a problem. - */ - public static enum AssertBehavior { - NONE, - THROW, - } - - private static final Thread sUiThread = Looper.getMainLooper().getThread(); - private static final Handler sUiHandler = new Handler(Looper.getMainLooper()); - - private static volatile Thread sBackgroundThread; - - // Referenced directly from GeckoAppShell in highly performance-sensitive code (The extra - // function call of the getter was harming performance. (Bug 897123)) - // Once Bug 709230 is resolved we should reconsider this as ProGuard should be able to optimise - // this out at compile time. - public static Handler sGeckoHandler; - public static volatile Thread sGeckoThread; - - // Delayed Runnable that resets the Gecko thread priority. - private static final Runnable sPriorityResetRunnable = new Runnable() { - @Override - public void run() { - resetGeckoPriority(); - } - }; - - private static boolean sIsGeckoPriorityReduced; - - @SuppressWarnings("serial") - public static class UiThreadBlockedException extends RuntimeException { - public UiThreadBlockedException() { - super(); - } - - public UiThreadBlockedException(String msg) { - super(msg); - } - - public UiThreadBlockedException(String msg, Throwable e) { - super(msg, e); - } - - public UiThreadBlockedException(Throwable e) { - super(e); - } - } - - public static void dumpAllStackTraces() { - Log.w(LOGTAG, "Dumping ALL the threads!"); - Map<Thread, StackTraceElement[]> allStacks = Thread.getAllStackTraces(); - for (Thread t : allStacks.keySet()) { - Log.w(LOGTAG, t.toString()); - for (StackTraceElement ste : allStacks.get(t)) { - Log.w(LOGTAG, ste.toString()); - } - Log.w(LOGTAG, "----"); - } - } - - public static void setBackgroundThread(Thread thread) { - sBackgroundThread = thread; - } - - public static Thread getUiThread() { - return sUiThread; - } - - public static Handler getUiHandler() { - return sUiHandler; - } - - public static void postToUiThread(Runnable runnable) { - sUiHandler.post(runnable); - } - - public static void postDelayedToUiThread(Runnable runnable, long timeout) { - sUiHandler.postDelayed(runnable, timeout); - } - - public static void removeCallbacksFromUiThread(Runnable runnable) { - sUiHandler.removeCallbacks(runnable); - } - - public static Thread getBackgroundThread() { - return sBackgroundThread; - } - - public static Handler getBackgroundHandler() { - return GeckoBackgroundThread.getHandler(); - } - - public static void postToBackgroundThread(Runnable runnable) { - GeckoBackgroundThread.post(runnable); - } - - public static void assertOnUiThread(final AssertBehavior assertBehavior) { - assertOnThread(getUiThread(), assertBehavior); - } - - public static void assertOnUiThread() { - assertOnThread(getUiThread(), AssertBehavior.THROW); - } - - public static void assertNotOnUiThread() { - assertNotOnThread(getUiThread(), AssertBehavior.THROW); - } - - @RobocopTarget - public static void assertOnGeckoThread() { - assertOnThread(sGeckoThread, AssertBehavior.THROW); - } - - public static void assertNotOnGeckoThread() { - if (sGeckoThread == null) { - // Cannot be on Gecko thread if Gecko thread is not live yet. - return; - } - assertNotOnThread(sGeckoThread, AssertBehavior.THROW); - } - - public static void assertOnBackgroundThread() { - assertOnThread(getBackgroundThread(), AssertBehavior.THROW); - } - - public static void assertOnThread(final Thread expectedThread) { - assertOnThread(expectedThread, AssertBehavior.THROW); - } - - public static void assertOnThread(final Thread expectedThread, AssertBehavior behavior) { - assertOnThreadComparison(expectedThread, behavior, true); - } - - public static void assertNotOnThread(final Thread expectedThread, AssertBehavior behavior) { - assertOnThreadComparison(expectedThread, behavior, false); - } - - private static void assertOnThreadComparison(final Thread expectedThread, AssertBehavior behavior, boolean expected) { - final Thread currentThread = Thread.currentThread(); - final long currentThreadId = currentThread.getId(); - final long expectedThreadId = expectedThread.getId(); - - if ((currentThreadId == expectedThreadId) == expected) { - return; - } - - final String message; - if (expected) { - message = "Expected thread " + expectedThreadId + - " (\"" + expectedThread.getName() + "\"), but running on thread " + - currentThreadId + " (\"" + currentThread.getName() + "\")"; - } else { - message = "Expected anything but " + expectedThreadId + - " (\"" + expectedThread.getName() + "\"), but running there."; - } - - final IllegalThreadStateException e = new IllegalThreadStateException(message); - - switch (behavior) { - case THROW: - throw e; - default: - Log.e(LOGTAG, "Method called on wrong thread!", e); - } - } - - public static boolean isOnGeckoThread() { - if (sGeckoThread != null) { - return isOnThread(sGeckoThread); - } - return false; - } - - public static boolean isOnUiThread() { - return isOnThread(getUiThread()); - } - - @RobocopTarget - public static boolean isOnBackgroundThread() { - if (sBackgroundThread == null) { - return false; - } - - return isOnThread(sBackgroundThread); - } - - @RobocopTarget - public static boolean isOnThread(Thread thread) { - return (Thread.currentThread().getId() == thread.getId()); - } - - /** - * Reduces the priority of the Gecko thread, allowing other operations - * (such as those related to the UI and database) to take precedence. - * - * Note that there are no guards in place to prevent multiple calls - * to this method from conflicting with each other. - * - * @param timeout Timeout in ms after which the priority will be reset - */ - public static void reduceGeckoPriority(long timeout) { - if (Runtime.getRuntime().availableProcessors() > 1) { - // Don't reduce priority for multicore devices. We use availableProcessors() - // for its fast performance. It may give false negatives (i.e. multicore - // detected as single-core), but we can tolerate this behavior. - return; - } - if (!sIsGeckoPriorityReduced && sGeckoThread != null) { - sIsGeckoPriorityReduced = true; - sGeckoThread.setPriority(Thread.MIN_PRIORITY); - getUiHandler().postDelayed(sPriorityResetRunnable, timeout); - } - } - - /** - * Resets the priority of a thread whose priority has been reduced - * by reduceGeckoPriority. - */ - public static void resetGeckoPriority() { - if (sIsGeckoPriorityReduced) { - sIsGeckoPriorityReduced = false; - sGeckoThread.setPriority(Thread.NORM_PRIORITY); - getUiHandler().removeCallbacks(sPriorityResetRunnable); - } - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/UIAsyncTask.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/UIAsyncTask.java deleted file mode 100644 index 26cc32a99..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/UIAsyncTask.java +++ /dev/null @@ -1,121 +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.util; - -import android.os.Handler; -import android.os.Looper; - -/** - * Executes a background task and publishes the result on the UI thread. - * - * The standard {@link android.os.AsyncTask} only runs onPostExecute on the - * thread it is constructed on, so this is a convenience class for creating - * tasks off the UI thread. - * - * We use generics differently to Android's AsyncTask. - * Android uses a "Params" type parameter to represent the type of all the parameters to this task. - * It then uses arguments of type Params... to permit arbitrarily-many of these to be passed - * fluently. - * - * Unfortunately, since Java does not support generic array types (and since varargs desugars to a - * single array parameter) that behaviour exposes a hole in the type system. See: - * http://docs.oracle.com/javase/tutorial/java/generics/nonReifiableVarargsType.html#vulnerabilities - * - * Instead, we equivalently have a single type parameter "Param". A UiAsyncTask may take exactly one - * parameter of type Param. Since Param can be an array type, this no more restrictive than the - * other approach, it just provides additional type safety. - */ -public abstract class UIAsyncTask<Param, Result> { - /** - * Provide a convenient API for parameter-free UiAsyncTasks by wrapping parameter-taking methods - * from UiAsyncTask in parameterless equivalents. - */ - public static abstract class WithoutParams<InnerResult> extends UIAsyncTask<Void, InnerResult> { - public WithoutParams(Handler backgroundThreadHandler) { - super(backgroundThreadHandler); - } - - public void execute() { - execute(null); - } - - @Override - protected InnerResult doInBackground(Void unused) { - return doInBackground(); - } - - protected abstract InnerResult doInBackground(); - } - - final Handler mBackgroundThreadHandler; - private volatile boolean mCancelled; - private static Handler sHandler; - - /** - * Creates a new asynchronous task. - * - * @param backgroundThreadHandler the handler to execute the background task on - */ - public UIAsyncTask(Handler backgroundThreadHandler) { - mBackgroundThreadHandler = backgroundThreadHandler; - } - - private static synchronized Handler getUiHandler() { - if (sHandler == null) { - sHandler = new Handler(Looper.getMainLooper()); - } - - return sHandler; - } - - private final class BackgroundTaskRunnable implements Runnable { - private final Param mParam; - - public BackgroundTaskRunnable(Param param) { - mParam = param; - } - - @Override - public void run() { - final Result result = doInBackground(mParam); - - getUiHandler().post(new Runnable() { - @Override - public void run() { - if (mCancelled) { - onCancelled(); - } else { - onPostExecute(result); - } - } - }); - } - } - - protected void execute(final Param param) { - getUiHandler().post(new Runnable() { - @Override - public void run() { - onPreExecute(); - mBackgroundThreadHandler.post(new BackgroundTaskRunnable(param)); - } - }); - } - - public final boolean cancel() { - mCancelled = true; - return mCancelled; - } - - public final boolean isCancelled() { - return mCancelled; - } - - protected void onPreExecute() { } - protected void onPostExecute(Result result) { } - protected void onCancelled() { } - protected abstract Result doInBackground(Param param); -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/UUIDUtil.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/UUIDUtil.java deleted file mode 100644 index cef303a87..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/UUIDUtil.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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.util; - -import java.util.regex.Pattern; - -/** - * Utilities for UUIDs. - */ -public class UUIDUtil { - private UUIDUtil() {} - - public static final String UUID_REGEX = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"; - public static final Pattern UUID_PATTERN = Pattern.compile(UUID_REGEX); -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/WeakReferenceHandler.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/WeakReferenceHandler.java deleted file mode 100644 index 3e8508bce..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/WeakReferenceHandler.java +++ /dev/null @@ -1,27 +0,0 @@ -/* 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.util; - -import android.os.Handler; - -import java.lang.ref.WeakReference; - -/** - * A Handler to help prevent memory leaks when using Handlers as inner classes. - * - * To use, extend the Handler, if it's an inner class, make it static, - * and reference `this` via the associated WeakReference. - * - * For additional context, see the "HandlerLeak" android lint item and this post by Romain Guy: - * https://groups.google.com/forum/#!msg/android-developers/1aPZXZG6kWk/lIYDavGYn5UJ - */ -public class WeakReferenceHandler<T> extends Handler { - public final WeakReference<T> mTarget; - - public WeakReferenceHandler(final T that) { - super(); - mTarget = new WeakReference<>(that); - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/WindowUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/WindowUtils.java deleted file mode 100644 index 5298f846a..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/WindowUtils.java +++ /dev/null @@ -1,59 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.util; - -import org.mozilla.gecko.AppConstants.Versions; - -import android.content.Context; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.Display; -import android.view.WindowManager; - -import java.lang.reflect.Method; - -public class WindowUtils { - private static final String LOGTAG = "Gecko" + WindowUtils.class.getSimpleName(); - - private WindowUtils() { /* To prevent instantiation */ } - - /** - * Returns the best-guess physical device dimensions, including the system status bars. Note - * that DisplayMetrics.height/widthPixels does not include the system bars. - * - * via http://stackoverflow.com/a/23861333 - * - * @param context the calling Activity's Context - * @return The number of pixels of the device's largest dimension, ignoring software status bars - */ - public static int getLargestDimension(final Context context) { - final Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); - - if (Versions.feature17Plus) { - final DisplayMetrics realMetrics = new DisplayMetrics(); - display.getRealMetrics(realMetrics); - return Math.max(realMetrics.widthPixels, realMetrics.heightPixels); - - } else { - int tempWidth; - int tempHeight; - try { - final Method getRawH = Display.class.getMethod("getRawHeight"); - final Method getRawW = Display.class.getMethod("getRawWidth"); - tempWidth = (Integer) getRawW.invoke(display); - tempHeight = (Integer) getRawH.invoke(display); - } catch (Exception e) { - // This is the best we can do. - tempWidth = display.getWidth(); - tempHeight = display.getHeight(); - Log.w(LOGTAG, "Couldn't use reflection to get the real display metrics."); - } - - return Math.max(tempWidth, tempHeight); - - } - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/publicsuffix/PublicSuffix.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/publicsuffix/PublicSuffix.java deleted file mode 100644 index 6a146cfcf..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/publicsuffix/PublicSuffix.java +++ /dev/null @@ -1,121 +0,0 @@ -/* 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.util.publicsuffix; - -import android.content.Context; -import android.support.annotation.NonNull; -import android.support.annotation.WorkerThread; - -import org.mozilla.gecko.util.StringUtils; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -/** - * Helper methods for the public suffix part of a domain. - * - * A "public suffix" is one under which Internet users can (or historically could) directly register - * names. Some examples of public suffixes are .com, .co.uk and pvt.k12.ma.us. - * - * https://publicsuffix.org/ - * - * Some parts of the implementation of this class are based on InternetDomainName class of the Guava - * project: https://github.com/google/guava - */ -public class PublicSuffix { - /** - * Strip the public suffix from the domain. Returns the original domain if no public suffix - * could be found. - * - * www.mozilla.org -> www.mozilla - * independent.co.uk -> independent - */ - @NonNull - @WorkerThread // This method might need to load data from disk - public static String stripPublicSuffix(Context context, @NonNull String domain) { - if (domain.length() == 0) { - return domain; - } - - final int index = findPublicSuffixIndex(context, domain); - if (index == -1) { - return domain; - } - - return domain.substring(0, index); - } - - /** - * Returns the index of the leftmost part of the public suffix, or -1 if not found. - */ - @WorkerThread - private static int findPublicSuffixIndex(Context context, String domain) { - final List<String> parts = normalizeAndSplit(domain); - final int partsSize = parts.size(); - final Set<String> exact = PublicSuffixPatterns.getExactSet(context); - - for (int i = 0; i < partsSize; i++) { - String ancestorName = StringUtils.join(".", parts.subList(i, partsSize)); - - if (exact.contains(ancestorName)) { - return joinIndex(parts, i); - } - - // Excluded domains (e.g. !nhs.uk) use the next highest - // domain as the effective public suffix (e.g. uk). - if (PublicSuffixPatterns.EXCLUDED.contains(ancestorName)) { - return joinIndex(parts, i + 1); - } - - if (matchesWildcardPublicSuffix(ancestorName)) { - return joinIndex(parts, i); - } - } - - return -1; - } - - /** - * Normalize domain and split into domain parts (www.mozilla.org -> [www, mozilla, org]). - */ - private static List<String> normalizeAndSplit(String domain) { - domain = domain.replaceAll("[.\u3002\uFF0E\uFF61]", "."); // All dot-like characters to '.' - domain = domain.toLowerCase(); - - if (domain.endsWith(".")) { - domain = domain.substring(0, domain.length() - 1); // Strip trailing '.' - } - - List<String> parts = new ArrayList<>(); - Collections.addAll(parts, domain.split("\\.")); - - return parts; - } - - /** - * Translate the index of the leftmost part of the public suffix to the index of the domain string. - * - * [www, mozilla, org] and 2 => 12 (www.mozilla) - */ - private static int joinIndex(List<String> parts, int index) { - int actualIndex = parts.get(0).length(); - - for (int i = 1; i < index; i++) { - actualIndex += parts.get(i).length() + 1; // Add one for the "." that is not part of the list elements - } - - return actualIndex; - } - - /** - * Does the domain name match one of the "wildcard" patterns (e.g. {@code "*.ar"})? - */ - private static boolean matchesWildcardPublicSuffix(String domain) { - final String[] pieces = domain.split("\\.", 2); - return pieces.length == 2 && PublicSuffixPatterns.UNDER.contains(pieces[1]); - } -} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/publicsuffix/PublicSuffixPatterns.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/publicsuffix/PublicSuffixPatterns.java deleted file mode 100644 index 8c4b80ce1..000000000 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/publicsuffix/PublicSuffixPatterns.java +++ /dev/null @@ -1,117 +0,0 @@ -/* 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.util.publicsuffix; - -import android.content.Context; -import android.util.Log; - -import org.mozilla.gecko.util.IOUtils; - -import java.io.BufferedInputStream; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.HashSet; -import java.util.Set; - -class PublicSuffixPatterns { - /** If a hostname is contained as a key in this map, it is a public suffix. */ - private static Set<String> EXACT = null; - - static synchronized Set<String> getExactSet(Context context) { - if (EXACT != null) { - return EXACT; - } - - EXACT = new HashSet<>(); - - InputStream stream = null; - - try { - stream = context.getAssets().open("publicsuffixlist"); - BufferedReader reader = new BufferedReader(new InputStreamReader( - new BufferedInputStream(stream))); - - String line; - while ((line = reader.readLine()) != null) { - EXACT.add(line); - } - - } catch (IOException e) { - Log.e("Patterns", "IOException during loading public suffix list"); - } finally { - IOUtils.safeStreamClose(stream); - } - - return EXACT; - } - - - /** - * If a hostname is not a key in the EXCLUDE map, and if removing its - * leftmost component results in a name which is a key in this map, it is a - * public suffix. - */ - static final Set<String> UNDER = new HashSet<>(); - static { - UNDER.add("bd"); - UNDER.add("magentosite.cloud"); - UNDER.add("ke"); - UNDER.add("triton.zone"); - UNDER.add("compute.estate"); - UNDER.add("ye"); - UNDER.add("pg"); - UNDER.add("kh"); - UNDER.add("platform.sh"); - UNDER.add("fj"); - UNDER.add("ck"); - UNDER.add("fk"); - UNDER.add("alces.network"); - UNDER.add("sch.uk"); - UNDER.add("jm"); - UNDER.add("mm"); - UNDER.add("api.githubcloud.com"); - UNDER.add("ext.githubcloud.com"); - UNDER.add("0emm.com"); - UNDER.add("githubcloudusercontent.com"); - UNDER.add("cns.joyent.com"); - UNDER.add("bn"); - UNDER.add("yokohama.jp"); - UNDER.add("nagoya.jp"); - UNDER.add("kobe.jp"); - UNDER.add("sendai.jp"); - UNDER.add("kawasaki.jp"); - UNDER.add("sapporo.jp"); - UNDER.add("kitakyushu.jp"); - UNDER.add("np"); - UNDER.add("nom.br"); - UNDER.add("er"); - UNDER.add("cryptonomic.net"); - UNDER.add("gu"); - UNDER.add("kw"); - UNDER.add("zw"); - UNDER.add("mz"); - } - - /** - * The elements in this map would pass the UNDER test, but are known not to - * be public suffixes and are thus excluded from consideration. Since it - * refers to elements in UNDER of the same type, the type is actually not - * important here. The map is simply used for consistency reasons. - */ - static final Set<String> EXCLUDED = new HashSet<>(); - static { - EXCLUDED.add("www.ck"); - EXCLUDED.add("city.yokohama.jp"); - EXCLUDED.add("city.nagoya.jp"); - EXCLUDED.add("city.kobe.jp"); - EXCLUDED.add("city.sendai.jp"); - EXCLUDED.add("city.kawasaki.jp"); - EXCLUDED.add("city.sapporo.jp"); - EXCLUDED.add("city.kitakyushu.jp"); - EXCLUDED.add("teledata.mz"); - } -} diff --git a/mobile/android/geckoview/src/thirdparty/java/com/googlecode/eyesfree/braille/selfbraille/ISelfBrailleService.java b/mobile/android/geckoview/src/thirdparty/java/com/googlecode/eyesfree/braille/selfbraille/ISelfBrailleService.java deleted file mode 100644 index 3bdd8c450..000000000 --- a/mobile/android/geckoview/src/thirdparty/java/com/googlecode/eyesfree/braille/selfbraille/ISelfBrailleService.java +++ /dev/null @@ -1,147 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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 com.googlecode.eyesfree.braille.selfbraille; - -/** - * Interface for a client to control braille output for a part of the - * accessibility node tree. - */ -public interface ISelfBrailleService extends android.os.IInterface { - /** Local-side IPC implementation stub class. */ - public static abstract class Stub extends android.os.Binder implements - com.googlecode.eyesfree.braille.selfbraille.ISelfBrailleService { - private static final java.lang.String DESCRIPTOR = "com.googlecode.eyesfree.braille.selfbraille.ISelfBrailleService"; - - /** Construct the stub at attach it to the interface. */ - public Stub() { - this.attachInterface(this, DESCRIPTOR); - } - - /** - * Cast an IBinder object into an - * com.googlecode.eyesfree.braille.selfbraille.ISelfBrailleService - * interface, generating a proxy if needed. - */ - public static com.googlecode.eyesfree.braille.selfbraille.ISelfBrailleService asInterface( - android.os.IBinder obj) { - if ((obj == null)) { - return null; - } - android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); - if (((iin != null) && (iin instanceof com.googlecode.eyesfree.braille.selfbraille.ISelfBrailleService))) { - return ((com.googlecode.eyesfree.braille.selfbraille.ISelfBrailleService) iin); - } - return new com.googlecode.eyesfree.braille.selfbraille.ISelfBrailleService.Stub.Proxy( - obj); - } - - @Override - public android.os.IBinder asBinder() { - return this; - } - - @Override - public boolean onTransact(int code, android.os.Parcel data, - android.os.Parcel reply, int flags) - throws android.os.RemoteException { - switch (code) { - case INTERFACE_TRANSACTION: { - reply.writeString(DESCRIPTOR); - return true; - } - case TRANSACTION_write: { - data.enforceInterface(DESCRIPTOR); - android.os.IBinder _arg0; - _arg0 = data.readStrongBinder(); - com.googlecode.eyesfree.braille.selfbraille.WriteData _arg1; - if ((0 != data.readInt())) { - _arg1 = com.googlecode.eyesfree.braille.selfbraille.WriteData.CREATOR - .createFromParcel(data); - } else { - _arg1 = null; - } - this.write(_arg0, _arg1); - reply.writeNoException(); - return true; - } - case TRANSACTION_disconnect: { - data.enforceInterface(DESCRIPTOR); - android.os.IBinder _arg0; - _arg0 = data.readStrongBinder(); - this.disconnect(_arg0); - return true; - } - } - return super.onTransact(code, data, reply, flags); - } - - private static class Proxy implements - com.googlecode.eyesfree.braille.selfbraille.ISelfBrailleService { - private android.os.IBinder mRemote; - - Proxy(android.os.IBinder remote) { - mRemote = remote; - } - - @Override - public android.os.IBinder asBinder() { - return mRemote; - } - - public java.lang.String getInterfaceDescriptor() { - return DESCRIPTOR; - } - - @Override - public void write( - android.os.IBinder clientToken, - com.googlecode.eyesfree.braille.selfbraille.WriteData writeData) - throws android.os.RemoteException { - android.os.Parcel _data = android.os.Parcel.obtain(); - android.os.Parcel _reply = android.os.Parcel.obtain(); - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeStrongBinder(clientToken); - if ((writeData != null)) { - _data.writeInt(1); - writeData.writeToParcel(_data, 0); - } else { - _data.writeInt(0); - } - mRemote.transact(Stub.TRANSACTION_write, _data, _reply, 0); - _reply.readException(); - } finally { - _reply.recycle(); - _data.recycle(); - } - } - - @Override - public void disconnect(android.os.IBinder clientToken) - throws android.os.RemoteException { - android.os.Parcel _data = android.os.Parcel.obtain(); - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeStrongBinder(clientToken); - mRemote.transact(Stub.TRANSACTION_disconnect, _data, null, - android.os.IBinder.FLAG_ONEWAY); - } finally { - _data.recycle(); - } - } - } - - static final int TRANSACTION_write = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); - static final int TRANSACTION_disconnect = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); - } - - public void write(android.os.IBinder clientToken, - com.googlecode.eyesfree.braille.selfbraille.WriteData writeData) - throws android.os.RemoteException; - - public void disconnect(android.os.IBinder clientToken) - throws android.os.RemoteException; -} diff --git a/mobile/android/geckoview/src/thirdparty/java/com/googlecode/eyesfree/braille/selfbraille/SelfBrailleClient.java b/mobile/android/geckoview/src/thirdparty/java/com/googlecode/eyesfree/braille/selfbraille/SelfBrailleClient.java deleted file mode 100644 index e4a363aca..000000000 --- a/mobile/android/geckoview/src/thirdparty/java/com/googlecode/eyesfree/braille/selfbraille/SelfBrailleClient.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (C) 2012 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.googlecode.eyesfree.braille.selfbraille; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.Signature; -import android.os.Binder; -import android.os.Handler; -import android.os.IBinder; -import android.os.Message; -import android.os.RemoteException; -import android.util.Log; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -/** - * Client-side interface to the self brailling interface. - * - * Threading: Instances of this object should be created and shut down - * in a thread with a {@link Looper} associated with it. Other methods may - * be called on any thread. - */ -public class SelfBrailleClient { - private static final String LOG_TAG = - SelfBrailleClient.class.getSimpleName(); - private static final String ACTION_SELF_BRAILLE_SERVICE = - "com.googlecode.eyesfree.braille.service.ACTION_SELF_BRAILLE_SERVICE"; - private static final String BRAILLE_BACK_PACKAGE = - "com.googlecode.eyesfree.brailleback"; - private static final Intent mServiceIntent = - new Intent(ACTION_SELF_BRAILLE_SERVICE) - .setPackage(BRAILLE_BACK_PACKAGE); - /** - * SHA-1 hash value of the Eyes-Free release key certificate, used to sign - * BrailleBack. It was generated from the keystore with: - * $ keytool -exportcert -keystore <keystorefile> -alias android.keystore \ - * > cert - * $ keytool -printcert -file cert - */ - // The typecasts are to silence a compiler warning about loss of precision - private static final byte[] EYES_FREE_CERT_SHA1 = new byte[] { - (byte) 0x9B, (byte) 0x42, (byte) 0x4C, (byte) 0x2D, - (byte) 0x27, (byte) 0xAD, (byte) 0x51, (byte) 0xA4, - (byte) 0x2A, (byte) 0x33, (byte) 0x7E, (byte) 0x0B, - (byte) 0xB6, (byte) 0x99, (byte) 0x1C, (byte) 0x76, - (byte) 0xEC, (byte) 0xA4, (byte) 0x44, (byte) 0x61 - }; - /** - * Delay before the first rebind attempt on bind error or service - * disconnect. - */ - private static final int REBIND_DELAY_MILLIS = 500; - private static final int MAX_REBIND_ATTEMPTS = 5; - - private final Binder mIdentity = new Binder(); - private final Context mContext; - private final boolean mAllowDebugService; - private final SelfBrailleHandler mHandler = new SelfBrailleHandler(); - private boolean mShutdown = false; - - /** - * Written in handler thread, read in any thread calling methods on the - * object. - */ - private volatile Connection mConnection; - /** Protected by synchronizing on mHandler. */ - private int mNumFailedBinds = 0; - - /** - * Constructs an instance of this class. {@code context} is used to bind - * to the self braille service. The current thread must have a Looper - * associated with it. If {@code allowDebugService} is true, this instance - * will connect to a BrailleBack service without requiring it to be signed - * by the release key used to sign BrailleBack. - */ - public SelfBrailleClient(Context context, boolean allowDebugService) { - mContext = context; - mAllowDebugService = allowDebugService; - doBindService(); - } - - /** - * Shuts this instance down, deallocating any global resources it is using. - * This method must be called on the same thread that created this object. - */ - public void shutdown() { - mShutdown = true; - doUnbindService(); - } - - public void write(WriteData writeData) { - writeData.validate(); - ISelfBrailleService localService = getSelfBrailleService(); - if (localService != null) { - try { - localService.write(mIdentity, writeData); - } catch (RemoteException ex) { - Log.e(LOG_TAG, "Self braille write failed", ex); - } - } - } - - private void doBindService() { - Connection localConnection = new Connection(); - if (!mContext.bindService(mServiceIntent, localConnection, - Context.BIND_AUTO_CREATE)) { - Log.e(LOG_TAG, "Failed to bind to service"); - mHandler.scheduleRebind(); - return; - } - mConnection = localConnection; - Log.i(LOG_TAG, "Bound to self braille service"); - } - - private void doUnbindService() { - if (mConnection != null) { - ISelfBrailleService localService = getSelfBrailleService(); - if (localService != null) { - try { - localService.disconnect(mIdentity); - } catch (RemoteException ex) { - // Nothing to do. - } - } - mContext.unbindService(mConnection); - mConnection = null; - } - } - - private ISelfBrailleService getSelfBrailleService() { - Connection localConnection = mConnection; - if (localConnection != null) { - return localConnection.mService; - } - return null; - } - - private boolean verifyPackage() { - PackageManager pm = mContext.getPackageManager(); - PackageInfo pi; - try { - pi = pm.getPackageInfo(BRAILLE_BACK_PACKAGE, - PackageManager.GET_SIGNATURES); - } catch (PackageManager.NameNotFoundException ex) { - Log.w(LOG_TAG, "Can't verify package " + BRAILLE_BACK_PACKAGE, - ex); - return false; - } - MessageDigest digest; - try { - digest = MessageDigest.getInstance("SHA-1"); - } catch (NoSuchAlgorithmException ex) { - Log.e(LOG_TAG, "SHA-1 not supported", ex); - return false; - } - // Check if any of the certificates match our hash. - for (Signature signature : pi.signatures) { - digest.update(signature.toByteArray()); - if (MessageDigest.isEqual(EYES_FREE_CERT_SHA1, digest.digest())) { - return true; - } - digest.reset(); - } - if (mAllowDebugService) { - Log.w(LOG_TAG, String.format( - "*** %s connected to BrailleBack with invalid (debug?) " - + "signature ***", - mContext.getPackageName())); - return true; - } - return false; - } - private class Connection implements ServiceConnection { - // Read in application threads, written in main thread. - private volatile ISelfBrailleService mService; - - @Override - public void onServiceConnected(ComponentName className, - IBinder binder) { - if (!verifyPackage()) { - Log.w(LOG_TAG, String.format("Service certificate mismatch " - + "for %s, dropping connection", - BRAILLE_BACK_PACKAGE)); - mHandler.unbindService(); - return; - } - Log.i(LOG_TAG, "Connected to self braille service"); - mService = ISelfBrailleService.Stub.asInterface(binder); - synchronized (mHandler) { - mNumFailedBinds = 0; - } - } - - @Override - public void onServiceDisconnected(ComponentName className) { - Log.e(LOG_TAG, "Disconnected from self braille service"); - mService = null; - // Retry by rebinding. - mHandler.scheduleRebind(); - } - } - - private class SelfBrailleHandler extends Handler { - private static final int MSG_REBIND_SERVICE = 1; - private static final int MSG_UNBIND_SERVICE = 2; - - public void scheduleRebind() { - synchronized (this) { - if (mNumFailedBinds < MAX_REBIND_ATTEMPTS) { - int delay = REBIND_DELAY_MILLIS << mNumFailedBinds; - sendEmptyMessageDelayed(MSG_REBIND_SERVICE, delay); - ++mNumFailedBinds; - } - } - } - - public void unbindService() { - sendEmptyMessage(MSG_UNBIND_SERVICE); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_REBIND_SERVICE: - handleRebindService(); - break; - case MSG_UNBIND_SERVICE: - handleUnbindService(); - break; - } - } - - private void handleRebindService() { - if (mShutdown) { - return; - } - if (mConnection != null) { - doUnbindService(); - } - doBindService(); - } - - private void handleUnbindService() { - doUnbindService(); - } - } -} diff --git a/mobile/android/geckoview/src/thirdparty/java/com/googlecode/eyesfree/braille/selfbraille/WriteData.java b/mobile/android/geckoview/src/thirdparty/java/com/googlecode/eyesfree/braille/selfbraille/WriteData.java deleted file mode 100644 index ef81a2990..000000000 --- a/mobile/android/geckoview/src/thirdparty/java/com/googlecode/eyesfree/braille/selfbraille/WriteData.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright (C) 2012 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.googlecode.eyesfree.braille.selfbraille; - -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.view.View; -import android.view.accessibility.AccessibilityNodeInfo; - -/** - * Represents what should be shown on the braille display for a - * part of the accessibility node tree. - */ -public class WriteData implements Parcelable { - - private static final String PROP_SELECTION_START = "selectionStart"; - private static final String PROP_SELECTION_END = "selectionEnd"; - - private AccessibilityNodeInfo mAccessibilityNodeInfo; - private CharSequence mText; - private Bundle mProperties = Bundle.EMPTY; - - /** - * Returns a new {@link WriteData} instance for the given {@code view}. - */ - public static WriteData forView(View view) { - AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain(view); - WriteData writeData = new WriteData(); - writeData.mAccessibilityNodeInfo = node; - return writeData; - } - - public static WriteData forInfo(AccessibilityNodeInfo info){ - WriteData writeData = new WriteData(); - writeData.mAccessibilityNodeInfo = info; - return writeData; - } - - - public AccessibilityNodeInfo getAccessibilityNodeInfo() { - return mAccessibilityNodeInfo; - } - - /** - * Sets the text to be displayed when the accessibility node associated - * with this instance has focus. If this method is not called (or - * {@code text} is {@code null}), this client relinquishes control over - * this node. - */ - public WriteData setText(CharSequence text) { - mText = text; - return this; - } - - public CharSequence getText() { - return mText; - } - - /** - * Sets the start position in the text of a text selection or cursor that - * should be marked on the display. A negative value (the default) means - * no selection will be added. - */ - public WriteData setSelectionStart(int v) { - writableProperties().putInt(PROP_SELECTION_START, v); - return this; - } - - /** - * @see {@link #setSelectionStart}. - */ - public int getSelectionStart() { - return mProperties.getInt(PROP_SELECTION_START, -1); - } - - /** - * Sets the end of the text selection to be marked on the display. This - * value should only be non-negative if the selection start is - * non-negative. If this value is <= the selection start, the selection - * is a cursor. Otherwise, the selection covers the range from - * start(inclusive) to end (exclusive). - * - * @see {@link android.text.Selection}. - */ - public WriteData setSelectionEnd(int v) { - writableProperties().putInt(PROP_SELECTION_END, v); - return this; - } - - /** - * @see {@link #setSelectionEnd}. - */ - public int getSelectionEnd() { - return mProperties.getInt(PROP_SELECTION_END, -1); - } - - private Bundle writableProperties() { - if (mProperties == Bundle.EMPTY) { - mProperties = new Bundle(); - } - return mProperties; - } - - /** - * Checks constraints on the fields that must be satisfied before sending - * this instance to the self braille service. - * @throws IllegalStateException - */ - public void validate() throws IllegalStateException { - if (mAccessibilityNodeInfo == null) { - throw new IllegalStateException( - "Accessibility node info can't be null"); - } - int selectionStart = getSelectionStart(); - int selectionEnd = getSelectionEnd(); - if (mText == null) { - if (selectionStart > 0 || selectionEnd > 0) { - throw new IllegalStateException( - "Selection can't be set without text"); - } - } else { - if (selectionStart < 0 && selectionEnd >= 0) { - throw new IllegalStateException( - "Selection end without start"); - } - int textLength = mText.length(); - if (selectionStart > textLength || selectionEnd > textLength) { - throw new IllegalStateException("Selection out of bounds"); - } - } - } - - // For Parcelable support. - - public static final Parcelable.Creator<WriteData> CREATOR = - new Parcelable.Creator<WriteData>() { - @Override - public WriteData createFromParcel(Parcel in) { - return new WriteData(in); - } - - @Override - public WriteData[] newArray(int size) { - return new WriteData[size]; - } - }; - - @Override - public int describeContents() { - return 0; - } - - /** - * {@inheritDoc} - * <strong>Note:</strong> The {@link AccessibilityNodeInfo} will be - * recycled by this method, don't try to use this more than once. - */ - @Override - public void writeToParcel(Parcel out, int flags) { - mAccessibilityNodeInfo.writeToParcel(out, flags); - // The above call recycles the node, so make sure we don't use it - // anymore. - mAccessibilityNodeInfo = null; - out.writeString(mText.toString()); - out.writeBundle(mProperties); - } - - private WriteData() { - } - - private WriteData(Parcel in) { - mAccessibilityNodeInfo = - AccessibilityNodeInfo.CREATOR.createFromParcel(in); - mText = in.readString(); - mProperties = in.readBundle(); - } -} |