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