• 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.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 
62     private final HashMap<String, Integer> mPackages = new HashMap<String, Integer>();
63 
64     private static final int MSG_WRITE = ActivityManagerService.FIRST_COMPAT_MODE_MSG;
65 
66     private final CompatHandler mHandler;
67 
68     private final class CompatHandler extends Handler {
CompatHandler(Looper looper)69         public CompatHandler(Looper looper) {
70             super(looper, null, true);
71         }
72 
73         @Override
handleMessage(Message msg)74         public void handleMessage(Message msg) {
75             switch (msg.what) {
76                 case MSG_WRITE:
77                     saveCompatModes();
78                     break;
79             }
80         }
81     };
82 
CompatModePackages(ActivityManagerService service, File systemDir, Handler handler)83     public CompatModePackages(ActivityManagerService service, File systemDir, Handler handler) {
84         mService = service;
85         mFile = new AtomicFile(new File(systemDir, "packages-compat.xml"), "compat-mode");
86         mHandler = new CompatHandler(handler.getLooper());
87 
88         FileInputStream fis = null;
89         try {
90             fis = mFile.openRead();
91             XmlPullParser parser = Xml.newPullParser();
92             parser.setInput(fis, StandardCharsets.UTF_8.name());
93             int eventType = parser.getEventType();
94             while (eventType != XmlPullParser.START_TAG &&
95                     eventType != XmlPullParser.END_DOCUMENT) {
96                 eventType = parser.next();
97             }
98             if (eventType == XmlPullParser.END_DOCUMENT) {
99                 return;
100             }
101 
102             String tagName = parser.getName();
103             if ("compat-packages".equals(tagName)) {
104                 eventType = parser.next();
105                 do {
106                     if (eventType == XmlPullParser.START_TAG) {
107                         tagName = parser.getName();
108                         if (parser.getDepth() == 2) {
109                             if ("pkg".equals(tagName)) {
110                                 String pkg = parser.getAttributeValue(null, "name");
111                                 if (pkg != null) {
112                                     String mode = parser.getAttributeValue(null, "mode");
113                                     int modeInt = 0;
114                                     if (mode != null) {
115                                         try {
116                                             modeInt = Integer.parseInt(mode);
117                                         } catch (NumberFormatException e) {
118                                         }
119                                     }
120                                     mPackages.put(pkg, modeInt);
121                                 }
122                             }
123                         }
124                     }
125                     eventType = parser.next();
126                 } while (eventType != XmlPullParser.END_DOCUMENT);
127             }
128         } catch (XmlPullParserException e) {
129             Slog.w(TAG, "Error reading compat-packages", e);
130         } catch (java.io.IOException e) {
131             if (fis != null) Slog.w(TAG, "Error reading compat-packages", e);
132         } finally {
133             if (fis != null) {
134                 try {
135                     fis.close();
136                 } catch (java.io.IOException e1) {
137                 }
138             }
139         }
140     }
141 
getPackages()142     public HashMap<String, Integer> getPackages() {
143         return mPackages;
144     }
145 
getPackageFlags(String packageName)146     private int getPackageFlags(String packageName) {
147         Integer flags = mPackages.get(packageName);
148         return flags != null ? flags : 0;
149     }
150 
handlePackageDataClearedLocked(String packageName)151     public void handlePackageDataClearedLocked(String packageName) {
152         // User has explicitly asked to clear all associated data.
153         removePackage(packageName);
154     }
155 
handlePackageUninstalledLocked(String packageName)156     public void handlePackageUninstalledLocked(String packageName) {
157         // Clear settings when app is uninstalled since this is an explicit
158         // signal from the user to remove the app and all associated data.
159         removePackage(packageName);
160     }
161 
removePackage(String packageName)162     private void removePackage(String packageName) {
163         if (mPackages.containsKey(packageName)) {
164             mPackages.remove(packageName);
165             scheduleWrite();
166         }
167     }
168 
handlePackageAddedLocked(String packageName, boolean updated)169     public void handlePackageAddedLocked(String packageName, boolean updated) {
170         ApplicationInfo ai = null;
171         try {
172             ai = AppGlobals.getPackageManager().getApplicationInfo(packageName, 0, 0);
173         } catch (RemoteException e) {
174         }
175         if (ai == null) {
176             return;
177         }
178         CompatibilityInfo ci = compatibilityInfoForPackageLocked(ai);
179         final boolean mayCompat = !ci.alwaysSupportsScreen()
180                 && !ci.neverSupportsScreen();
181 
182         if (updated) {
183             // Update -- if the app no longer can run in compat mode, clear
184             // any current settings for it.
185             if (!mayCompat && mPackages.containsKey(packageName)) {
186                 mPackages.remove(packageName);
187                 scheduleWrite();
188             }
189         }
190     }
191 
scheduleWrite()192     private void scheduleWrite() {
193         mHandler.removeMessages(MSG_WRITE);
194         Message msg = mHandler.obtainMessage(MSG_WRITE);
195         mHandler.sendMessageDelayed(msg, 10000);
196     }
197 
compatibilityInfoForPackageLocked(ApplicationInfo ai)198     public CompatibilityInfo compatibilityInfoForPackageLocked(ApplicationInfo ai) {
199         final Configuration globalConfig = mService.getGlobalConfiguration();
200         CompatibilityInfo ci = new CompatibilityInfo(ai, globalConfig.screenLayout,
201                 globalConfig.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         final boolean enabled = (getPackageFlags(ai.packageName)&COMPAT_FLAG_ENABLED) != 0;
209         final Configuration globalConfig = mService.getGlobalConfiguration();
210         final CompatibilityInfo info = new CompatibilityInfo(ai, globalConfig.screenLayout,
211                 globalConfig.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 
setFrontActivityAskCompatModeLocked(boolean ask)234     public void setFrontActivityAskCompatModeLocked(boolean ask) {
235         ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked();
236         if (r != null) {
237             setPackageAskCompatModeLocked(r.packageName, ask);
238         }
239     }
240 
setPackageAskCompatModeLocked(String packageName, boolean ask)241     public void setPackageAskCompatModeLocked(String packageName, boolean ask) {
242         setPackageFlagLocked(packageName, COMPAT_FLAG_DONT_ASK, ask);
243     }
244 
setPackageFlagLocked(String packageName, int flag, boolean set)245     private void setPackageFlagLocked(String packageName, int flag, boolean set) {
246         final int curFlags = getPackageFlags(packageName);
247         final int newFlags = set ? (curFlags & ~flag) : (curFlags | flag);
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 
getFrontActivityScreenCompatModeLocked()258     public int getFrontActivityScreenCompatModeLocked() {
259         ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked();
260         if (r == null) {
261             return ActivityManager.COMPAT_MODE_UNKNOWN;
262         }
263         return computeCompatModeLocked(r.info.applicationInfo);
264     }
265 
setFrontActivityScreenCompatModeLocked(int mode)266     public void setFrontActivityScreenCompatModeLocked(int mode) {
267         ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked();
268         if (r == null) {
269             Slog.w(TAG, "setFrontActivityScreenCompatMode failed: no top activity");
270             return;
271         }
272         setPackageScreenCompatModeLocked(r.info.applicationInfo, mode);
273     }
274 
getPackageScreenCompatModeLocked(String packageName)275     public int getPackageScreenCompatModeLocked(String packageName) {
276         ApplicationInfo ai = null;
277         try {
278             ai = AppGlobals.getPackageManager().getApplicationInfo(packageName, 0, 0);
279         } catch (RemoteException e) {
280         }
281         if (ai == null) {
282             return ActivityManager.COMPAT_MODE_UNKNOWN;
283         }
284         return computeCompatModeLocked(ai);
285     }
286 
setPackageScreenCompatModeLocked(String packageName, int mode)287     public void setPackageScreenCompatModeLocked(String packageName, int mode) {
288         ApplicationInfo ai = null;
289         try {
290             ai = AppGlobals.getPackageManager().getApplicationInfo(packageName, 0, 0);
291         } catch (RemoteException e) {
292         }
293         if (ai == null) {
294             Slog.w(TAG, "setPackageScreenCompatMode failed: unknown package " + packageName);
295             return;
296         }
297         setPackageScreenCompatModeLocked(ai, mode);
298     }
299 
setPackageScreenCompatModeLocked(ApplicationInfo ai, int mode)300     private void setPackageScreenCompatModeLocked(ApplicationInfo ai, int mode) {
301         final String packageName = ai.packageName;
302 
303         int curFlags = getPackageFlags(packageName);
304 
305         boolean enable;
306         switch (mode) {
307             case ActivityManager.COMPAT_MODE_DISABLED:
308                 enable = false;
309                 break;
310             case ActivityManager.COMPAT_MODE_ENABLED:
311                 enable = true;
312                 break;
313             case ActivityManager.COMPAT_MODE_TOGGLE:
314                 enable = (curFlags&COMPAT_FLAG_ENABLED) == 0;
315                 break;
316             default:
317                 Slog.w(TAG, "Unknown screen compat mode req #" + mode + "; ignoring");
318                 return;
319         }
320 
321         int newFlags = curFlags;
322         if (enable) {
323             newFlags |= COMPAT_FLAG_ENABLED;
324         } else {
325             newFlags &= ~COMPAT_FLAG_ENABLED;
326         }
327 
328         CompatibilityInfo ci = compatibilityInfoForPackageLocked(ai);
329         if (ci.alwaysSupportsScreen()) {
330             Slog.w(TAG, "Ignoring compat mode change of " + packageName
331                     + "; compatibility never needed");
332             newFlags = 0;
333         }
334         if (ci.neverSupportsScreen()) {
335             Slog.w(TAG, "Ignoring compat mode change of " + packageName
336                     + "; compatibility always needed");
337             newFlags = 0;
338         }
339 
340         if (newFlags != curFlags) {
341             if (newFlags != 0) {
342                 mPackages.put(packageName, newFlags);
343             } else {
344                 mPackages.remove(packageName);
345             }
346 
347             // Need to get compatibility info in new state.
348             ci = compatibilityInfoForPackageLocked(ai);
349 
350             scheduleWrite();
351 
352             final ActivityStack stack = mService.getFocusedStack();
353             ActivityRecord starting = stack.restartPackage(packageName);
354 
355             // Tell all processes that loaded this package about the change.
356             for (int i=mService.mLruProcesses.size()-1; i>=0; i--) {
357                 ProcessRecord app = mService.mLruProcesses.get(i);
358                 if (!app.pkgList.containsKey(packageName)) {
359                     continue;
360                 }
361                 try {
362                     if (app.thread != null) {
363                         if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Sending to proc "
364                                 + app.processName + " new compat " + ci);
365                         app.thread.updatePackageCompatibilityInfo(packageName, ci);
366                     }
367                 } catch (Exception e) {
368                 }
369             }
370 
371             if (starting != null) {
372                 starting.ensureActivityConfiguration(0 /* globalChanges */,
373                         false /* preserveWindow */);
374                 // And we need to make sure at this point that all other activities
375                 // are made visible with the correct configuration.
376                 stack.ensureActivitiesVisibleLocked(starting, 0, !PRESERVE_WINDOWS);
377             }
378         }
379     }
380 
saveCompatModes()381     void saveCompatModes() {
382         HashMap<String, Integer> pkgs;
383         synchronized (mService) {
384             pkgs = new HashMap<String, Integer>(mPackages);
385         }
386 
387         FileOutputStream fos = null;
388 
389         try {
390             fos = mFile.startWrite();
391             XmlSerializer out = new FastXmlSerializer();
392             out.setOutput(fos, StandardCharsets.UTF_8.name());
393             out.startDocument(null, true);
394             out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
395             out.startTag(null, "compat-packages");
396 
397             final IPackageManager pm = AppGlobals.getPackageManager();
398             final Configuration globalConfig = mService.getGlobalConfiguration();
399             final int screenLayout = globalConfig.screenLayout;
400             final int smallestScreenWidthDp = globalConfig.smallestScreenWidthDp;
401             final Iterator<Map.Entry<String, Integer>> it = pkgs.entrySet().iterator();
402             while (it.hasNext()) {
403                 Map.Entry<String, Integer> entry = it.next();
404                 String pkg = entry.getKey();
405                 int mode = entry.getValue();
406                 if (mode == 0) {
407                     continue;
408                 }
409                 ApplicationInfo ai = null;
410                 try {
411                     ai = pm.getApplicationInfo(pkg, 0, 0);
412                 } catch (RemoteException e) {
413                 }
414                 if (ai == null) {
415                     continue;
416                 }
417                 CompatibilityInfo info = new CompatibilityInfo(ai, screenLayout,
418                         smallestScreenWidthDp, false);
419                 if (info.alwaysSupportsScreen()) {
420                     continue;
421                 }
422                 if (info.neverSupportsScreen()) {
423                     continue;
424                 }
425                 out.startTag(null, "pkg");
426                 out.attribute(null, "name", pkg);
427                 out.attribute(null, "mode", Integer.toString(mode));
428                 out.endTag(null, "pkg");
429             }
430 
431             out.endTag(null, "compat-packages");
432             out.endDocument();
433 
434             mFile.finishWrite(fos);
435         } catch (java.io.IOException e1) {
436             Slog.w(TAG, "Error writing compat packages", e1);
437             if (fos != null) {
438                 mFile.failWrite(fos);
439             }
440         }
441     }
442 }
443