blob: a05a3e767d1254073d33d819c014f5f25c00ab78 [file] [log] [blame]
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -07001/*
2 * Copyright (C) 2016 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
17package com.android.server;
18
Jorim Jaggi7119800f2018-07-09 17:57:10 +020019import static android.app.ActivityManager.UID_OBSERVER_ACTIVE;
20import static android.app.ActivityManager.UID_OBSERVER_GONE;
21
22import android.annotation.IntDef;
Daniel Colascione9779b5e2018-03-21 19:13:57 -070023import android.annotation.Nullable;
24
Jorim Jaggi7119800f2018-07-09 17:57:10 +020025import android.app.ActivityManager;
26import android.app.ActivityManagerInternal;
27import android.app.IActivityManager;
28import android.app.IUidObserver;
Carmen Jacksonf107a232017-05-16 10:37:26 -070029import android.content.BroadcastReceiver;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070030import android.content.Context;
Jeff Sharkey847bd852016-08-03 17:20:03 -060031import android.content.Intent;
Carmen Jacksonf107a232017-05-16 10:37:26 -070032import android.content.IntentFilter;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070033import android.content.pm.ActivityInfo;
34import android.content.pm.ApplicationInfo;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070035import android.content.pm.PackageManager;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070036import android.content.pm.ResolveInfo;
Jorim Jaggi0c849962018-07-15 14:24:38 +020037import android.database.ContentObserver;
Carmen Jacksonf107a232017-05-16 10:37:26 -070038import android.net.Uri;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070039import android.os.Binder;
40import android.os.Build;
Philip Cuadraa95cea02016-07-06 16:00:32 -070041import android.os.Handler;
42import android.os.Looper;
43import android.os.Message;
Jorim Jaggi7119800f2018-07-09 17:57:10 +020044import android.os.RemoteException;
Jeff Sharkey847bd852016-08-03 17:20:03 -060045import android.os.UserHandle;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070046import android.provider.MediaStore;
Jorim Jaggi0c849962018-07-15 14:24:38 +020047import android.provider.Settings;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070048import android.system.ErrnoException;
49import android.system.Os;
50import android.system.OsConstants;
Jorim Jaggi7119800f2018-07-09 17:57:10 +020051import android.util.ArrayMap;
Calin Juravle31ce3a82017-05-22 17:49:01 -070052import android.util.ArraySet;
Jeff Sharkey847bd852016-08-03 17:20:03 -060053import android.util.Slog;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070054
Jorim Jaggi7119800f2018-07-09 17:57:10 +020055import com.android.internal.annotations.GuardedBy;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070056import com.android.internal.app.ResolverActivity;
Philip Cuadraa95cea02016-07-06 16:00:32 -070057import com.android.internal.os.BackgroundThread;
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -060058import com.android.internal.util.DumpUtils;
Jorim Jaggi7119800f2018-07-09 17:57:10 +020059import com.android.internal.util.function.pooled.PooledLambda;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070060
Philip Cuadrad9bd8842016-07-12 17:29:38 -070061import dalvik.system.DexFile;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070062import dalvik.system.VMRuntime;
63
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070064import java.io.FileDescriptor;
Daniel Colascione9779b5e2018-03-21 19:13:57 -070065import java.io.Closeable;
66import java.io.InputStream;
67import java.io.DataInputStream;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070068import java.io.IOException;
69import java.io.PrintWriter;
Jorim Jaggi7119800f2018-07-09 17:57:10 +020070import java.lang.annotation.Retention;
71import java.lang.annotation.RetentionPolicy;
Carmen Jackson879fb682018-07-20 16:43:09 -070072import java.util.List;
Jeff Sharkey847bd852016-08-03 17:20:03 -060073import java.util.ArrayList;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070074
Daniel Colascione9779b5e2018-03-21 19:13:57 -070075import java.util.zip.ZipFile;
Daniel Colascione9779b5e2018-03-21 19:13:57 -070076import java.util.zip.ZipEntry;
77
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070078/**
79 * <p>PinnerService pins important files for key processes in memory.</p>
80 * <p>Files to pin are specified in the config_defaultPinnerServiceFiles
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070081 * overlay.</p>
82 * <p>Pin the default camera application if specified in config_pinnerCameraApp.</p>
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070083 */
84public final class PinnerService extends SystemService {
85 private static final boolean DEBUG = false;
86 private static final String TAG = "PinnerService";
Jorim Jaggi7119800f2018-07-09 17:57:10 +020087
Daniel Colascione9779b5e2018-03-21 19:13:57 -070088 private static final String PIN_META_FILENAME = "pinlist.meta";
89 private static final int PAGE_SIZE = (int) Os.sysconf(OsConstants._SC_PAGESIZE);
Jorim Jaggi7119800f2018-07-09 17:57:10 +020090 private static final int MATCH_FLAGS = PackageManager.MATCH_DEFAULT_ONLY
91 | PackageManager.MATCH_DIRECT_BOOT_AWARE
92 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
93
94 private static final int KEY_CAMERA = 0;
95 private static final int KEY_HOME = 1;
96
97 private static final int MAX_CAMERA_PIN_SIZE = 80 * (1 << 20); // 80MB max for camera app.
98 private static final int MAX_HOME_PIN_SIZE = 6 * (1 << 20); // 6MB max for home app.
99
100 @IntDef({KEY_CAMERA, KEY_HOME})
101 @Retention(RetentionPolicy.SOURCE)
102 public @interface AppKey {}
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700103
104 private final Context mContext;
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200105 private final ActivityManagerInternal mAmInternal;
106 private final IActivityManager mAm;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700107
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200108 /** The list of the statically pinned files. */
109 @GuardedBy("this")
110 private final ArrayList<PinnedFile> mPinnedFiles = new ArrayList<>();
111
112 /** The list of the pinned apps. This is a map from {@link AppKey} to a pinned app. */
113 @GuardedBy("this")
114 private final ArrayMap<Integer, PinnedApp> mPinnedApps = new ArrayMap<>();
115
116 /**
117 * The list of the pinned apps that need to be repinned as soon as the all processes of a given
118 * uid are no longer active. Note that with background dex opt, the new dex/vdex files are only
119 * loaded into the processes once it restarts. So in case background dex opt recompiled these
120 * files, we still need to keep the old ones pinned until the processes restart.
121 * <p>
122 * This is a map from uid to {@link AppKey}
123 */
124 @GuardedBy("this")
125 private final ArrayMap<Integer, Integer> mPendingRepin = new ArrayMap<>();
126
127 /**
128 * A set of {@link AppKey} that are configured to be pinned.
129 */
130 private final ArraySet<Integer> mPinKeys = new ArraySet<>();
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700131
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700132 private BinderService mBinderService;
Philip Cuadraa95cea02016-07-06 16:00:32 -0700133 private PinnerHandler mPinnerHandler = null;
134
Carmen Jacksonf107a232017-05-16 10:37:26 -0700135 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
136 @Override
137 public void onReceive(Context context, Intent intent) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200138 // If an app has updated, update pinned files accordingly.
139 if (Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())) {
Carmen Jacksonf107a232017-05-16 10:37:26 -0700140 Uri packageUri = intent.getData();
Calin Juravle31ce3a82017-05-22 17:49:01 -0700141 String packageName = packageUri.getSchemeSpecificPart();
142 ArraySet<String> updatedPackages = new ArraySet<>();
143 updatedPackages.add(packageName);
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200144 update(updatedPackages, true /* force */);
Carmen Jacksonf107a232017-05-16 10:37:26 -0700145 }
146 }
147 };
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700148
149 public PinnerService(Context context) {
150 super(context);
151
152 mContext = context;
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200153 boolean shouldPinCamera = context.getResources().getBoolean(
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700154 com.android.internal.R.bool.config_pinnerCameraApp);
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200155 boolean shouldPinHome = context.getResources().getBoolean(
156 com.android.internal.R.bool.config_pinnerHomeApp);
157 if (shouldPinCamera) {
158 mPinKeys.add(KEY_CAMERA);
159 }
160 if (shouldPinHome) {
161 mPinKeys.add(KEY_HOME);
162 }
Philip Cuadraa95cea02016-07-06 16:00:32 -0700163 mPinnerHandler = new PinnerHandler(BackgroundThread.get().getLooper());
Carmen Jacksonf107a232017-05-16 10:37:26 -0700164
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200165 mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
166 mAm = ActivityManager.getService();
167
Carmen Jacksonf107a232017-05-16 10:37:26 -0700168 IntentFilter filter = new IntentFilter();
169 filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
170 filter.addDataScheme("package");
171 mContext.registerReceiver(mBroadcastReceiver, filter);
Jorim Jaggi0c849962018-07-15 14:24:38 +0200172
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200173 registerUidListener();
Jorim Jaggi0c849962018-07-15 14:24:38 +0200174 registerUserSetupCompleteListener();
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700175 }
176
177 @Override
178 public void onStart() {
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700179 if (DEBUG) {
180 Slog.i(TAG, "Starting PinnerService");
181 }
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700182 mBinderService = new BinderService();
183 publishBinderService("pinner", mBinderService);
Carmen Jacksonf107a232017-05-16 10:37:26 -0700184 publishLocalService(PinnerService.class, this);
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700185
Jeff Sharkey847bd852016-08-03 17:20:03 -0600186 mPinnerHandler.obtainMessage(PinnerHandler.PIN_ONSTART_MSG).sendToTarget();
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200187 sendPinAppsMessage(UserHandle.USER_SYSTEM);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700188 }
189
190 /**
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200191 * Repin apps on user switch.
192 * <p>
193 * If more than one user is using the device each user may set a different preference for the
194 * individual apps. Make sure that user's preference is pinned into memory.
Philip Cuadraa95cea02016-07-06 16:00:32 -0700195 */
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700196 @Override
197 public void onSwitchUser(int userHandle) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200198 sendPinAppsMessage(userHandle);
199 }
200
201 @Override
202 public void onUnlockUser(int userHandle) {
203 sendPinAppsMessage(userHandle);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700204 }
205
Philip Cuadraa95cea02016-07-06 16:00:32 -0700206 /**
Carmen Jacksonf107a232017-05-16 10:37:26 -0700207 * Update the currently pinned files.
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200208 * Specifically, this only updates pinning for the apps that need to be pinned.
Carmen Jacksonf107a232017-05-16 10:37:26 -0700209 * The other files pinned in onStart will not need to be updated.
210 */
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200211 public void update(ArraySet<String> updatedPackages, boolean force) {
212 int currentUser = ActivityManager.getCurrentUser();
213 for (int i = mPinKeys.size() - 1; i >= 0; i--) {
214 int key = mPinKeys.valueAt(i);
215 ApplicationInfo info = getInfoForKey(key, currentUser);
216 if (info != null && updatedPackages.contains(info.packageName)) {
217 Slog.i(TAG, "Updating pinned files for " + info.packageName + " force=" + force);
218 sendPinAppMessage(key, currentUser, force);
219 }
Calin Juravle31ce3a82017-05-22 17:49:01 -0700220 }
Carmen Jacksonf107a232017-05-16 10:37:26 -0700221 }
222
223 /**
Philip Cuadraa95cea02016-07-06 16:00:32 -0700224 * Handler for on start pinning message
225 */
226 private void handlePinOnStart() {
227 // Files to pin come from the overlay and can be specified per-device config
228 String[] filesToPin = mContext.getResources().getStringArray(
229 com.android.internal.R.array.config_defaultPinnerServiceFiles);
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700230 // Continue trying to pin each file even if we fail to pin some of them
231 for (String fileToPin : filesToPin) {
232 PinnedFile pf = pinFile(fileToPin,
233 Integer.MAX_VALUE,
234 /*attemptPinIntrospection=*/false);
235 if (pf == null) {
236 Slog.e(TAG, "Failed to pin file = " + fileToPin);
237 continue;
238 }
239
240 synchronized (this) {
241 mPinnedFiles.add(pf);
Philip Cuadraa95cea02016-07-06 16:00:32 -0700242 }
243 }
244 }
245
Jorim Jaggi0c849962018-07-15 14:24:38 +0200246 /**
247 * Registers a listener to repin the home app when user setup is complete, as the home intent
248 * initially resolves to setup wizard, but once setup is complete, it will resolve to the
249 * regular home app.
250 */
251 private void registerUserSetupCompleteListener() {
252 Uri userSetupCompleteUri = Settings.Secure.getUriFor(
253 Settings.Secure.USER_SETUP_COMPLETE);
254 mContext.getContentResolver().registerContentObserver(userSetupCompleteUri,
255 false, new ContentObserver(null) {
256 @Override
257 public void onChange(boolean selfChange, Uri uri) {
258 if (userSetupCompleteUri.equals(uri)) {
259 sendPinAppMessage(KEY_HOME, ActivityManager.getCurrentUser(),
260 true /* force */);
261 }
262 }
263 }, UserHandle.USER_ALL);
264 }
265
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200266 private void registerUidListener() {
267 try {
268 mAm.registerUidObserver(new IUidObserver.Stub() {
269 @Override
270 public void onUidGone(int uid, boolean disabled) throws RemoteException {
271 mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
272 PinnerService::handleUidGone, PinnerService.this, uid));
273 }
274
275 @Override
276 public void onUidActive(int uid) throws RemoteException {
277 mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
278 PinnerService::handleUidActive, PinnerService.this, uid));
279 }
280
281 @Override
282 public void onUidIdle(int uid, boolean disabled) throws RemoteException {
283 }
284
285 @Override
286 public void onUidStateChanged(int uid, int procState, long procStateSeq)
287 throws RemoteException {
288 }
289
290 @Override
291 public void onUidCachedChanged(int uid, boolean cached) throws RemoteException {
292 }
293 }, UID_OBSERVER_GONE | UID_OBSERVER_ACTIVE, 0, "system");
294 } catch (RemoteException e) {
295 Slog.e(TAG, "Failed to register uid observer", e);
296 }
297 }
298
299 private void handleUidGone(int uid) {
300 updateActiveState(uid, false /* active */);
301 int key;
302 synchronized (this) {
303
304 // In case we have a pending repin, repin now. See mPendingRepin for more information.
305 key = mPendingRepin.getOrDefault(uid, -1);
306 if (key == -1) {
307 return;
308 }
309 mPendingRepin.remove(uid);
310 }
311 pinApp(key, ActivityManager.getCurrentUser(), false /* force */);
312 }
313
314 private void handleUidActive(int uid) {
315 updateActiveState(uid, true /* active */);
316 }
317
318 private void updateActiveState(int uid, boolean active) {
319 synchronized (this) {
320 for (int i = mPinnedApps.size() - 1; i >= 0; i--) {
321 PinnedApp app = mPinnedApps.valueAt(i);
322 if (app.uid == uid) {
323 app.active = active;
324 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700325 }
326 }
327 }
328
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200329 private void unpinApp(@AppKey int key) {
330 ArrayList<PinnedFile> pinnedAppFiles;
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700331 synchronized (this) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200332 PinnedApp app = mPinnedApps.get(key);
333 if (app == null) {
334 return;
335 }
336 mPinnedApps.remove(key);
337 pinnedAppFiles = new ArrayList<>(app.mFiles);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700338 }
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200339 for (PinnedFile pinnedFile : pinnedAppFiles) {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700340 pinnedFile.close();
341 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700342 }
343
344 private boolean isResolverActivity(ActivityInfo info) {
345 return ResolverActivity.class.getName().equals(info.name);
346 }
347
348 private ApplicationInfo getCameraInfo(int userHandle) {
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700349 Intent cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
Carmen Jackson879fb682018-07-20 16:43:09 -0700350 ApplicationInfo info = getApplicationInfoForIntent(cameraIntent, userHandle,
351 false /* defaultToSystemApp */);
352
353 // If the STILL_IMAGE_CAMERA intent doesn't resolve, try the _SECURE intent.
354 // We don't use _SECURE first because it will never get set on a device
355 // without File-based Encryption. But if the user has only set the intent
356 // before unlocking their device, we may still be able to identify their
357 // preference using this intent.
358 if (info == null) {
359 cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE);
360 info = getApplicationInfoForIntent(cameraIntent, userHandle,
361 false /* defaultToSystemApp */);
362 }
363
364 // If the _SECURE intent doesn't resolve, try the original intent but request
365 // the system app for camera if there was more than one result.
366 if (info == null) {
367 cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
368 info = getApplicationInfoForIntent(cameraIntent, userHandle,
369 true /* defaultToSystemApp */);
370 }
371 return info;
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200372 }
373
374 private ApplicationInfo getHomeInfo(int userHandle) {
375 Intent intent = mAmInternal.getHomeIntent();
Carmen Jackson879fb682018-07-20 16:43:09 -0700376 return getApplicationInfoForIntent(intent, userHandle, false);
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200377 }
378
Carmen Jackson879fb682018-07-20 16:43:09 -0700379 private ApplicationInfo getApplicationInfoForIntent(Intent intent, int userHandle,
380 boolean defaultToSystemApp) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200381 if (intent == null) {
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700382 return null;
383 }
Carmen Jackson879fb682018-07-20 16:43:09 -0700384
385 ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivityAsUser(intent,
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200386 MATCH_FLAGS, userHandle);
Carmen Jackson879fb682018-07-20 16:43:09 -0700387
388 // If this intent can resolve to only one app, choose that one.
389 // Otherwise, if we've requested to default to the system app, return it;
390 // if we have not requested that default, return null if there's more than one option.
391 // If there's more than one system app, return null since we don't know which to pick.
392 if (resolveInfo == null) {
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700393 return null;
394 }
Carmen Jackson879fb682018-07-20 16:43:09 -0700395
396 if (!isResolverActivity(resolveInfo.activityInfo)) {
397 return resolveInfo.activityInfo.applicationInfo;
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200398 }
Carmen Jackson879fb682018-07-20 16:43:09 -0700399
400 if (defaultToSystemApp) {
401 List<ResolveInfo> infoList = mContext.getPackageManager()
402 .queryIntentActivitiesAsUser(intent, MATCH_FLAGS, userHandle);
403 ApplicationInfo systemAppInfo = null;
404 for (ResolveInfo info : infoList) {
405 if ((info.activityInfo.applicationInfo.flags
406 & ApplicationInfo.FLAG_SYSTEM) != 0) {
407 if (systemAppInfo == null) {
408 systemAppInfo = info.activityInfo.applicationInfo;
409 } else {
410 // If there's more than one system app, return null due to ambiguity.
411 return null;
412 }
413 }
414 }
415 return systemAppInfo;
416 }
417
418 return null;
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200419 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700420
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200421 private void sendPinAppsMessage(int userHandle) {
422 mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinApps, this,
423 userHandle));
424 }
425
426 private void pinApps(int userHandle) {
427 for (int i = mPinKeys.size() - 1; i >= 0; i--) {
428 int key = mPinKeys.valueAt(i);
429 pinApp(key, userHandle, true /* force */);
430 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700431 }
432
Carmen Jacksonf107a232017-05-16 10:37:26 -0700433 /**
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200434 * @see #pinApp(int, int, boolean)
Carmen Jacksonf107a232017-05-16 10:37:26 -0700435 */
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200436 private void sendPinAppMessage(int key, int userHandle, boolean force) {
437 mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinApp, this,
438 key, userHandle, force));
439 }
440
441 /**
442 * Pins an app of a specific type {@code key}.
443 *
444 * @param force If false, this will not repin the app if it's currently active. See
445 * {@link #mPendingRepin}.
446 */
447 private void pinApp(int key, int userHandle, boolean force) {
448 int uid = getUidForKey(key);
449
450 // In case the app is currently active, don't repin until next process restart. See
451 // mPendingRepin for more information.
452 if (!force && uid != -1) {
453 synchronized (this) {
454 mPendingRepin.put(uid, key);
455 }
456 return;
457 }
458 unpinApp(key);
459 ApplicationInfo info = getInfoForKey(key, userHandle);
460 if (info != null) {
461 pinApp(key, info);
462 }
463 }
464
465 /**
466 * Checks whether the pinned package with {@code key} is active or not.
467
468 * @return The uid of the pinned app, or {@code -1} otherwise.
469 */
470 private int getUidForKey(@AppKey int key) {
471 synchronized (this) {
472 PinnedApp existing = mPinnedApps.get(key);
473 return existing != null && existing.active
474 ? existing.uid
475 : -1;
476 }
477 }
478
479 /**
480 * Retrieves the current application info for the given app type.
481 *
482 * @param key The app type to retrieve the info for.
483 * @param userHandle The user id of the current user.
484 */
485 private @Nullable ApplicationInfo getInfoForKey(@AppKey int key, int userHandle) {
486 switch (key) {
487 case KEY_CAMERA:
488 return getCameraInfo(userHandle);
489 case KEY_HOME:
490 return getHomeInfo(userHandle);
491 default:
492 return null;
493 }
494 }
495
496 /**
497 * @return The app type name for {@code key}.
498 */
499 private String getNameForKey(@AppKey int key) {
500 switch (key) {
501 case KEY_CAMERA:
502 return "Camera";
503 case KEY_HOME:
504 return "Home";
505 default:
506 return null;
507 }
508 }
509
510 /**
511 * @return The maximum amount of bytes to be pinned for an app of type {@code key}.
512 */
513 private int getSizeLimitForKey(@AppKey int key) {
514 switch (key) {
515 case KEY_CAMERA:
516 return MAX_CAMERA_PIN_SIZE;
517 case KEY_HOME:
518 return MAX_HOME_PIN_SIZE;
519 default:
520 return 0;
521 }
522 }
523
524 /**
525 * Pins an application.
526 *
527 * @param key The key of the app to pin.
528 * @param appInfo The corresponding app info.
529 */
530 private void pinApp(@AppKey int key, @Nullable ApplicationInfo appInfo) {
531 if (appInfo == null) {
532 return;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700533 }
534
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200535 PinnedApp pinnedApp = new PinnedApp(appInfo);
536 synchronized (this) {
537 mPinnedApps.put(key, pinnedApp);
538 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700539
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200540 // pin APK
541 int pinSizeLimit = getSizeLimitForKey(key);
542 String apk = appInfo.sourceDir;
543 PinnedFile pf = pinFile(apk, pinSizeLimit, /*attemptPinIntrospection=*/true);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700544 if (pf == null) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200545 Slog.e(TAG, "Failed to pin " + apk);
546 return;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700547 }
548 if (DEBUG) {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700549 Slog.i(TAG, "Pinned " + pf.fileName);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700550 }
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700551 synchronized (this) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200552 pinnedApp.mFiles.add(pf);
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700553 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700554
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700555 // determine the ABI from either ApplicationInfo or Build
556 String arch = "arm";
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200557 if (appInfo.primaryCpuAbi != null) {
558 if (VMRuntime.is64BitAbi(appInfo.primaryCpuAbi)) {
John Eckerdal60b07cd2016-11-03 14:04:47 +0100559 arch = arch + "64";
560 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700561 } else {
562 if (VMRuntime.is64BitAbi(Build.SUPPORTED_ABIS[0])) {
563 arch = arch + "64";
564 }
565 }
Philip Cuadrad9bd8842016-07-12 17:29:38 -0700566
567 // get the path to the odex or oat file
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200568 String baseCodePath = appInfo.getBaseCodePath();
Calin Juravle128721a2017-05-15 20:20:50 -0700569 String[] files = null;
Philip Cuadrad9bd8842016-07-12 17:29:38 -0700570 try {
Calin Juravle128721a2017-05-15 20:20:50 -0700571 files = DexFile.getDexFileOutputPaths(baseCodePath, arch);
Philip Cuadrad9bd8842016-07-12 17:29:38 -0700572 } catch (IOException ioe) {}
Calin Juravle128721a2017-05-15 20:20:50 -0700573 if (files == null) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200574 return;
Philip Cuadrad9bd8842016-07-12 17:29:38 -0700575 }
576
577 //not pinning the oat/odex is not a fatal error
Calin Juravle128721a2017-05-15 20:20:50 -0700578 for (String file : files) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200579 pf = pinFile(file, pinSizeLimit, /*attemptPinIntrospection=*/false);
Calin Juravle128721a2017-05-15 20:20:50 -0700580 if (pf != null) {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700581 synchronized (this) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200582 pinnedApp.mFiles.add(pf);
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700583 }
Calin Juravle128721a2017-05-15 20:20:50 -0700584 if (DEBUG) {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700585 Slog.i(TAG, "Pinned " + pf.fileName);
Calin Juravle128721a2017-05-15 20:20:50 -0700586 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700587 }
588 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700589 }
590
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700591 /** mlock length bytes of fileToPin in memory
592 *
593 * If attemptPinIntrospection is true, then treat the file to pin as a zip file and
594 * look for a "pinlist.meta" file in the archive root directory. The structure of this
595 * file is a PINLIST_META as described below:
596 *
597 * <pre>
598 * PINLIST_META: PIN_RANGE*
599 * PIN_RANGE: PIN_START PIN_LENGTH
600 * PIN_START: big endian i32: offset in bytes of pin region from file start
601 * PIN_LENGTH: big endian i32: length of pin region in bytes
602 * </pre>
603 *
604 * (We use big endian because that's what DataInputStream is hardcoded to use.)
605 *
606 * If attemptPinIntrospection is false, then we use a single implicit PIN_RANGE of (0,
607 * maxBytesToPin); that is, we attempt to pin the first maxBytesToPin bytes of the file.
608 *
609 * After we open a file, we march through the list of pin ranges and attempt to pin
610 * each one, stopping after we've pinned maxBytesToPin bytes. (We may truncate the last
611 * pinned range to fit.) In this way, by choosing to emit certain PIN_RANGE pairs
612 * before others, file generators can express pins in priority order, making most
613 * effective use of the pinned-page quota.
614 *
615 * N.B. Each PIN_RANGE is clamped to the actual bounds of the file; all inputs have a
616 * meaningful interpretation. Also, a range locking a single byte of a page locks the
617 * whole page. Any truncated PIN_RANGE at EOF is ignored. Overlapping pinned entries
618 * are legal, but each pin of a byte counts toward the pin quota regardless of whether
619 * that byte has already been pinned, so the generator of PINLIST_META ought to ensure
620 * that ranges are non-overlapping.
621 *
622 * @param fileToPin Path to file to pin
623 * @param maxBytesToPin Maximum number of bytes to pin
624 * @param attemptPinIntrospection If true, try to open file as a
625 * zip in order to extract the
626 * @return Pinned memory resource owner thing or null on error
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700627 */
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700628 private static PinnedFile pinFile(String fileToPin,
629 int maxBytesToPin,
630 boolean attemptPinIntrospection) {
631 ZipFile fileAsZip = null;
632 InputStream pinRangeStream = null;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700633 try {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700634 if (attemptPinIntrospection) {
635 fileAsZip = maybeOpenZip(fileToPin);
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700636 }
637
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700638 if (fileAsZip != null) {
639 pinRangeStream = maybeOpenPinMetaInZip(fileAsZip, fileToPin);
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700640 }
641
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700642 Slog.d(TAG, "pinRangeStream: " + pinRangeStream);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700643
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700644 PinRangeSource pinRangeSource = (pinRangeStream != null)
645 ? new PinRangeSourceStream(pinRangeStream)
646 : new PinRangeSourceStatic(0, Integer.MAX_VALUE /* will be clipped */);
647 return pinFileRanges(fileToPin, maxBytesToPin, pinRangeSource);
648 } finally {
649 safeClose(pinRangeStream);
650 safeClose(fileAsZip); // Also closes any streams we've opened
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700651 }
652 }
653
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700654 /**
655 * Attempt to open a file as a zip file. On any sort of corruption, log, swallow the
656 * error, and return null.
657 */
658 private static ZipFile maybeOpenZip(String fileName) {
659 ZipFile zip = null;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700660 try {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700661 zip = new ZipFile(fileName);
662 } catch (IOException ex) {
663 Slog.w(TAG,
664 String.format(
665 "could not open \"%s\" as zip: pinning as blob",
666 fileName),
667 ex);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700668 }
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700669 return zip;
670 }
671
672 /**
673 * Open a pin metadata file in the zip if one is present.
674 *
675 * @param zipFile Zip file to search
676 * @return Open input stream or null on any error
677 */
678 private static InputStream maybeOpenPinMetaInZip(ZipFile zipFile, String fileName) {
679 ZipEntry pinMetaEntry = zipFile.getEntry(PIN_META_FILENAME);
680 InputStream pinMetaStream = null;
681 if (pinMetaEntry != null) {
682 try {
683 pinMetaStream = zipFile.getInputStream(pinMetaEntry);
684 } catch (IOException ex) {
685 Slog.w(TAG,
686 String.format("error reading pin metadata \"%s\": pinning as blob",
687 fileName),
688 ex);
689 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700690 }
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700691 return pinMetaStream;
692 }
693
694 private static abstract class PinRangeSource {
695 /** Retrive a range to pin.
696 *
697 * @param outPinRange Receives the pin region
698 * @return True if we filled in outPinRange or false if we're out of pin entries
699 */
700 abstract boolean read(PinRange outPinRange);
701 }
702
703 private static final class PinRangeSourceStatic extends PinRangeSource {
704 private final int mPinStart;
705 private final int mPinLength;
706 private boolean mDone = false;
707
708 PinRangeSourceStatic(int pinStart, int pinLength) {
709 mPinStart = pinStart;
710 mPinLength = pinLength;
711 }
712
713 @Override
714 boolean read(PinRange outPinRange) {
715 outPinRange.start = mPinStart;
716 outPinRange.length = mPinLength;
717 boolean done = mDone;
718 mDone = true;
719 return !done;
720 }
721 }
722
723 private static final class PinRangeSourceStream extends PinRangeSource {
724 private final DataInputStream mStream;
725 private boolean mDone = false;
726
727 PinRangeSourceStream(InputStream stream) {
728 mStream = new DataInputStream(stream);
729 }
730
731 @Override
732 boolean read(PinRange outPinRange) {
733 if (!mDone) {
734 try {
735 outPinRange.start = mStream.readInt();
736 outPinRange.length = mStream.readInt();
737 } catch (IOException ex) {
738 mDone = true;
739 }
740 }
741 return !mDone;
742 }
743 }
744
745 /**
746 * Helper for pinFile.
747 *
748 * @param fileToPin Name of file to pin
749 * @param maxBytesToPin Maximum number of bytes to pin
750 * @param pinRangeSource Read PIN_RANGE entries from this stream to tell us what bytes
751 * to pin.
752 * @return PinnedFile or null on error
753 */
754 private static PinnedFile pinFileRanges(
755 String fileToPin,
756 int maxBytesToPin,
757 PinRangeSource pinRangeSource)
758 {
759 FileDescriptor fd = new FileDescriptor();
760 long address = -1;
761 int mapSize = 0;
762
763 try {
764 int openFlags = (OsConstants.O_RDONLY |
765 OsConstants.O_CLOEXEC |
766 OsConstants.O_NOFOLLOW);
767 fd = Os.open(fileToPin, openFlags, 0);
768 mapSize = (int) Math.min(Os.fstat(fd).st_size, Integer.MAX_VALUE);
769 address = Os.mmap(0, mapSize,
770 OsConstants.PROT_READ,
771 OsConstants.MAP_SHARED,
772 fd, /*offset=*/0);
773
774 PinRange pinRange = new PinRange();
775 int bytesPinned = 0;
776
777 // We pin at page granularity, so make sure the limit is page-aligned
778 if (maxBytesToPin % PAGE_SIZE != 0) {
779 maxBytesToPin -= maxBytesToPin % PAGE_SIZE;
780 }
781
782 while (bytesPinned < maxBytesToPin && pinRangeSource.read(pinRange)) {
783 int pinStart = pinRange.start;
784 int pinLength = pinRange.length;
785 pinStart = clamp(0, pinStart, mapSize);
786 pinLength = clamp(0, pinLength, mapSize - pinStart);
787 pinLength = Math.min(maxBytesToPin - bytesPinned, pinLength);
788
789 // mlock doesn't require the region to be page-aligned, but we snap the
790 // lock region to page boundaries anyway so that we don't under-count
791 // locking a single byte of a page as a charge of one byte even though the
792 // OS will retain the whole page. Thanks to this adjustment, we slightly
793 // over-count the pin charge of back-to-back pins touching the same page,
794 // but better that than undercounting. Besides: nothing stops pin metafile
795 // creators from making the actual regions page-aligned.
796 pinLength += pinStart % PAGE_SIZE;
797 pinStart -= pinStart % PAGE_SIZE;
798 if (pinLength % PAGE_SIZE != 0) {
799 pinLength += PAGE_SIZE - pinLength % PAGE_SIZE;
800 }
801 pinLength = clamp(0, pinLength, maxBytesToPin - bytesPinned);
802
803 if (pinLength > 0) {
804 if (DEBUG) {
805 Slog.d(TAG,
806 String.format(
807 "pinning at %s %s bytes of %s",
808 pinStart, pinLength, fileToPin));
809 }
810 Os.mlock(address + pinStart, pinLength);
811 }
812 bytesPinned += pinLength;
813 }
814
815 PinnedFile pinnedFile = new PinnedFile(address, mapSize, fileToPin, bytesPinned);
816 address = -1; // Ownership transferred
817 return pinnedFile;
818 } catch (ErrnoException ex) {
819 Slog.e(TAG, "Could not pin file " + fileToPin, ex);
820 return null;
821 } finally {
822 safeClose(fd);
823 if (address >= 0) {
824 safeMunmap(address, mapSize);
825 }
826 }
827 }
828
829 private static int clamp(int min, int value, int max) {
830 return Math.max(min, Math.min(value, max));
831 }
832
833 private static void safeMunmap(long address, long mapSize) {
834 try {
835 Os.munmap(address, mapSize);
836 } catch (ErrnoException ex) {
837 Slog.w(TAG, "ignoring error in unmap", ex);
838 }
839 }
840
841 /**
842 * Close FD, swallowing irrelevant errors.
843 */
844 private static void safeClose(@Nullable FileDescriptor fd) {
845 if (fd != null && fd.valid()) {
846 try {
847 Os.close(fd);
848 } catch (ErrnoException ex) {
849 // Swallow the exception: non-EBADF errors in close(2)
850 // indicate deferred paging write errors, which we
851 // don't care about here. The underlying file
852 // descriptor is always closed.
853 if (ex.errno == OsConstants.EBADF) {
854 throw new AssertionError(ex);
855 }
856 }
857 }
858 }
859
860 /**
861 * Close closeable thing, swallowing errors.
862 */
863 private static void safeClose(@Nullable Closeable thing) {
864 if (thing != null) {
865 try {
866 thing.close();
867 } catch (IOException ex) {
868 Slog.w(TAG, "ignoring error closing resource: " + thing, ex);
869 }
870 }
871 }
872
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700873 private final class BinderService extends Binder {
874 @Override
875 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -0600876 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200877 synchronized (PinnerService.this) {
878 long totalSize = 0;
879 for (PinnedFile pinnedFile : mPinnedFiles) {
880 pw.format("%s %s\n", pinnedFile.fileName, pinnedFile.bytesPinned);
881 totalSize += pinnedFile.bytesPinned;
882 }
883 pw.println();
884 for (int key : mPinnedApps.keySet()) {
885 PinnedApp app = mPinnedApps.get(key);
886 pw.print(getNameForKey(key));
887 pw.print(" uid="); pw.print(app.uid);
888 pw.print(" active="); pw.print(app.active);
889 pw.println();
890 for (PinnedFile pf : mPinnedApps.get(key).mFiles) {
891 pw.print(" "); pw.format("%s %s\n", pf.fileName, pf.bytesPinned);
892 totalSize += pf.bytesPinned;
893 }
894 }
895 pw.format("Total size: %s\n", totalSize);
896 pw.println();
897 if (!mPendingRepin.isEmpty()) {
898 pw.print("Pending repin: ");
899 for (int key : mPendingRepin.values()) {
900 pw.print(getNameForKey(key)); pw.print(' ');
901 }
902 pw.println();
903 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700904 }
905 }
906 }
907
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700908 private static final class PinnedFile implements AutoCloseable {
909 private long mAddress;
910 final int mapSize;
911 final String fileName;
912 final int bytesPinned;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700913
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700914 PinnedFile(long address, int mapSize, String fileName, int bytesPinned) {
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700915 mAddress = address;
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700916 this.mapSize = mapSize;
917 this.fileName = fileName;
918 this.bytesPinned = bytesPinned;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700919 }
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700920
921 @Override
922 public void close() {
923 if (mAddress >= 0) {
924 safeMunmap(mAddress, mapSize);
925 mAddress = -1;
926 }
927 }
928
929 @Override
930 public void finalize() {
931 close();
932 }
933 }
934
935 final static class PinRange {
936 int start;
937 int length;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700938 }
Philip Cuadraa95cea02016-07-06 16:00:32 -0700939
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200940 /**
941 * Represents an app that was pinned.
942 */
943 private final class PinnedApp {
944
945 /**
946 * The uid of the package being pinned. This stays constant while the package stays
947 * installed.
948 */
949 final int uid;
950
951 /** Whether it is currently active, i.e. there is a running process from that package. */
952 boolean active;
953
954 /** List of pinned files. */
955 final ArrayList<PinnedFile> mFiles = new ArrayList<>();
956
957 private PinnedApp(ApplicationInfo appInfo) {
958 uid = appInfo.uid;
959 active = mAmInternal.isUidActive(uid);
960 }
961 }
962
Philip Cuadraa95cea02016-07-06 16:00:32 -0700963 final class PinnerHandler extends Handler {
Philip Cuadraa95cea02016-07-06 16:00:32 -0700964 static final int PIN_ONSTART_MSG = 4001;
965
966 public PinnerHandler(Looper looper) {
967 super(looper, null, true);
968 }
969
970 @Override
971 public void handleMessage(Message msg) {
972 switch (msg.what) {
Philip Cuadraa95cea02016-07-06 16:00:32 -0700973 case PIN_ONSTART_MSG:
974 {
975 handlePinOnStart();
976 }
977 break;
978
979 default:
980 super.handleMessage(msg);
981 }
982 }
983 }
984
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700985}