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