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