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