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