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