зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1207417 - Settings mapper to sync b2g and android configurations r=snorp
This commit is contained in:
Родитель
44b885ef4c
Коммит
b32ea8e77e
|
@ -8,6 +8,7 @@ JAVAFILES := \
|
||||||
src/main/java/org/mozilla/b2gdroid/Apps.java \
|
src/main/java/org/mozilla/b2gdroid/Apps.java \
|
||||||
src/main/java/org/mozilla/b2gdroid/Launcher.java \
|
src/main/java/org/mozilla/b2gdroid/Launcher.java \
|
||||||
src/main/java/org/mozilla/b2gdroid/ScreenStateObserver.java \
|
src/main/java/org/mozilla/b2gdroid/ScreenStateObserver.java \
|
||||||
|
src/main/java/org/mozilla/b2gdroid/SettingsMapper.java \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
# The GeckoView consuming APK depends on the GeckoView JAR files. There are two
|
# The GeckoView consuming APK depends on the GeckoView JAR files. There are two
|
||||||
|
|
|
@ -49,6 +49,9 @@
|
||||||
<!-- Needed to disable the default lockscreen -->
|
<!-- Needed to disable the default lockscreen -->
|
||||||
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
|
<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"
|
<application android:label="@string/b2g"
|
||||||
android:icon="@drawable/b2g"
|
android:icon="@drawable/b2g"
|
||||||
android:logo="@drawable/b2g"
|
android:logo="@drawable/b2g"
|
||||||
|
|
|
@ -33,6 +33,7 @@ import org.mozilla.gecko.util.GeckoEventListener;
|
||||||
|
|
||||||
import org.mozilla.b2gdroid.ScreenStateObserver;
|
import org.mozilla.b2gdroid.ScreenStateObserver;
|
||||||
import org.mozilla.b2gdroid.Apps;
|
import org.mozilla.b2gdroid.Apps;
|
||||||
|
import org.mozilla.b2gdroid.SettingsMapper;
|
||||||
|
|
||||||
public class Launcher extends Activity
|
public class Launcher extends Activity
|
||||||
implements GeckoEventListener, ContextGetter {
|
implements GeckoEventListener, ContextGetter {
|
||||||
|
@ -41,6 +42,7 @@ public class Launcher extends Activity
|
||||||
private ContactService mContactService;
|
private ContactService mContactService;
|
||||||
private ScreenStateObserver mScreenStateObserver;
|
private ScreenStateObserver mScreenStateObserver;
|
||||||
private Apps mApps;
|
private Apps mApps;
|
||||||
|
private SettingsMapper mSettings;
|
||||||
|
|
||||||
/** ContextGetter */
|
/** ContextGetter */
|
||||||
public Context getContext() {
|
public Context getContext() {
|
||||||
|
@ -58,6 +60,7 @@ public class Launcher extends Activity
|
||||||
GeckoBatteryManager.getInstance().start(this);
|
GeckoBatteryManager.getInstance().start(this);
|
||||||
mContactService = new ContactService(EventDispatcher.getInstance(), this);
|
mContactService = new ContactService(EventDispatcher.getInstance(), this);
|
||||||
mApps = new Apps(this);
|
mApps = new Apps(this);
|
||||||
|
mSettings = new SettingsMapper(this, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void hideSplashScreen() {
|
private void hideSplashScreen() {
|
||||||
|
@ -116,6 +119,7 @@ public class Launcher extends Activity
|
||||||
|
|
||||||
mContactService.destroy();
|
mContactService.destroy();
|
||||||
mApps.destroy();
|
mApps.destroy();
|
||||||
|
mSettings.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -6,35 +6,44 @@ this.EXPORTED_SYMBOLS = ["MessagesBridge"];
|
||||||
|
|
||||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
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/Services.jsm");
|
||||||
Cu.import("resource://gre/modules/SystemAppProxy.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.
|
// This module receives messages from Launcher.java as observer notifications.
|
||||||
|
// It also listens for settings changes to relay them back to Android.
|
||||||
|
|
||||||
function debug() {
|
function debug() {
|
||||||
dump("-*- MessagesBridge " + Array.slice(arguments) + "\n");
|
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 = {
|
this.MessagesBridge = {
|
||||||
init: function() {
|
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);
|
Services.obs.addObserver(this, "xpcom-shutdown", false);
|
||||||
},
|
},
|
||||||
|
|
||||||
observe: function(aSubject, aTopic, aData) {
|
onAndroidMessage: 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
let data = JSON.parse(aData);
|
let data = JSON.parse(aData);
|
||||||
debug(`Got Android:Launcher message ${data.action}`);
|
debug(`Got Android:Launcher message ${data.action}`);
|
||||||
|
|
||||||
let window = SystemAppProxy.getFrame().contentWindow;
|
let window = getWindow();
|
||||||
switch (data.action) {
|
switch (data.action) {
|
||||||
case "screen_on":
|
case "screen_on":
|
||||||
case "screen_off":
|
case "screen_off":
|
||||||
|
@ -53,6 +62,57 @@ this.MessagesBridge = {
|
||||||
window.dispatchEvent(new window.KeyboardEvent("keyup", { key: "Home" }));
|
window.dispatchEvent(new window.KeyboardEvent("keyup", { key: "Home" }));
|
||||||
break;
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче