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