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