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