author | Fabrice Desré <fabrice@mozilla.com> |
Thu, 24 Sep 2015 09:55:52 -0700 | |
changeset 264281 | 1b161b73d4b94ab56d09b67f0d37a10591bf2e63 |
parent 264280 | 3c90be122d3e352416e25a088ed7f282f3e0433e |
child 264282 | e014082c421e633a87e1185dfb6e062af508eb2c |
push id | 65590 |
push user | kwierso@gmail.com |
push date | Fri, 25 Sep 2015 00:14:23 +0000 |
treeherder | mozilla-inbound@0ab67cace54f [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | snorp |
bugs | 1207417 |
milestone | 44.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/mobile/android/b2gdroid/app/Makefile.in +++ b/mobile/android/b2gdroid/app/Makefile.in @@ -3,16 +3,17 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. ANDROID_MANIFEST_FILE := src/main/AndroidManifest.xml JAVAFILES := \ src/main/java/org/mozilla/b2gdroid/Apps.java \ src/main/java/org/mozilla/b2gdroid/Launcher.java \ src/main/java/org/mozilla/b2gdroid/ScreenStateObserver.java \ + src/main/java/org/mozilla/b2gdroid/SettingsMapper.java \ $(NULL) # The GeckoView consuming APK depends on the GeckoView JAR files. There are two # issues: first, the GeckoView JAR files need to be built before they are # consumed here. This happens for delicate reasons. In the (serial) libs tier, # base/ is traversed before b2gdroid/app. Since base/libs builds classes.dex, # the underlying JAR files are built before the libs tier of b2gdroid/app is # processed. Second, there is a correctness issue: the GeckoView JAR providing
--- a/mobile/android/b2gdroid/app/src/main/AndroidManifest.xml +++ b/mobile/android/b2gdroid/app/src/main/AndroidManifest.xml @@ -44,16 +44,19 @@ <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" /> <!-- Needed to disable the default lockscreen --> <uses-permission android:name="android.permission.DISABLE_KEYGUARD" /> + <uses-permission android:name="android.permission.WRITE_SETTINGS" /> + <uses-permission android:name="android.permission.SET_WALLPAPER" /> + <application android:label="@string/b2g" android:icon="@drawable/b2g" android:logo="@drawable/b2g" android:hardwareAccelerated="true" android:debuggable="true"> <meta-data android:name="com.sec.android.support.multiwindow" android:value="true"/>
--- a/mobile/android/b2gdroid/app/src/main/java/org/mozilla/b2gdroid/Launcher.java +++ b/mobile/android/b2gdroid/app/src/main/java/org/mozilla/b2gdroid/Launcher.java @@ -28,24 +28,26 @@ import org.mozilla.gecko.GeckoBatteryMan import org.mozilla.gecko.GeckoEvent; import org.mozilla.gecko.GeckoThread; import org.mozilla.gecko.IntentHelper; import org.mozilla.gecko.updater.UpdateServiceHelper; import org.mozilla.gecko.util.GeckoEventListener; import org.mozilla.b2gdroid.ScreenStateObserver; import org.mozilla.b2gdroid.Apps; +import org.mozilla.b2gdroid.SettingsMapper; public class Launcher extends Activity implements GeckoEventListener, ContextGetter { private static final String LOGTAG = "B2G"; private ContactService mContactService; private ScreenStateObserver mScreenStateObserver; private Apps mApps; + private SettingsMapper mSettings; /** ContextGetter */ public Context getContext() { return this; } public SharedPreferences getSharedPreferences() { return null; @@ -53,16 +55,17 @@ public class Launcher extends Activity /** Initializes Gecko APIs */ private void initGecko() { GeckoAppShell.setContextGetter(this); GeckoBatteryManager.getInstance().start(this); mContactService = new ContactService(EventDispatcher.getInstance(), this); mApps = new Apps(this); + mSettings = new SettingsMapper(this, null); } private void hideSplashScreen() { final View splash = findViewById(R.id.splashscreen); runOnUiThread(new Runnable() { @Override public void run() { splash.setVisibility(View.GONE); } @@ -111,16 +114,17 @@ public class Launcher extends Activity mScreenStateObserver.destroy(this); mScreenStateObserver = null; EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "Launcher:Ready"); mContactService.destroy(); mApps.destroy(); + mSettings.destroy(); } @Override protected void onNewIntent(Intent intent) { final String action = intent.getAction(); Log.w(LOGTAG, "onNewIntent " + action); if (Intent.ACTION_VIEW.equals(action)) { Log.w(LOGTAG, "Asking gecko to view " + intent.getDataString());
new file mode 100644 --- /dev/null +++ b/mobile/android/b2gdroid/app/src/main/java/org/mozilla/b2gdroid/SettingsMapper.java @@ -0,0 +1,247 @@ +/* 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.b2gdroid; + +import java.util.Hashtable; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import android.app.WallpaperManager; +import android.content.Context; +import android.database.ContentObserver; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Handler; +import android.provider.Settings.System; +import android.util.Base64; +import android.util.Log; + +import org.mozilla.gecko.EventDispatcher; +import org.mozilla.gecko.GeckoAppShell; +import org.mozilla.gecko.GeckoEvent; +import org.mozilla.gecko.util.GeckoEventListener; + +// This class communicates back and forth with MessagesBridge.jsm to +// map Android configuration settings and gaia settings. +// Each setting extends the base BaseMapping class to normalize values +// when needed. + +class SettingsMapper extends ContentObserver implements GeckoEventListener { + private static final String LOGTAG = "SettingsMapper"; + + private Context mContext; + private Hashtable<String, BaseMapping> mGeckoSettings; + private Hashtable<String, BaseMapping> mAndroidSettings; + + abstract class BaseMapping { + // Returns the list of gaia settings that are managed this class. + abstract String[] getGeckoSettings(); + + // Returns the list of android settings that are managed this class. + abstract String[] getAndroidSettings(); + + // Called when we a registered gecko setting changes. + abstract void onGeckoChange(String setting, JSONObject message); + + // Called when we a registered android setting changes. + abstract void onAndroidChange(Uri uri); + + void sendGeckoSetting(String name, String value) { + JSONObject obj = new JSONObject(); + try { + obj.put(name, value); + sendGeckoSetting(obj); + } catch(JSONException e) { + Log.d(LOGTAG, e.toString()); + } + } + + void sendGeckoSetting(String name, long value) { + JSONObject obj = new JSONObject(); + try { + obj.put(name, value); + sendGeckoSetting(obj); + } catch(JSONException e) { + Log.d(LOGTAG, e.toString()); + } + } + + void sendGeckoSetting(JSONObject obj) { + GeckoEvent e = GeckoEvent.createBroadcastEvent("Android:Setting", obj.toString()); + GeckoAppShell.sendEventToGecko(e); + } + } + + class ScreenTimeoutMapping extends BaseMapping { + ScreenTimeoutMapping() {} + + String[] getGeckoSettings() { + String props[] = {"screen.timeout"}; + return props; + } + + String[] getAndroidSettings() { + String props[] = {"content://settings/system/screen_off_timeout"}; + return props; + } + + void onGeckoChange(String setting, JSONObject message) { + try { + int timeout = message.getInt("value"); + // b2g uses seconds for the timeout while Android expects ms. + // "never" is 0 in b2g, -1 in Android. + if (timeout == 0) { + timeout = -1; + } else { + timeout *= 1000; + } + System.putInt(mContext.getContentResolver(), + System.SCREEN_OFF_TIMEOUT, + timeout); + } catch(Exception ex) { + Log.d(LOGTAG, "Error setting screen.timeout value", ex); + } + } + + void onAndroidChange(Uri uri) { + try { + int timeout = System.getInt(mContext.getContentResolver(), + System.SCREEN_OFF_TIMEOUT); + Log.d(LOGTAG, "Android set timeout to " + timeout); + + // Convert to a gaia timeout. + timeout /= 1000; + sendGeckoSetting("screen.timeout", timeout); + } catch(Exception e) {} + } + } + + class WallpaperMapping extends BaseMapping { + private Context mContext; + + WallpaperMapping(Context context) { + mContext = context; + } + + String[] getGeckoSettings() { + String props[] = {"wallpaper.image"}; + return props; + } + + String[] getAndroidSettings() { + String props[] = {}; + return props; + } + + void onGeckoChange(String setting, JSONObject message) { + try { + final String url = message.getString("value"); + Log.d(LOGTAG, "wallpaper.image is now " + url); + WallpaperManager manager = WallpaperManager.getInstance(mContext); + // Remove the data:image/png;base64, prefix from the url. + byte[] raw = Base64.decode(url.substring(22), Base64.NO_WRAP); + Bitmap bitmap = BitmapFactory.decodeByteArray(raw, 0, raw.length); + if (bitmap == null) { + Log.d(LOGTAG, "Unable to create a bitmap!"); + } + manager.setBitmap(bitmap); + } catch(Exception ex) { + Log.d(LOGTAG, "Error setting wallpaper", ex); + } + } + + // Android doesn't notify on wallpaper changes. + void onAndroidChange(Uri uri) { } + } + + SettingsMapper(Context context, Handler handler) { + super(handler); + mContext = context; + EventDispatcher.getInstance() + .registerGeckoThreadListener(this, + "Settings:Change"); + + mContext.getContentResolver() + .registerContentObserver(System.CONTENT_URI, + true, + this); + + mGeckoSettings = new Hashtable<String, BaseMapping>(); + mAndroidSettings = new Hashtable<String, BaseMapping>(); + + // Add all the mappings. + addMapping(new ScreenTimeoutMapping()); + addMapping(new WallpaperMapping(mContext)); + } + + void addMapping(BaseMapping mapping) { + String[] props = mapping.getGeckoSettings(); + for (int i = 0; i < props.length; i++) { + mGeckoSettings.put(props[i], mapping); + } + + props = mapping.getAndroidSettings(); + for (int i = 0; i < props.length; i++) { + mAndroidSettings.put(props[i], mapping); + } + } + + void destroy() { + EventDispatcher.getInstance() + .unregisterGeckoThreadListener(this, + "Settings:Change"); + mGeckoSettings.clear(); + mGeckoSettings = null; + mAndroidSettings.clear(); + mAndroidSettings = null; + mContext.getContentResolver().unregisterContentObserver(this); + } + + public void handleMessage(String event, JSONObject message) { + Log.w(LOGTAG, "Received " + event); + + try { + String setting = message.getString("setting"); + BaseMapping mapping = mGeckoSettings.get(setting); + if (mapping != null) { + Log.d(LOGTAG, "Changing gecko setting " + setting); + mapping.onGeckoChange(setting, message); + } else { + Log.d(LOGTAG, "No gecko mapping registered for " + setting); + } + } catch(Exception ex) { + Log.d(LOGTAG, "Error getting setting name", ex); + } + } + + // ContentObserver, see + // http://developer.android.com/reference/android/database/ContentObserver.html + @Override + public boolean deliverSelfNotifications() { + return false; + } + + @Override + public void onChange(boolean selfChange) { + onChange(selfChange, null); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + super.onChange(selfChange); + Log.d(LOGTAG, "Settings change detected uri=" + uri); + BaseMapping mapping = mAndroidSettings.get(uri.toString()); + if (mapping != null) { + Log.d(LOGTAG, "Changing android setting " + uri); + mapping.onAndroidChange(uri); + } else { + Log.d(LOGTAG, "No android mapping registered for " + uri); + } + } + +}
--- a/mobile/android/b2gdroid/components/MessagesBridge.jsm +++ b/mobile/android/b2gdroid/components/MessagesBridge.jsm @@ -1,45 +1,54 @@ /* 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.EXPORTED_SYMBOLS = ["MessagesBridge"]; const { classes: Cc, interfaces: Ci, utils: Cu } = Components; +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/SystemAppProxy.jsm"); +Cu.import("resource://gre/modules/Messaging.jsm"); + +XPCOMUtils.defineLazyServiceGetter(this, "settings", + "@mozilla.org/settingsService;1", + "nsISettingsService"); // This module receives messages from Launcher.java as observer notifications. +// It also listens for settings changes to relay them back to Android. function debug() { dump("-*- MessagesBridge " + Array.slice(arguments) + "\n"); } +function getWindow() { + return SystemAppProxy.getFrame().contentWindow || + Services.wm.getMostRecentWindow("navigator:browser"); +} + +// To prevent roundtrips like android -> gecko -> android we keep track of +// in flight setting changes. +let _blockedSettings = new Set(); + this.MessagesBridge = { init: function() { - Services.obs.addObserver(this, "Android:Launcher", false); + Services.obs.addObserver(this.onAndroidMessage, "Android:Launcher", false); + Services.obs.addObserver(this.onAndroidSetting, "Android:Setting", false); + Services.obs.addObserver(this.onSettingChange, "mozsettings-changed", false); Services.obs.addObserver(this, "xpcom-shutdown", false); }, - observe: function(aSubject, aTopic, aData) { - if (aTopic == "xpcom-shutdown") { - Services.obs.removeObserver(this, "Android:Launcher"); - Services.obs.removeObserver(this, "xpcom-shutdown"); - } - - if (aTopic != "Android:Launcher") { - return; - } - + onAndroidMessage: function(aSubject, aTopic, aData) { let data = JSON.parse(aData); debug(`Got Android:Launcher message ${data.action}`); - let window = SystemAppProxy.getFrame().contentWindow; + let window = getWindow(); switch (data.action) { case "screen_on": case "screen_off": // In both cases, make it look like pressing the power button // by dispatching keydown & keyup on the system app window. window.dispatchEvent(new window.KeyboardEvent("keydown", { key: "Power" })); window.dispatchEvent(new window.KeyboardEvent("keyup", { key: "Power" })); break; @@ -48,12 +57,63 @@ this.MessagesBridge = { data: { type: "url", url: data.url } }); break; case "home-key": window.dispatchEvent(new window.KeyboardEvent("keydown", { key: "Home" })); window.dispatchEvent(new window.KeyboardEvent("keyup", { key: "Home" })); break; } + }, + + onAndroidSetting: function(aSubject, aTopic, aData) { + let data = JSON.parse(aData); + let lock = settings.createLock(); + let key = Object.keys(data)[0]; + debug(`Got Android:Setting message ${key} -> ${data[key]}`); + // Don't relay back to android the same setting change. + _blockedSettings.add(key); + lock.set(key, data[key], null); + }, + + onSettingChange: function(aSubject, aTopic, aData) { + if ("wrappedJSObject" in aSubject) { + aSubject = aSubject.wrappedJSObject; + } + if (aSubject) { + debug("Got setting change: " + aSubject.key + " -> " + aSubject.value); + + if (_blockedSettings.has(aSubject.key)) { + _blockedSettings.delete(aSubject.key); + debug("Rejecting blocked setting change for " + aSubject.key); + return; + } + + let window = getWindow(); + + if (aSubject.value instanceof window.Blob) { + debug(aSubject.key + " is a Blob"); + let reader = new window.FileReader(); + reader.readAsDataURL(aSubject.value); + reader.onloadend = function() { + Messaging.sendRequest({ type: "Settings:Change", + setting: aSubject.key, + value: reader.result }); + } + } else { + Messaging.sendRequest({ type: "Settings:Change", + setting: aSubject.key, + value: aSubject.value }); + } + } + }, + + observe: function(aSubject, aTopic, aData) { + if (aTopic == "xpcom-shutdown") { + Services.obs.removeObserver(this.onAndroidMessage, "Android:Launcher"); + Services.obs.removeObserver(this.onAndroidSetting, "Android:Setting"); + Services.obs.removeObserver(this.onSettingChange, "mozsettings-changed"); + Services.obs.removeObserver(this, "xpcom-shutdown"); + } } } this.MessagesBridge.init();