Home | History | Annotate | Download | only in am
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.am;
     18 
     19 import static com.android.server.am.ActivityManagerDebugConfig.*;
     20 import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
     21 
     22 import java.io.File;
     23 import java.io.FileInputStream;
     24 import java.io.FileOutputStream;
     25 import java.nio.charset.StandardCharsets;
     26 import java.util.HashMap;
     27 import java.util.Iterator;
     28 import java.util.Map;
     29 
     30 import org.xmlpull.v1.XmlPullParser;
     31 import org.xmlpull.v1.XmlPullParserException;
     32 import org.xmlpull.v1.XmlSerializer;
     33 
     34 import com.android.internal.util.FastXmlSerializer;
     35 
     36 import android.app.ActivityManager;
     37 import android.app.AppGlobals;
     38 import android.content.pm.ApplicationInfo;
     39 import android.content.pm.IPackageManager;
     40 import android.content.res.CompatibilityInfo;
     41 import android.content.res.Configuration;
     42 import android.os.Handler;
     43 import android.os.Looper;
     44 import android.os.Message;
     45 import android.os.RemoteException;
     46 import android.util.AtomicFile;
     47 import android.util.Slog;
     48 import android.util.Xml;
     49 
     50 public final class CompatModePackages {
     51     private static final String TAG = TAG_WITH_CLASS_NAME ? "CompatModePackages" : TAG_AM;
     52     private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
     53 
     54     private final ActivityManagerService mService;
     55     private final AtomicFile mFile;
     56 
     57     // Compatibility state: no longer ask user to select the mode.
     58     public static final int COMPAT_FLAG_DONT_ASK = 1<<0;
     59     // Compatibility state: compatibility mode is enabled.
     60     public static final int COMPAT_FLAG_ENABLED = 1<<1;
     61 
     62     private final HashMap<String, Integer> mPackages = new HashMap<String, Integer>();
     63 
     64     private static final int MSG_WRITE = ActivityManagerService.FIRST_COMPAT_MODE_MSG;
     65 
     66     private final CompatHandler mHandler;
     67 
     68     private final class CompatHandler extends Handler {
     69         public CompatHandler(Looper looper) {
     70             super(looper, null, true);
     71         }
     72 
     73         @Override
     74         public void handleMessage(Message msg) {
     75             switch (msg.what) {
     76                 case MSG_WRITE:
     77                     saveCompatModes();
     78                     break;
     79             }
     80         }
     81     };
     82 
     83     public CompatModePackages(ActivityManagerService service, File systemDir, Handler handler) {
     84         mService = service;
     85         mFile = new AtomicFile(new File(systemDir, "packages-compat.xml"), "compat-mode");
     86         mHandler = new CompatHandler(handler.getLooper());
     87 
     88         FileInputStream fis = null;
     89         try {
     90             fis = mFile.openRead();
     91             XmlPullParser parser = Xml.newPullParser();
     92             parser.setInput(fis, StandardCharsets.UTF_8.name());
     93             int eventType = parser.getEventType();
     94             while (eventType != XmlPullParser.START_TAG &&
     95                     eventType != XmlPullParser.END_DOCUMENT) {
     96                 eventType = parser.next();
     97             }
     98             if (eventType == XmlPullParser.END_DOCUMENT) {
     99                 return;
    100             }
    101 
    102             String tagName = parser.getName();
    103             if ("compat-packages".equals(tagName)) {
    104                 eventType = parser.next();
    105                 do {
    106                     if (eventType == XmlPullParser.START_TAG) {
    107                         tagName = parser.getName();
    108                         if (parser.getDepth() == 2) {
    109                             if ("pkg".equals(tagName)) {
    110                                 String pkg = parser.getAttributeValue(null, "name");
    111                                 if (pkg != null) {
    112                                     String mode = parser.getAttributeValue(null, "mode");
    113                                     int modeInt = 0;
    114                                     if (mode != null) {
    115                                         try {
    116                                             modeInt = Integer.parseInt(mode);
    117                                         } catch (NumberFormatException e) {
    118                                         }
    119                                     }
    120                                     mPackages.put(pkg, modeInt);
    121                                 }
    122                             }
    123                         }
    124                     }
    125                     eventType = parser.next();
    126                 } while (eventType != XmlPullParser.END_DOCUMENT);
    127             }
    128         } catch (XmlPullParserException e) {
    129             Slog.w(TAG, "Error reading compat-packages", e);
    130         } catch (java.io.IOException e) {
    131             if (fis != null) Slog.w(TAG, "Error reading compat-packages", e);
    132         } finally {
    133             if (fis != null) {
    134                 try {
    135                     fis.close();
    136                 } catch (java.io.IOException e1) {
    137                 }
    138             }
    139         }
    140     }
    141 
    142     public HashMap<String, Integer> getPackages() {
    143         return mPackages;
    144     }
    145 
    146     private int getPackageFlags(String packageName) {
    147         Integer flags = mPackages.get(packageName);
    148         return flags != null ? flags : 0;
    149     }
    150 
    151     public void handlePackageDataClearedLocked(String packageName) {
    152         // User has explicitly asked to clear all associated data.
    153         removePackage(packageName);
    154     }
    155 
    156     public void handlePackageUninstalledLocked(String packageName) {
    157         // Clear settings when app is uninstalled since this is an explicit
    158         // signal from the user to remove the app and all associated data.
    159         removePackage(packageName);
    160     }
    161 
    162     private void removePackage(String packageName) {
    163         if (mPackages.containsKey(packageName)) {
    164             mPackages.remove(packageName);
    165             scheduleWrite();
    166         }
    167     }
    168 
    169     public void handlePackageAddedLocked(String packageName, boolean updated) {
    170         ApplicationInfo ai = null;
    171         try {
    172             ai = AppGlobals.getPackageManager().getApplicationInfo(packageName, 0, 0);
    173         } catch (RemoteException e) {
    174         }
    175         if (ai == null) {
    176             return;
    177         }
    178         CompatibilityInfo ci = compatibilityInfoForPackageLocked(ai);
    179         final boolean mayCompat = !ci.alwaysSupportsScreen()
    180                 && !ci.neverSupportsScreen();
    181 
    182         if (updated) {
    183             // Update -- if the app no longer can run in compat mode, clear
    184             // any current settings for it.
    185             if (!mayCompat && mPackages.containsKey(packageName)) {
    186                 mPackages.remove(packageName);
    187                 scheduleWrite();
    188             }
    189         }
    190     }
    191 
    192     private void scheduleWrite() {
    193         mHandler.removeMessages(MSG_WRITE);
    194         Message msg = mHandler.obtainMessage(MSG_WRITE);
    195         mHandler.sendMessageDelayed(msg, 10000);
    196     }
    197 
    198     public CompatibilityInfo compatibilityInfoForPackageLocked(ApplicationInfo ai) {
    199         final Configuration globalConfig = mService.getGlobalConfiguration();
    200         CompatibilityInfo ci = new CompatibilityInfo(ai, globalConfig.screenLayout,
    201                 globalConfig.smallestScreenWidthDp,
    202                 (getPackageFlags(ai.packageName)&COMPAT_FLAG_ENABLED) != 0);
    203         //Slog.i(TAG, "*********** COMPAT FOR PKG " + ai.packageName + ": " + ci);
    204         return ci;
    205     }
    206 
    207     public int computeCompatModeLocked(ApplicationInfo ai) {
    208         final boolean enabled = (getPackageFlags(ai.packageName)&COMPAT_FLAG_ENABLED) != 0;
    209         final Configuration globalConfig = mService.getGlobalConfiguration();
    210         final CompatibilityInfo info = new CompatibilityInfo(ai, globalConfig.screenLayout,
    211                 globalConfig.smallestScreenWidthDp, enabled);
    212         if (info.alwaysSupportsScreen()) {
    213             return ActivityManager.COMPAT_MODE_NEVER;
    214         }
    215         if (info.neverSupportsScreen()) {
    216             return ActivityManager.COMPAT_MODE_ALWAYS;
    217         }
    218         return enabled ? ActivityManager.COMPAT_MODE_ENABLED
    219                 : ActivityManager.COMPAT_MODE_DISABLED;
    220     }
    221 
    222     public boolean getFrontActivityAskCompatModeLocked() {
    223         ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked();
    224         if (r == null) {
    225             return false;
    226         }
    227         return getPackageAskCompatModeLocked(r.packageName);
    228     }
    229 
    230     public boolean getPackageAskCompatModeLocked(String packageName) {
    231         return (getPackageFlags(packageName)&COMPAT_FLAG_DONT_ASK) == 0;
    232     }
    233 
    234     public void setFrontActivityAskCompatModeLocked(boolean ask) {
    235         ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked();
    236         if (r != null) {
    237             setPackageAskCompatModeLocked(r.packageName, ask);
    238         }
    239     }
    240 
    241     public void setPackageAskCompatModeLocked(String packageName, boolean ask) {
    242         setPackageFlagLocked(packageName, COMPAT_FLAG_DONT_ASK, ask);
    243     }
    244 
    245     private void setPackageFlagLocked(String packageName, int flag, boolean set) {
    246         final int curFlags = getPackageFlags(packageName);
    247         final int newFlags = set ? (curFlags & ~flag) : (curFlags | flag);
    248         if (curFlags != newFlags) {
    249             if (newFlags != 0) {
    250                 mPackages.put(packageName, newFlags);
    251             } else {
    252                 mPackages.remove(packageName);
    253             }
    254             scheduleWrite();
    255         }
    256     }
    257 
    258     public int getFrontActivityScreenCompatModeLocked() {
    259         ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked();
    260         if (r == null) {
    261             return ActivityManager.COMPAT_MODE_UNKNOWN;
    262         }
    263         return computeCompatModeLocked(r.info.applicationInfo);
    264     }
    265 
    266     public void setFrontActivityScreenCompatModeLocked(int mode) {
    267         ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked();
    268         if (r == null) {
    269             Slog.w(TAG, "setFrontActivityScreenCompatMode failed: no top activity");
    270             return;
    271         }
    272         setPackageScreenCompatModeLocked(r.info.applicationInfo, mode);
    273     }
    274 
    275     public int getPackageScreenCompatModeLocked(String packageName) {
    276         ApplicationInfo ai = null;
    277         try {
    278             ai = AppGlobals.getPackageManager().getApplicationInfo(packageName, 0, 0);
    279         } catch (RemoteException e) {
    280         }
    281         if (ai == null) {
    282             return ActivityManager.COMPAT_MODE_UNKNOWN;
    283         }
    284         return computeCompatModeLocked(ai);
    285     }
    286 
    287     public void setPackageScreenCompatModeLocked(String packageName, int mode) {
    288         ApplicationInfo ai = null;
    289         try {
    290             ai = AppGlobals.getPackageManager().getApplicationInfo(packageName, 0, 0);
    291         } catch (RemoteException e) {
    292         }
    293         if (ai == null) {
    294             Slog.w(TAG, "setPackageScreenCompatMode failed: unknown package " + packageName);
    295             return;
    296         }
    297         setPackageScreenCompatModeLocked(ai, mode);
    298     }
    299 
    300     private void setPackageScreenCompatModeLocked(ApplicationInfo ai, int mode) {
    301         final String packageName = ai.packageName;
    302 
    303         int curFlags = getPackageFlags(packageName);
    304 
    305         boolean enable;
    306         switch (mode) {
    307             case ActivityManager.COMPAT_MODE_DISABLED:
    308                 enable = false;
    309                 break;
    310             case ActivityManager.COMPAT_MODE_ENABLED:
    311                 enable = true;
    312                 break;
    313             case ActivityManager.COMPAT_MODE_TOGGLE:
    314                 enable = (curFlags&COMPAT_FLAG_ENABLED) == 0;
    315                 break;
    316             default:
    317                 Slog.w(TAG, "Unknown screen compat mode req #" + mode + "; ignoring");
    318                 return;
    319         }
    320 
    321         int newFlags = curFlags;
    322         if (enable) {
    323             newFlags |= COMPAT_FLAG_ENABLED;
    324         } else {
    325             newFlags &= ~COMPAT_FLAG_ENABLED;
    326         }
    327 
    328         CompatibilityInfo ci = compatibilityInfoForPackageLocked(ai);
    329         if (ci.alwaysSupportsScreen()) {
    330             Slog.w(TAG, "Ignoring compat mode change of " + packageName
    331                     + "; compatibility never needed");
    332             newFlags = 0;
    333         }
    334         if (ci.neverSupportsScreen()) {
    335             Slog.w(TAG, "Ignoring compat mode change of " + packageName
    336                     + "; compatibility always needed");
    337             newFlags = 0;
    338         }
    339 
    340         if (newFlags != curFlags) {
    341             if (newFlags != 0) {
    342                 mPackages.put(packageName, newFlags);
    343             } else {
    344                 mPackages.remove(packageName);
    345             }
    346 
    347             // Need to get compatibility info in new state.
    348             ci = compatibilityInfoForPackageLocked(ai);
    349 
    350             scheduleWrite();
    351 
    352             final ActivityStack stack = mService.getFocusedStack();
    353             ActivityRecord starting = stack.restartPackage(packageName);
    354 
    355             // Tell all processes that loaded this package about the change.
    356             for (int i=mService.mLruProcesses.size()-1; i>=0; i--) {
    357                 ProcessRecord app = mService.mLruProcesses.get(i);
    358                 if (!app.pkgList.containsKey(packageName)) {
    359                     continue;
    360                 }
    361                 try {
    362                     if (app.thread != null) {
    363                         if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Sending to proc "
    364                                 + app.processName + " new compat " + ci);
    365                         app.thread.updatePackageCompatibilityInfo(packageName, ci);
    366                     }
    367                 } catch (Exception e) {
    368                 }
    369             }
    370 
    371             if (starting != null) {
    372                 starting.ensureActivityConfiguration(0 /* globalChanges */,
    373                         false /* preserveWindow */);
    374                 // And we need to make sure at this point that all other activities
    375                 // are made visible with the correct configuration.
    376                 stack.ensureActivitiesVisibleLocked(starting, 0, !PRESERVE_WINDOWS);
    377             }
    378         }
    379     }
    380 
    381     void saveCompatModes() {
    382         HashMap<String, Integer> pkgs;
    383         synchronized (mService) {
    384             pkgs = new HashMap<String, Integer>(mPackages);
    385         }
    386 
    387         FileOutputStream fos = null;
    388 
    389         try {
    390             fos = mFile.startWrite();
    391             XmlSerializer out = new FastXmlSerializer();
    392             out.setOutput(fos, StandardCharsets.UTF_8.name());
    393             out.startDocument(null, true);
    394             out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
    395             out.startTag(null, "compat-packages");
    396 
    397             final IPackageManager pm = AppGlobals.getPackageManager();
    398             final Configuration globalConfig = mService.getGlobalConfiguration();
    399             final int screenLayout = globalConfig.screenLayout;
    400             final int smallestScreenWidthDp = globalConfig.smallestScreenWidthDp;
    401             final Iterator<Map.Entry<String, Integer>> it = pkgs.entrySet().iterator();
    402             while (it.hasNext()) {
    403                 Map.Entry<String, Integer> entry = it.next();
    404                 String pkg = entry.getKey();
    405                 int mode = entry.getValue();
    406                 if (mode == 0) {
    407                     continue;
    408                 }
    409                 ApplicationInfo ai = null;
    410                 try {
    411                     ai = pm.getApplicationInfo(pkg, 0, 0);
    412                 } catch (RemoteException e) {
    413                 }
    414                 if (ai == null) {
    415                     continue;
    416                 }
    417                 CompatibilityInfo info = new CompatibilityInfo(ai, screenLayout,
    418                         smallestScreenWidthDp, false);
    419                 if (info.alwaysSupportsScreen()) {
    420                     continue;
    421                 }
    422                 if (info.neverSupportsScreen()) {
    423                     continue;
    424                 }
    425                 out.startTag(null, "pkg");
    426                 out.attribute(null, "name", pkg);
    427                 out.attribute(null, "mode", Integer.toString(mode));
    428                 out.endTag(null, "pkg");
    429             }
    430 
    431             out.endTag(null, "compat-packages");
    432             out.endDocument();
    433 
    434             mFile.finishWrite(fos);
    435         } catch (java.io.IOException e1) {
    436             Slog.w(TAG, "Error writing compat packages", e1);
    437             if (fos != null) {
    438                 mFile.failWrite(fos);
    439             }
    440         }
    441     }
    442 }
    443