| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.am; |
| |
| import static com.android.server.am.ActivityManagerDebugConfig.*; |
| import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.nio.charset.StandardCharsets; |
| 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.ApplicationInfo; |
| import android.content.pm.IPackageManager; |
| import android.content.res.CompatibilityInfo; |
| import android.content.res.Configuration; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.RemoteException; |
| import android.util.AtomicFile; |
| import android.util.Slog; |
| import android.util.Xml; |
| |
| public final class CompatModePackages { |
| private static final String TAG = TAG_WITH_CLASS_NAME ? "CompatModePackages" : TAG_AM; |
| private static final String TAG_CONFIGURATION = TAG + POSTFIX_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 CompatHandler mHandler; |
| |
| private final class CompatHandler extends Handler { |
| public CompatHandler(Looper looper) { |
| super(looper, null, true); |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case MSG_WRITE: |
| saveCompatModes(); |
| break; |
| } |
| } |
| }; |
| |
| public CompatModePackages(ActivityManagerService service, File systemDir, Handler handler) { |
| mService = service; |
| mFile = new AtomicFile(new File(systemDir, "packages-compat.xml"), "compat-mode"); |
| mHandler = new CompatHandler(handler.getLooper()); |
| |
| FileInputStream fis = null; |
| try { |
| fis = mFile.openRead(); |
| XmlPullParser parser = Xml.newPullParser(); |
| parser.setInput(fis, StandardCharsets.UTF_8.name()); |
| int eventType = parser.getEventType(); |
| while (eventType != XmlPullParser.START_TAG && |
| eventType != XmlPullParser.END_DOCUMENT) { |
| eventType = parser.next(); |
| } |
| if (eventType == XmlPullParser.END_DOCUMENT) { |
| return; |
| } |
| |
| 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 handlePackageDataClearedLocked(String packageName) { |
| // User has explicitly asked to clear all associated data. |
| removePackage(packageName); |
| } |
| |
| public void handlePackageUninstalledLocked(String packageName) { |
| // Clear settings when app is uninstalled since this is an explicit |
| // signal from the user to remove the app and all associated data. |
| removePackage(packageName); |
| } |
| |
| private void removePackage(String packageName) { |
| if (mPackages.containsKey(packageName)) { |
| mPackages.remove(packageName); |
| scheduleWrite(); |
| } |
| } |
| |
| 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); |
| scheduleWrite(); |
| } |
| } |
| } |
| |
| private void scheduleWrite() { |
| mHandler.removeMessages(MSG_WRITE); |
| Message msg = mHandler.obtainMessage(MSG_WRITE); |
| mHandler.sendMessageDelayed(msg, 10000); |
| } |
| |
| public CompatibilityInfo compatibilityInfoForPackageLocked(ApplicationInfo ai) { |
| final Configuration globalConfig = mService.getGlobalConfiguration(); |
| CompatibilityInfo ci = new CompatibilityInfo(ai, globalConfig.screenLayout, |
| globalConfig.smallestScreenWidthDp, |
| (getPackageFlags(ai.packageName)&COMPAT_FLAG_ENABLED) != 0); |
| //Slog.i(TAG, "*********** COMPAT FOR PKG " + ai.packageName + ": " + ci); |
| return ci; |
| } |
| |
| public int computeCompatModeLocked(ApplicationInfo ai) { |
| final boolean enabled = (getPackageFlags(ai.packageName)&COMPAT_FLAG_ENABLED) != 0; |
| final Configuration globalConfig = mService.getGlobalConfiguration(); |
| final CompatibilityInfo info = new CompatibilityInfo(ai, globalConfig.screenLayout, |
| globalConfig.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 getPackageAskCompatModeLocked(String packageName) { |
| return (getPackageFlags(packageName)&COMPAT_FLAG_DONT_ASK) == 0; |
| } |
| |
| public void setPackageAskCompatModeLocked(String packageName, boolean ask) { |
| setPackageFlagLocked(packageName, COMPAT_FLAG_DONT_ASK, ask); |
| } |
| |
| private void setPackageFlagLocked(String packageName, int flag, boolean set) { |
| final int curFlags = getPackageFlags(packageName); |
| final int newFlags = set ? (curFlags & ~flag) : (curFlags | flag); |
| if (curFlags != newFlags) { |
| if (newFlags != 0) { |
| mPackages.put(packageName, newFlags); |
| } else { |
| mPackages.remove(packageName); |
| } |
| scheduleWrite(); |
| } |
| } |
| |
| 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); |
| } |
| |
| 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); |
| |
| scheduleWrite(); |
| |
| final ActivityStack stack = mService.mActivityTaskManager.getFocusedStack(); |
| ActivityRecord starting = stack.restartPackage(packageName); |
| |
| // 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.containsKey(packageName)) { |
| continue; |
| } |
| try { |
| if (app.thread != null) { |
| if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Sending to proc " |
| + app.processName + " new compat " + ci); |
| app.thread.updatePackageCompatibilityInfo(packageName, ci); |
| } |
| } catch (Exception e) { |
| } |
| } |
| |
| if (starting != null) { |
| starting.ensureActivityConfiguration(0 /* globalChanges */, |
| false /* preserveWindow */); |
| // And we need to make sure at this point that all other activities |
| // are made visible with the correct configuration. |
| stack.ensureActivitiesVisibleLocked(starting, 0, !PRESERVE_WINDOWS); |
| } |
| } |
| } |
| |
| 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, StandardCharsets.UTF_8.name()); |
| 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 Configuration globalConfig = mService.getGlobalConfiguration(); |
| final int screenLayout = globalConfig.screenLayout; |
| final int smallestScreenWidthDp = globalConfig.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); |
| } |
| } |
| } |
| } |