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 {
CompatHandler(Looper looper)71         public CompatHandler(Looper looper) {
72             super(looper, null, true);
73         }
74 
75         @Override
handleMessage(Message msg)76         public void handleMessage(Message msg) {
77             switch (msg.what) {
78                 case MSG_WRITE:
79                     saveCompatModes();
80                     break;
81             }
82         }
83     };
84 
CompatModePackages(ActivityManagerService service, File systemDir, Handler handler)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 
getPackages()144     public HashMap<String, Integer> getPackages() {
145         return mPackages;
146     }
147 
getPackageFlags(String packageName)148     private int getPackageFlags(String packageName) {
149         Integer flags = mPackages.get(packageName);
150         return flags != null ? flags : 0;
151     }
152 
handlePackageDataClearedLocked(String packageName)153     public void handlePackageDataClearedLocked(String packageName) {
154         // User has explicitly asked to clear all associated data.
155         removePackage(packageName);
156     }
157 
handlePackageUninstalledLocked(String packageName)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 
removePackage(String packageName)164     private void removePackage(String packageName) {
165         if (mPackages.containsKey(packageName)) {
166             mPackages.remove(packageName);
167             scheduleWrite();
168         }
169     }
170 
handlePackageAddedLocked(String packageName, boolean updated)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 
scheduleWrite()194     private void scheduleWrite() {
195         mHandler.removeMessages(MSG_WRITE);
196         Message msg = mHandler.obtainMessage(MSG_WRITE);
197         mHandler.sendMessageDelayed(msg, 10000);
198     }
199 
compatibilityInfoForPackageLocked(ApplicationInfo ai)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 
computeCompatModeLocked(ApplicationInfo ai)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 
getFrontActivityAskCompatModeLocked()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 
getPackageAskCompatModeLocked(String packageName)232     public boolean getPackageAskCompatModeLocked(String packageName) {
233         return (getPackageFlags(packageName)&COMPAT_FLAG_DONT_ASK) == 0;
234     }
235 
getPackageNotifyUnsupportedZoomLocked(String packageName)236     public boolean getPackageNotifyUnsupportedZoomLocked(String packageName) {
237         return (getPackageFlags(packageName)&UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY) == 0;
238     }
239 
setFrontActivityAskCompatModeLocked(boolean ask)240     public void setFrontActivityAskCompatModeLocked(boolean ask) {
241         ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked();
242         if (r != null) {
243             setPackageAskCompatModeLocked(r.packageName, ask);
244         }
245     }
246 
setPackageAskCompatModeLocked(String packageName, boolean ask)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 
setPackageNotifyUnsupportedZoomLocked(String packageName, boolean notify)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 
getFrontActivityScreenCompatModeLocked()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 
setFrontActivityScreenCompatModeLocked(int mode)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 
getPackageScreenCompatModeLocked(String packageName)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 
setPackageScreenCompatModeLocked(String packageName, int mode)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 
setPackageScreenCompatModeLocked(ApplicationInfo ai, int mode)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 
saveCompatModes()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