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