New compat mode front end: UI and persistence.
Adds a really crappy UI for toggling compat mode.
Persists compat mode selection across boots.
Turns on compat mode by default for newly installed apps.
Change-Id: Idc83494397bd17c41450bc9e9a05e4386c509399
diff --git a/services/java/com/android/server/am/CompatModePackages.java b/services/java/com/android/server/am/CompatModePackages.java
new file mode 100644
index 0000000..1faf8da
--- /dev/null
+++ b/services/java/com/android/server/am/CompatModePackages.java
@@ -0,0 +1,295 @@
+package com.android.server.am;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.util.HashSet;
+import java.util.Iterator;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import com.android.internal.os.AtomicFile;
+import com.android.internal.util.FastXmlSerializer;
+
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.res.CompatibilityInfo;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.util.Xml;
+
+public class CompatModePackages {
+ private final String TAG = ActivityManagerService.TAG;
+ private final boolean DEBUG_CONFIGURATION = ActivityManagerService.DEBUG_CONFIGURATION;
+
+ private final ActivityManagerService mService;
+ private final AtomicFile mFile;
+
+ private final HashSet<String> mPackages = new HashSet<String>();
+
+ private static final int MSG_WRITE = 1;
+
+ private final Handler mHandler = new Handler() {
+ @Override public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_WRITE:
+ saveCompatModes();
+ break;
+ default:
+ super.handleMessage(msg);
+ break;
+ }
+ }
+ };
+
+ public CompatModePackages(ActivityManagerService service, File systemDir) {
+ mService = service;
+ mFile = new AtomicFile(new File(systemDir, "packages-compat.xml"));
+
+ FileInputStream fis = null;
+ try {
+ fis = mFile.openRead();
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(fis, null);
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.START_TAG) {
+ eventType = parser.next();
+ }
+ String tagName = parser.getName();
+ if ("compat-packages".equals(tagName)) {
+ eventType = parser.next();
+ do {
+ if (eventType == XmlPullParser.START_TAG) {
+ tagName = parser.getName();
+ if (parser.getDepth() == 2) {
+ if ("pkg".equals(tagName)) {
+ String pkg = parser.getAttributeValue(null, "name");
+ if (pkg != null) {
+ mPackages.add(pkg);
+ }
+ }
+ }
+ }
+ eventType = parser.next();
+ } while (eventType != XmlPullParser.END_DOCUMENT);
+ }
+ } catch (XmlPullParserException e) {
+ Slog.w(TAG, "Error reading compat-packages", e);
+ } catch (java.io.IOException e) {
+ if (fis != null) Slog.w(TAG, "Error reading compat-packages", e);
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (java.io.IOException e1) {
+ }
+ }
+ }
+ }
+
+ public HashSet<String> getPackages() {
+ return mPackages;
+ }
+
+ public CompatibilityInfo compatibilityInfoForPackageLocked(ApplicationInfo ai) {
+ return new CompatibilityInfo(ai, mService.mConfiguration.screenLayout,
+ mPackages.contains(ai.packageName));
+ }
+
+ private int computeCompatModeLocked(ApplicationInfo ai) {
+ boolean enabled = mPackages.contains(ai.packageName);
+ CompatibilityInfo info = new CompatibilityInfo(ai,
+ mService.mConfiguration.screenLayout, enabled);
+ if (info.alwaysSupportsScreen()) {
+ return ActivityManager.COMPAT_MODE_NEVER;
+ }
+ if (info.neverSupportsScreen()) {
+ return ActivityManager.COMPAT_MODE_ALWAYS;
+ }
+ return enabled ? ActivityManager.COMPAT_MODE_ENABLED
+ : ActivityManager.COMPAT_MODE_DISABLED;
+ }
+
+ public int getFrontActivityScreenCompatModeLocked() {
+ ActivityRecord r = mService.mMainStack.topRunningActivityLocked(null);
+ if (r == null) {
+ return ActivityManager.COMPAT_MODE_UNKNOWN;
+ }
+ return computeCompatModeLocked(r.info.applicationInfo);
+ }
+
+ public void setFrontActivityScreenCompatModeLocked(int mode) {
+ ActivityRecord r = mService.mMainStack.topRunningActivityLocked(null);
+ if (r == null) {
+ Slog.w(TAG, "setFrontActivityScreenCompatMode failed: no top activity");
+ return;
+ }
+ setPackageScreenCompatModeLocked(r.info.applicationInfo, mode);
+ }
+
+ public int getPackageScreenCompatModeLocked(String packageName) {
+ ApplicationInfo ai = null;
+ try {
+ ai = AppGlobals.getPackageManager().getApplicationInfo(packageName, 0);
+ } catch (RemoteException e) {
+ }
+ if (ai == null) {
+ return ActivityManager.COMPAT_MODE_UNKNOWN;
+ }
+ return computeCompatModeLocked(ai);
+ }
+
+ public void setPackageScreenCompatModeLocked(String packageName, int mode) {
+ ApplicationInfo ai = null;
+ try {
+ ai = AppGlobals.getPackageManager().getApplicationInfo(packageName, 0);
+ } catch (RemoteException e) {
+ }
+ if (ai == null) {
+ Slog.w(TAG, "setPackageScreenCompatMode failed: unknown package " + packageName);
+ return;
+ }
+ setPackageScreenCompatModeLocked(ai, mode);
+ }
+
+ private void setPackageScreenCompatModeLocked(ApplicationInfo ai, int mode) {
+ final String packageName = ai.packageName;
+
+ boolean changed = false;
+ boolean enable;
+ switch (mode) {
+ case ActivityManager.COMPAT_MODE_DISABLED:
+ enable = false;
+ break;
+ case ActivityManager.COMPAT_MODE_ENABLED:
+ enable = true;
+ break;
+ case ActivityManager.COMPAT_MODE_TOGGLE:
+ enable = !mPackages.contains(packageName);
+ break;
+ default:
+ Slog.w(TAG, "Unknown screen compat mode req #" + mode + "; ignoring");
+ return;
+ }
+ if (enable) {
+ if (!mPackages.contains(packageName)) {
+ changed = true;
+ mPackages.add(packageName);
+ }
+ } else {
+ if (mPackages.contains(packageName)) {
+ changed = true;
+ mPackages.remove(packageName);
+ }
+ }
+ if (changed) {
+ CompatibilityInfo ci = compatibilityInfoForPackageLocked(ai);
+ if (ci.alwaysSupportsScreen()) {
+ Slog.w(TAG, "Ignoring compat mode change of " + packageName
+ + "; compatibility never needed");
+ return;
+ }
+ if (ci.neverSupportsScreen()) {
+ Slog.w(TAG, "Ignoring compat mode change of " + packageName
+ + "; compatibility always needed");
+ return;
+ }
+
+ mHandler.removeMessages(MSG_WRITE);
+ Message msg = mHandler.obtainMessage(MSG_WRITE);
+ mHandler.sendMessageDelayed(msg, 10000);
+
+ // Tell all processes that loaded this package about the change.
+ for (int i=mService.mLruProcesses.size()-1; i>=0; i--) {
+ ProcessRecord app = mService.mLruProcesses.get(i);
+ if (!app.pkgList.contains(packageName)) {
+ continue;
+ }
+ try {
+ if (app.thread != null) {
+ if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending to proc "
+ + app.processName + " new compat " + ci);
+ app.thread.updatePackageCompatibilityInfo(packageName, ci);
+ }
+ } catch (Exception e) {
+ }
+ }
+
+ // All activities that came from the packge must be
+ // restarted as if there was a config change.
+ for (int i=mService.mMainStack.mHistory.size()-1; i>=0; i--) {
+ ActivityRecord a = (ActivityRecord)mService.mMainStack.mHistory.get(i);
+ if (a.info.packageName.equals(packageName)) {
+ a.forceNewConfig = true;
+ }
+ }
+
+ ActivityRecord starting = mService.mMainStack.topRunningActivityLocked(null);
+ if (starting != null) {
+ mService.mMainStack.ensureActivityConfigurationLocked(starting, 0);
+ // And we need to make sure at this point that all other activities
+ // are made visible with the correct configuration.
+ mService.mMainStack.ensureActivitiesVisibleLocked(starting, 0);
+ }
+ }
+ }
+
+ void saveCompatModes() {
+ HashSet<String> pkgs;
+ synchronized (mService) {
+ pkgs = new HashSet<String>(mPackages);
+ }
+
+ FileOutputStream fos = null;
+
+ try {
+ fos = mFile.startWrite();
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(fos, "utf-8");
+ out.startDocument(null, true);
+ out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+ out.startTag(null, "compat-packages");
+
+ final IPackageManager pm = AppGlobals.getPackageManager();
+ final int screenLayout = mService.mConfiguration.screenLayout;
+ final Iterator<String> it = pkgs.iterator();
+ while (it.hasNext()) {
+ String pkg = it.next();
+ ApplicationInfo ai = null;
+ try {
+ ai = pm.getApplicationInfo(pkg, 0);
+ } catch (RemoteException e) {
+ }
+ if (ai == null) {
+ continue;
+ }
+ CompatibilityInfo info = new CompatibilityInfo(ai, screenLayout, false);
+ if (info.alwaysSupportsScreen()) {
+ continue;
+ }
+ if (info.neverSupportsScreen()) {
+ continue;
+ }
+ out.startTag(null, "pkg");
+ out.attribute(null, "name", pkg);
+ out.endTag(null, "pkg");
+ }
+
+ out.endTag(null, "compat-packages");
+ out.endDocument();
+
+ mFile.finishWrite(fos);
+ } catch (java.io.IOException e1) {
+ Slog.w(TAG, "Error writing compat packages", e1);
+ if (fos != null) {
+ mFile.failWrite(fos);
+ }
+ }
+ }
+}