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