package com.android.server.am; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; 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.ActivityInfo; 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; // Compatibility state: no longer ask user to select the mode. public static final int COMPAT_FLAG_DONT_ASK = 1<<0; // Compatibility state: compatibility mode is enabled. public static final int COMPAT_FLAG_ENABLED = 1<<1; private final HashMap<String, Integer> mPackages = new HashMap<String, Integer>(); 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) { String mode = parser.getAttributeValue(null, "mode"); int modeInt = 0; if (mode != null) { try { modeInt = Integer.parseInt(mode); } catch (NumberFormatException e) { } } mPackages.put(pkg, modeInt); } } } } 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 HashMap<String, Integer> getPackages() { return mPackages; } private int getPackageFlags(String packageName) { Integer flags = mPackages.get(packageName); return flags != null ? flags : 0; } public void handlePackageAddedLocked(String packageName, boolean updated) { ApplicationInfo ai = null; try { ai = AppGlobals.getPackageManager().getApplicationInfo(packageName, 0); } catch (RemoteException e) { } if (ai == null) { return; } CompatibilityInfo ci = compatibilityInfoForPackageLocked(ai); final boolean mayCompat = !ci.alwaysSupportsScreen() && !ci.neverSupportsScreen(); if (updated) { // Update -- if the app no longer can run in compat mode, clear // any current settings for it. if (!mayCompat && mPackages.containsKey(packageName)) { mPackages.remove(packageName); mHandler.removeMessages(MSG_WRITE); Message msg = mHandler.obtainMessage(MSG_WRITE); mHandler.sendMessageDelayed(msg, 10000); } } } public CompatibilityInfo compatibilityInfoForPackageLocked(ApplicationInfo ai) { CompatibilityInfo ci = new CompatibilityInfo(ai, mService.mConfiguration.screenLayout, mService.mConfiguration.smallestScreenWidthDp, (getPackageFlags(ai.packageName)&COMPAT_FLAG_ENABLED) != 0); //Slog.i(TAG, "*********** COMPAT FOR PKG " + ai.packageName + ": " + ci); return ci; } public int computeCompatModeLocked(ApplicationInfo ai) { boolean enabled = (getPackageFlags(ai.packageName)&COMPAT_FLAG_ENABLED) != 0; CompatibilityInfo info = new CompatibilityInfo(ai, mService.mConfiguration.screenLayout, mService.mConfiguration.smallestScreenWidthDp, 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 boolean getFrontActivityAskCompatModeLocked() { ActivityRecord r = mService.mMainStack.topRunningActivityLocked(null); if (r == null) { return false; } return getPackageAskCompatModeLocked(r.packageName); } public boolean getPackageAskCompatModeLocked(String packageName) { return (getPackageFlags(packageName)&COMPAT_FLAG_DONT_ASK) == 0; } public void setFrontActivityAskCompatModeLocked(boolean ask) { ActivityRecord r = mService.mMainStack.topRunningActivityLocked(null); if (r != null) { setPackageAskCompatModeLocked(r.packageName, ask); } } public void setPackageAskCompatModeLocked(String packageName, boolean ask) { int curFlags = getPackageFlags(packageName); int newFlags = ask ? (curFlags&~COMPAT_FLAG_DONT_ASK) : (curFlags|COMPAT_FLAG_DONT_ASK); if (curFlags != newFlags) { if (newFlags != 0) { mPackages.put(packageName, newFlags); } else { mPackages.remove(packageName); } mHandler.removeMessages(MSG_WRITE); Message msg = mHandler.obtainMessage(MSG_WRITE); mHandler.sendMessageDelayed(msg, 10000); } } 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; int curFlags = getPackageFlags(packageName); 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 = (curFlags&COMPAT_FLAG_ENABLED) == 0; break; default: Slog.w(TAG, "Unknown screen compat mode req #" + mode + "; ignoring"); return; } int newFlags = curFlags; if (enable) { newFlags |= COMPAT_FLAG_ENABLED; } else { newFlags &= ~COMPAT_FLAG_ENABLED; } CompatibilityInfo ci = compatibilityInfoForPackageLocked(ai); if (ci.alwaysSupportsScreen()) { Slog.w(TAG, "Ignoring compat mode change of " + packageName + "; compatibility never needed"); newFlags = 0; } if (ci.neverSupportsScreen()) { Slog.w(TAG, "Ignoring compat mode change of " + packageName + "; compatibility always needed"); newFlags = 0; } if (newFlags != curFlags) { if (newFlags != 0) { mPackages.put(packageName, newFlags); } else { mPackages.remove(packageName); } // Need to get compatibility info in new state. ci = compatibilityInfoForPackageLocked(ai); mHandler.removeMessages(MSG_WRITE); Message msg = mHandler.obtainMessage(MSG_WRITE); mHandler.sendMessageDelayed(msg, 10000); ActivityRecord starting = mService.mMainStack.topRunningActivityLocked(null); // All activities that came from the package 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; if (starting != null && a == starting && a.visible) { a.startFreezingScreenLocked(starting.app, ActivityInfo.CONFIG_SCREEN_LAYOUT); } } } // 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) { } } 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() { HashMap<String, Integer> pkgs; synchronized (mService) { pkgs = new HashMap<String, Integer>(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 int smallestScreenWidthDp = mService.mConfiguration.smallestScreenWidthDp; final Iterator<Map.Entry<String, Integer>> it = pkgs.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, Integer> entry = it.next(); String pkg = entry.getKey(); int mode = entry.getValue(); if (mode == 0) { continue; } ApplicationInfo ai = null; try { ai = pm.getApplicationInfo(pkg, 0); } catch (RemoteException e) { } if (ai == null) { continue; } CompatibilityInfo info = new CompatibilityInfo(ai, screenLayout, smallestScreenWidthDp, false); if (info.alwaysSupportsScreen()) { continue; } if (info.neverSupportsScreen()) { continue; } out.startTag(null, "pkg"); out.attribute(null, "name", pkg); out.attribute(null, "mode", Integer.toString(mode)); 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); } } } }