1 package com.android.server.am; 2 3 import java.io.File; 4 import java.io.FileInputStream; 5 import java.io.FileOutputStream; 6 import java.util.HashMap; 7 import java.util.Iterator; 8 import java.util.Map; 9 10 import org.xmlpull.v1.XmlPullParser; 11 import org.xmlpull.v1.XmlPullParserException; 12 import org.xmlpull.v1.XmlSerializer; 13 14 import com.android.internal.util.FastXmlSerializer; 15 16 import android.app.ActivityManager; 17 import android.app.AppGlobals; 18 import android.content.pm.ApplicationInfo; 19 import android.content.pm.IPackageManager; 20 import android.content.res.CompatibilityInfo; 21 import android.os.Handler; 22 import android.os.Message; 23 import android.os.RemoteException; 24 import android.util.AtomicFile; 25 import android.util.Slog; 26 import android.util.Xml; 27 28 public 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