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.ActivityInfo; 19 import android.content.pm.ApplicationInfo; 20 import android.content.pm.IPackageManager; 21 import android.content.res.CompatibilityInfo; 22 import android.os.Handler; 23 import android.os.Message; 24 import android.os.RemoteException; 25 import android.util.AtomicFile; 26 import android.util.Slog; 27 import android.util.Xml; 28 29 public 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