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