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