CompatModePackages.java revision 7df9b45bfe3cc6a6b3f33be008bb69a31b15999f
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
17package com.android.server.am;
18
19import static com.android.server.am.ActivityManagerDebugConfig.*;
20import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
21
22import java.io.File;
23import java.io.FileInputStream;
24import java.io.FileOutputStream;
25import java.nio.charset.StandardCharsets;
26import java.util.HashMap;
27import java.util.Iterator;
28import java.util.Map;
29
30import org.xmlpull.v1.XmlPullParser;
31import org.xmlpull.v1.XmlPullParserException;
32import org.xmlpull.v1.XmlSerializer;
33
34import com.android.internal.util.FastXmlSerializer;
35
36import android.app.ActivityManager;
37import android.app.AppGlobals;
38import android.content.pm.ApplicationInfo;
39import android.content.pm.IPackageManager;
40import android.content.res.CompatibilityInfo;
41import android.os.Handler;
42import android.os.Looper;
43import android.os.Message;
44import android.os.RemoteException;
45import android.util.AtomicFile;
46import android.util.Slog;
47import android.util.Xml;
48
49public 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 {
70        public CompatHandler(Looper looper) {
71            super(looper, null, true);
72        }
73
74        @Override
75        public void handleMessage(Message msg) {
76            switch (msg.what) {
77                case MSG_WRITE:
78                    saveCompatModes();
79                    break;
80            }
81        }
82    };
83
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
143    public HashMap<String, Integer> getPackages() {
144        return mPackages;
145    }
146
147    private int getPackageFlags(String packageName) {
148        Integer flags = mPackages.get(packageName);
149        return flags != null ? flags : 0;
150    }
151
152    public void handlePackageDataClearedLocked(String packageName) {
153        // User has explicitly asked to clear all associated data.
154        removePackage(packageName);
155    }
156
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
163    private void removePackage(String packageName) {
164        if (mPackages.containsKey(packageName)) {
165            mPackages.remove(packageName);
166            scheduleWrite();
167        }
168    }
169
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
193    private void scheduleWrite() {
194        mHandler.removeMessages(MSG_WRITE);
195        Message msg = mHandler.obtainMessage(MSG_WRITE);
196        mHandler.sendMessageDelayed(msg, 10000);
197    }
198
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
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
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
230    public boolean getPackageAskCompatModeLocked(String packageName) {
231        return (getPackageFlags(packageName)&COMPAT_FLAG_DONT_ASK) == 0;
232    }
233
234    public boolean getPackageNotifyUnsupportedZoomLocked(String packageName) {
235        return (getPackageFlags(packageName)&UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY) == 0;
236    }
237
238    public void setFrontActivityAskCompatModeLocked(boolean ask) {
239        ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked();
240        if (r != null) {
241            setPackageAskCompatModeLocked(r.packageName, ask);
242        }
243    }
244
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
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
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
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
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
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
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
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