package com.android.server.am;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.HashMap;
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.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.AtomicFile;
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 = ActivityManagerService.FIRST_COMPAT_MODE_MSG;
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, 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, 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, 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, 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);
}
}
}
}