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.os.Handler; 42 import android.os.Looper; 43 import android.os.Message; 44 import android.os.RemoteException; 45 import android.util.AtomicFile; 46 import android.util.Slog; 47 import android.util.Xml; 48 49 public 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