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