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