blob: f5b29e9b76b8918408ac92e25ee0ad05c2f20641 [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;
Jeff Sharkey847bd852016-08-03 17:20:03 -060072import java.util.ArrayList;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070073
Daniel Colascione9779b5e2018-03-21 19:13:57 -070074import java.util.zip.ZipFile;
Daniel Colascione9779b5e2018-03-21 19:13:57 -070075import java.util.zip.ZipEntry;
76
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070077/**
78 * <p>PinnerService pins important files for key processes in memory.</p>
79 * <p>Files to pin are specified in the config_defaultPinnerServiceFiles
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070080 * overlay.</p>
81 * <p>Pin the default camera application if specified in config_pinnerCameraApp.</p>
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070082 */
83public final class PinnerService extends SystemService {
84 private static final boolean DEBUG = false;
85 private static final String TAG = "PinnerService";
Jorim Jaggi7119800f2018-07-09 17:57:10 +020086
Daniel Colascione9779b5e2018-03-21 19:13:57 -070087 private static final String PIN_META_FILENAME = "pinlist.meta";
88 private static final int PAGE_SIZE = (int) Os.sysconf(OsConstants._SC_PAGESIZE);
Jorim Jaggi7119800f2018-07-09 17:57:10 +020089 private static final int MATCH_FLAGS = PackageManager.MATCH_DEFAULT_ONLY
90 | PackageManager.MATCH_DIRECT_BOOT_AWARE
91 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
92
93 private static final int KEY_CAMERA = 0;
94 private static final int KEY_HOME = 1;
95
96 private static final int MAX_CAMERA_PIN_SIZE = 80 * (1 << 20); // 80MB max for camera app.
97 private static final int MAX_HOME_PIN_SIZE = 6 * (1 << 20); // 6MB max for home app.
98
99 @IntDef({KEY_CAMERA, KEY_HOME})
100 @Retention(RetentionPolicy.SOURCE)
101 public @interface AppKey {}
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700102
103 private final Context mContext;
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200104 private final ActivityManagerInternal mAmInternal;
105 private final IActivityManager mAm;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700106
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200107 /** The list of the statically pinned files. */
108 @GuardedBy("this")
109 private final ArrayList<PinnedFile> mPinnedFiles = new ArrayList<>();
110
111 /** The list of the pinned apps. This is a map from {@link AppKey} to a pinned app. */
112 @GuardedBy("this")
113 private final ArrayMap<Integer, PinnedApp> mPinnedApps = new ArrayMap<>();
114
115 /**
116 * The list of the pinned apps that need to be repinned as soon as the all processes of a given
117 * uid are no longer active. Note that with background dex opt, the new dex/vdex files are only
118 * loaded into the processes once it restarts. So in case background dex opt recompiled these
119 * files, we still need to keep the old ones pinned until the processes restart.
120 * <p>
121 * This is a map from uid to {@link AppKey}
122 */
123 @GuardedBy("this")
124 private final ArrayMap<Integer, Integer> mPendingRepin = new ArrayMap<>();
125
126 /**
127 * A set of {@link AppKey} that are configured to be pinned.
128 */
129 private final ArraySet<Integer> mPinKeys = new ArraySet<>();
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700130
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700131 private BinderService mBinderService;
Philip Cuadraa95cea02016-07-06 16:00:32 -0700132 private PinnerHandler mPinnerHandler = null;
133
Carmen Jacksonf107a232017-05-16 10:37:26 -0700134 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
135 @Override
136 public void onReceive(Context context, Intent intent) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200137 // If an app has updated, update pinned files accordingly.
138 if (Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())) {
Carmen Jacksonf107a232017-05-16 10:37:26 -0700139 Uri packageUri = intent.getData();
Calin Juravle31ce3a82017-05-22 17:49:01 -0700140 String packageName = packageUri.getSchemeSpecificPart();
141 ArraySet<String> updatedPackages = new ArraySet<>();
142 updatedPackages.add(packageName);
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200143 update(updatedPackages, true /* force */);
Carmen Jacksonf107a232017-05-16 10:37:26 -0700144 }
145 }
146 };
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700147
148 public PinnerService(Context context) {
149 super(context);
150
151 mContext = context;
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200152 boolean shouldPinCamera = context.getResources().getBoolean(
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700153 com.android.internal.R.bool.config_pinnerCameraApp);
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200154 boolean shouldPinHome = context.getResources().getBoolean(
155 com.android.internal.R.bool.config_pinnerHomeApp);
156 if (shouldPinCamera) {
157 mPinKeys.add(KEY_CAMERA);
158 }
159 if (shouldPinHome) {
160 mPinKeys.add(KEY_HOME);
161 }
Philip Cuadraa95cea02016-07-06 16:00:32 -0700162 mPinnerHandler = new PinnerHandler(BackgroundThread.get().getLooper());
Carmen Jacksonf107a232017-05-16 10:37:26 -0700163
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200164 mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
165 mAm = ActivityManager.getService();
166
Carmen Jacksonf107a232017-05-16 10:37:26 -0700167 IntentFilter filter = new IntentFilter();
168 filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
169 filter.addDataScheme("package");
170 mContext.registerReceiver(mBroadcastReceiver, filter);
Jorim Jaggi0c849962018-07-15 14:24:38 +0200171
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200172 registerUidListener();
Jorim Jaggi0c849962018-07-15 14:24:38 +0200173 registerUserSetupCompleteListener();
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700174 }
175
176 @Override
177 public void onStart() {
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700178 if (DEBUG) {
179 Slog.i(TAG, "Starting PinnerService");
180 }
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700181 mBinderService = new BinderService();
182 publishBinderService("pinner", mBinderService);
Carmen Jacksonf107a232017-05-16 10:37:26 -0700183 publishLocalService(PinnerService.class, this);
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700184
Jeff Sharkey847bd852016-08-03 17:20:03 -0600185 mPinnerHandler.obtainMessage(PinnerHandler.PIN_ONSTART_MSG).sendToTarget();
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200186 sendPinAppsMessage(UserHandle.USER_SYSTEM);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700187 }
188
189 /**
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200190 * Repin apps on user switch.
191 * <p>
192 * If more than one user is using the device each user may set a different preference for the
193 * individual apps. Make sure that user's preference is pinned into memory.
Philip Cuadraa95cea02016-07-06 16:00:32 -0700194 */
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700195 @Override
196 public void onSwitchUser(int userHandle) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200197 sendPinAppsMessage(userHandle);
198 }
199
200 @Override
201 public void onUnlockUser(int userHandle) {
202 sendPinAppsMessage(userHandle);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700203 }
204
Philip Cuadraa95cea02016-07-06 16:00:32 -0700205 /**
Carmen Jacksonf107a232017-05-16 10:37:26 -0700206 * Update the currently pinned files.
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200207 * Specifically, this only updates pinning for the apps that need to be pinned.
Carmen Jacksonf107a232017-05-16 10:37:26 -0700208 * The other files pinned in onStart will not need to be updated.
209 */
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200210 public void update(ArraySet<String> updatedPackages, boolean force) {
211 int currentUser = ActivityManager.getCurrentUser();
212 for (int i = mPinKeys.size() - 1; i >= 0; i--) {
213 int key = mPinKeys.valueAt(i);
214 ApplicationInfo info = getInfoForKey(key, currentUser);
215 if (info != null && updatedPackages.contains(info.packageName)) {
216 Slog.i(TAG, "Updating pinned files for " + info.packageName + " force=" + force);
217 sendPinAppMessage(key, currentUser, force);
218 }
Calin Juravle31ce3a82017-05-22 17:49:01 -0700219 }
Carmen Jacksonf107a232017-05-16 10:37:26 -0700220 }
221
222 /**
Philip Cuadraa95cea02016-07-06 16:00:32 -0700223 * Handler for on start pinning message
224 */
225 private void handlePinOnStart() {
226 // Files to pin come from the overlay and can be specified per-device config
227 String[] filesToPin = mContext.getResources().getStringArray(
228 com.android.internal.R.array.config_defaultPinnerServiceFiles);
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700229 // Continue trying to pin each file even if we fail to pin some of them
230 for (String fileToPin : filesToPin) {
231 PinnedFile pf = pinFile(fileToPin,
232 Integer.MAX_VALUE,
233 /*attemptPinIntrospection=*/false);
234 if (pf == null) {
235 Slog.e(TAG, "Failed to pin file = " + fileToPin);
236 continue;
237 }
238
239 synchronized (this) {
240 mPinnedFiles.add(pf);
Philip Cuadraa95cea02016-07-06 16:00:32 -0700241 }
242 }
243 }
244
Jorim Jaggi0c849962018-07-15 14:24:38 +0200245 /**
246 * Registers a listener to repin the home app when user setup is complete, as the home intent
247 * initially resolves to setup wizard, but once setup is complete, it will resolve to the
248 * regular home app.
249 */
250 private void registerUserSetupCompleteListener() {
251 Uri userSetupCompleteUri = Settings.Secure.getUriFor(
252 Settings.Secure.USER_SETUP_COMPLETE);
253 mContext.getContentResolver().registerContentObserver(userSetupCompleteUri,
254 false, new ContentObserver(null) {
255 @Override
256 public void onChange(boolean selfChange, Uri uri) {
257 if (userSetupCompleteUri.equals(uri)) {
258 sendPinAppMessage(KEY_HOME, ActivityManager.getCurrentUser(),
259 true /* force */);
260 }
261 }
262 }, UserHandle.USER_ALL);
263 }
264
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200265 private void registerUidListener() {
266 try {
267 mAm.registerUidObserver(new IUidObserver.Stub() {
268 @Override
269 public void onUidGone(int uid, boolean disabled) throws RemoteException {
270 mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
271 PinnerService::handleUidGone, PinnerService.this, uid));
272 }
273
274 @Override
275 public void onUidActive(int uid) throws RemoteException {
276 mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
277 PinnerService::handleUidActive, PinnerService.this, uid));
278 }
279
280 @Override
281 public void onUidIdle(int uid, boolean disabled) throws RemoteException {
282 }
283
284 @Override
285 public void onUidStateChanged(int uid, int procState, long procStateSeq)
286 throws RemoteException {
287 }
288
289 @Override
290 public void onUidCachedChanged(int uid, boolean cached) throws RemoteException {
291 }
292 }, UID_OBSERVER_GONE | UID_OBSERVER_ACTIVE, 0, "system");
293 } catch (RemoteException e) {
294 Slog.e(TAG, "Failed to register uid observer", e);
295 }
296 }
297
298 private void handleUidGone(int uid) {
299 updateActiveState(uid, false /* active */);
300 int key;
301 synchronized (this) {
302
303 // In case we have a pending repin, repin now. See mPendingRepin for more information.
304 key = mPendingRepin.getOrDefault(uid, -1);
305 if (key == -1) {
306 return;
307 }
308 mPendingRepin.remove(uid);
309 }
310 pinApp(key, ActivityManager.getCurrentUser(), false /* force */);
311 }
312
313 private void handleUidActive(int uid) {
314 updateActiveState(uid, true /* active */);
315 }
316
317 private void updateActiveState(int uid, boolean active) {
318 synchronized (this) {
319 for (int i = mPinnedApps.size() - 1; i >= 0; i--) {
320 PinnedApp app = mPinnedApps.valueAt(i);
321 if (app.uid == uid) {
322 app.active = active;
323 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700324 }
325 }
326 }
327
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200328 private void unpinApp(@AppKey int key) {
329 ArrayList<PinnedFile> pinnedAppFiles;
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700330 synchronized (this) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200331 PinnedApp app = mPinnedApps.get(key);
332 if (app == null) {
333 return;
334 }
335 mPinnedApps.remove(key);
336 pinnedAppFiles = new ArrayList<>(app.mFiles);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700337 }
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200338 for (PinnedFile pinnedFile : pinnedAppFiles) {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700339 pinnedFile.close();
340 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700341 }
342
343 private boolean isResolverActivity(ActivityInfo info) {
344 return ResolverActivity.class.getName().equals(info.name);
345 }
346
347 private ApplicationInfo getCameraInfo(int userHandle) {
348 // find the camera via an intent
349 // use INTENT_ACTION_STILL_IMAGE_CAMERA instead of _SECURE. On a
350 // device without a fbe enabled, the _SECURE intent will never get set.
351 Intent cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200352 return getApplicationInfoForIntent(cameraIntent, userHandle);
353 }
354
355 private ApplicationInfo getHomeInfo(int userHandle) {
356 Intent intent = mAmInternal.getHomeIntent();
357 return getApplicationInfoForIntent(intent, userHandle);
358 }
359
360 private ApplicationInfo getApplicationInfoForIntent(Intent intent, int userHandle) {
361 if (intent == null) {
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700362 return null;
363 }
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200364 ResolveInfo info = mContext.getPackageManager().resolveActivityAsUser(intent,
365 MATCH_FLAGS, userHandle);
366 if (info == null) {
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700367 return null;
368 }
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200369 if (isResolverActivity(info.activityInfo)) {
370 return null;
371 }
372 return info.activityInfo.applicationInfo;
373 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700374
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200375 private void sendPinAppsMessage(int userHandle) {
376 mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinApps, this,
377 userHandle));
378 }
379
380 private void pinApps(int userHandle) {
381 for (int i = mPinKeys.size() - 1; i >= 0; i--) {
382 int key = mPinKeys.valueAt(i);
383 pinApp(key, userHandle, true /* force */);
384 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700385 }
386
Carmen Jacksonf107a232017-05-16 10:37:26 -0700387 /**
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200388 * @see #pinApp(int, int, boolean)
Carmen Jacksonf107a232017-05-16 10:37:26 -0700389 */
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200390 private void sendPinAppMessage(int key, int userHandle, boolean force) {
391 mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinApp, this,
392 key, userHandle, force));
393 }
394
395 /**
396 * Pins an app of a specific type {@code key}.
397 *
398 * @param force If false, this will not repin the app if it's currently active. See
399 * {@link #mPendingRepin}.
400 */
401 private void pinApp(int key, int userHandle, boolean force) {
402 int uid = getUidForKey(key);
403
404 // In case the app is currently active, don't repin until next process restart. See
405 // mPendingRepin for more information.
406 if (!force && uid != -1) {
407 synchronized (this) {
408 mPendingRepin.put(uid, key);
409 }
410 return;
411 }
412 unpinApp(key);
413 ApplicationInfo info = getInfoForKey(key, userHandle);
414 if (info != null) {
415 pinApp(key, info);
416 }
417 }
418
419 /**
420 * Checks whether the pinned package with {@code key} is active or not.
421
422 * @return The uid of the pinned app, or {@code -1} otherwise.
423 */
424 private int getUidForKey(@AppKey int key) {
425 synchronized (this) {
426 PinnedApp existing = mPinnedApps.get(key);
427 return existing != null && existing.active
428 ? existing.uid
429 : -1;
430 }
431 }
432
433 /**
434 * Retrieves the current application info for the given app type.
435 *
436 * @param key The app type to retrieve the info for.
437 * @param userHandle The user id of the current user.
438 */
439 private @Nullable ApplicationInfo getInfoForKey(@AppKey int key, int userHandle) {
440 switch (key) {
441 case KEY_CAMERA:
442 return getCameraInfo(userHandle);
443 case KEY_HOME:
444 return getHomeInfo(userHandle);
445 default:
446 return null;
447 }
448 }
449
450 /**
451 * @return The app type name for {@code key}.
452 */
453 private String getNameForKey(@AppKey int key) {
454 switch (key) {
455 case KEY_CAMERA:
456 return "Camera";
457 case KEY_HOME:
458 return "Home";
459 default:
460 return null;
461 }
462 }
463
464 /**
465 * @return The maximum amount of bytes to be pinned for an app of type {@code key}.
466 */
467 private int getSizeLimitForKey(@AppKey int key) {
468 switch (key) {
469 case KEY_CAMERA:
470 return MAX_CAMERA_PIN_SIZE;
471 case KEY_HOME:
472 return MAX_HOME_PIN_SIZE;
473 default:
474 return 0;
475 }
476 }
477
478 /**
479 * Pins an application.
480 *
481 * @param key The key of the app to pin.
482 * @param appInfo The corresponding app info.
483 */
484 private void pinApp(@AppKey int key, @Nullable ApplicationInfo appInfo) {
485 if (appInfo == null) {
486 return;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700487 }
488
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200489 PinnedApp pinnedApp = new PinnedApp(appInfo);
490 synchronized (this) {
491 mPinnedApps.put(key, pinnedApp);
492 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700493
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200494 // pin APK
495 int pinSizeLimit = getSizeLimitForKey(key);
496 String apk = appInfo.sourceDir;
497 PinnedFile pf = pinFile(apk, pinSizeLimit, /*attemptPinIntrospection=*/true);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700498 if (pf == null) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200499 Slog.e(TAG, "Failed to pin " + apk);
500 return;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700501 }
502 if (DEBUG) {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700503 Slog.i(TAG, "Pinned " + pf.fileName);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700504 }
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700505 synchronized (this) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200506 pinnedApp.mFiles.add(pf);
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700507 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700508
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700509 // determine the ABI from either ApplicationInfo or Build
510 String arch = "arm";
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200511 if (appInfo.primaryCpuAbi != null) {
512 if (VMRuntime.is64BitAbi(appInfo.primaryCpuAbi)) {
John Eckerdal60b07cd2016-11-03 14:04:47 +0100513 arch = arch + "64";
514 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700515 } else {
516 if (VMRuntime.is64BitAbi(Build.SUPPORTED_ABIS[0])) {
517 arch = arch + "64";
518 }
519 }
Philip Cuadrad9bd8842016-07-12 17:29:38 -0700520
521 // get the path to the odex or oat file
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200522 String baseCodePath = appInfo.getBaseCodePath();
Calin Juravle128721a2017-05-15 20:20:50 -0700523 String[] files = null;
Philip Cuadrad9bd8842016-07-12 17:29:38 -0700524 try {
Calin Juravle128721a2017-05-15 20:20:50 -0700525 files = DexFile.getDexFileOutputPaths(baseCodePath, arch);
Philip Cuadrad9bd8842016-07-12 17:29:38 -0700526 } catch (IOException ioe) {}
Calin Juravle128721a2017-05-15 20:20:50 -0700527 if (files == null) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200528 return;
Philip Cuadrad9bd8842016-07-12 17:29:38 -0700529 }
530
531 //not pinning the oat/odex is not a fatal error
Calin Juravle128721a2017-05-15 20:20:50 -0700532 for (String file : files) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200533 pf = pinFile(file, pinSizeLimit, /*attemptPinIntrospection=*/false);
Calin Juravle128721a2017-05-15 20:20:50 -0700534 if (pf != null) {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700535 synchronized (this) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200536 pinnedApp.mFiles.add(pf);
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700537 }
Calin Juravle128721a2017-05-15 20:20:50 -0700538 if (DEBUG) {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700539 Slog.i(TAG, "Pinned " + pf.fileName);
Calin Juravle128721a2017-05-15 20:20:50 -0700540 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700541 }
542 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700543 }
544
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700545 /** mlock length bytes of fileToPin in memory
546 *
547 * If attemptPinIntrospection is true, then treat the file to pin as a zip file and
548 * look for a "pinlist.meta" file in the archive root directory. The structure of this
549 * file is a PINLIST_META as described below:
550 *
551 * <pre>
552 * PINLIST_META: PIN_RANGE*
553 * PIN_RANGE: PIN_START PIN_LENGTH
554 * PIN_START: big endian i32: offset in bytes of pin region from file start
555 * PIN_LENGTH: big endian i32: length of pin region in bytes
556 * </pre>
557 *
558 * (We use big endian because that's what DataInputStream is hardcoded to use.)
559 *
560 * If attemptPinIntrospection is false, then we use a single implicit PIN_RANGE of (0,
561 * maxBytesToPin); that is, we attempt to pin the first maxBytesToPin bytes of the file.
562 *
563 * After we open a file, we march through the list of pin ranges and attempt to pin
564 * each one, stopping after we've pinned maxBytesToPin bytes. (We may truncate the last
565 * pinned range to fit.) In this way, by choosing to emit certain PIN_RANGE pairs
566 * before others, file generators can express pins in priority order, making most
567 * effective use of the pinned-page quota.
568 *
569 * N.B. Each PIN_RANGE is clamped to the actual bounds of the file; all inputs have a
570 * meaningful interpretation. Also, a range locking a single byte of a page locks the
571 * whole page. Any truncated PIN_RANGE at EOF is ignored. Overlapping pinned entries
572 * are legal, but each pin of a byte counts toward the pin quota regardless of whether
573 * that byte has already been pinned, so the generator of PINLIST_META ought to ensure
574 * that ranges are non-overlapping.
575 *
576 * @param fileToPin Path to file to pin
577 * @param maxBytesToPin Maximum number of bytes to pin
578 * @param attemptPinIntrospection If true, try to open file as a
579 * zip in order to extract the
580 * @return Pinned memory resource owner thing or null on error
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700581 */
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700582 private static PinnedFile pinFile(String fileToPin,
583 int maxBytesToPin,
584 boolean attemptPinIntrospection) {
585 ZipFile fileAsZip = null;
586 InputStream pinRangeStream = null;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700587 try {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700588 if (attemptPinIntrospection) {
589 fileAsZip = maybeOpenZip(fileToPin);
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700590 }
591
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700592 if (fileAsZip != null) {
593 pinRangeStream = maybeOpenPinMetaInZip(fileAsZip, fileToPin);
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700594 }
595
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700596 Slog.d(TAG, "pinRangeStream: " + pinRangeStream);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700597
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700598 PinRangeSource pinRangeSource = (pinRangeStream != null)
599 ? new PinRangeSourceStream(pinRangeStream)
600 : new PinRangeSourceStatic(0, Integer.MAX_VALUE /* will be clipped */);
601 return pinFileRanges(fileToPin, maxBytesToPin, pinRangeSource);
602 } finally {
603 safeClose(pinRangeStream);
604 safeClose(fileAsZip); // Also closes any streams we've opened
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700605 }
606 }
607
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700608 /**
609 * Attempt to open a file as a zip file. On any sort of corruption, log, swallow the
610 * error, and return null.
611 */
612 private static ZipFile maybeOpenZip(String fileName) {
613 ZipFile zip = null;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700614 try {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700615 zip = new ZipFile(fileName);
616 } catch (IOException ex) {
617 Slog.w(TAG,
618 String.format(
619 "could not open \"%s\" as zip: pinning as blob",
620 fileName),
621 ex);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700622 }
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700623 return zip;
624 }
625
626 /**
627 * Open a pin metadata file in the zip if one is present.
628 *
629 * @param zipFile Zip file to search
630 * @return Open input stream or null on any error
631 */
632 private static InputStream maybeOpenPinMetaInZip(ZipFile zipFile, String fileName) {
633 ZipEntry pinMetaEntry = zipFile.getEntry(PIN_META_FILENAME);
634 InputStream pinMetaStream = null;
635 if (pinMetaEntry != null) {
636 try {
637 pinMetaStream = zipFile.getInputStream(pinMetaEntry);
638 } catch (IOException ex) {
639 Slog.w(TAG,
640 String.format("error reading pin metadata \"%s\": pinning as blob",
641 fileName),
642 ex);
643 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700644 }
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700645 return pinMetaStream;
646 }
647
648 private static abstract class PinRangeSource {
649 /** Retrive a range to pin.
650 *
651 * @param outPinRange Receives the pin region
652 * @return True if we filled in outPinRange or false if we're out of pin entries
653 */
654 abstract boolean read(PinRange outPinRange);
655 }
656
657 private static final class PinRangeSourceStatic extends PinRangeSource {
658 private final int mPinStart;
659 private final int mPinLength;
660 private boolean mDone = false;
661
662 PinRangeSourceStatic(int pinStart, int pinLength) {
663 mPinStart = pinStart;
664 mPinLength = pinLength;
665 }
666
667 @Override
668 boolean read(PinRange outPinRange) {
669 outPinRange.start = mPinStart;
670 outPinRange.length = mPinLength;
671 boolean done = mDone;
672 mDone = true;
673 return !done;
674 }
675 }
676
677 private static final class PinRangeSourceStream extends PinRangeSource {
678 private final DataInputStream mStream;
679 private boolean mDone = false;
680
681 PinRangeSourceStream(InputStream stream) {
682 mStream = new DataInputStream(stream);
683 }
684
685 @Override
686 boolean read(PinRange outPinRange) {
687 if (!mDone) {
688 try {
689 outPinRange.start = mStream.readInt();
690 outPinRange.length = mStream.readInt();
691 } catch (IOException ex) {
692 mDone = true;
693 }
694 }
695 return !mDone;
696 }
697 }
698
699 /**
700 * Helper for pinFile.
701 *
702 * @param fileToPin Name of file to pin
703 * @param maxBytesToPin Maximum number of bytes to pin
704 * @param pinRangeSource Read PIN_RANGE entries from this stream to tell us what bytes
705 * to pin.
706 * @return PinnedFile or null on error
707 */
708 private static PinnedFile pinFileRanges(
709 String fileToPin,
710 int maxBytesToPin,
711 PinRangeSource pinRangeSource)
712 {
713 FileDescriptor fd = new FileDescriptor();
714 long address = -1;
715 int mapSize = 0;
716
717 try {
718 int openFlags = (OsConstants.O_RDONLY |
719 OsConstants.O_CLOEXEC |
720 OsConstants.O_NOFOLLOW);
721 fd = Os.open(fileToPin, openFlags, 0);
722 mapSize = (int) Math.min(Os.fstat(fd).st_size, Integer.MAX_VALUE);
723 address = Os.mmap(0, mapSize,
724 OsConstants.PROT_READ,
725 OsConstants.MAP_SHARED,
726 fd, /*offset=*/0);
727
728 PinRange pinRange = new PinRange();
729 int bytesPinned = 0;
730
731 // We pin at page granularity, so make sure the limit is page-aligned
732 if (maxBytesToPin % PAGE_SIZE != 0) {
733 maxBytesToPin -= maxBytesToPin % PAGE_SIZE;
734 }
735
736 while (bytesPinned < maxBytesToPin && pinRangeSource.read(pinRange)) {
737 int pinStart = pinRange.start;
738 int pinLength = pinRange.length;
739 pinStart = clamp(0, pinStart, mapSize);
740 pinLength = clamp(0, pinLength, mapSize - pinStart);
741 pinLength = Math.min(maxBytesToPin - bytesPinned, pinLength);
742
743 // mlock doesn't require the region to be page-aligned, but we snap the
744 // lock region to page boundaries anyway so that we don't under-count
745 // locking a single byte of a page as a charge of one byte even though the
746 // OS will retain the whole page. Thanks to this adjustment, we slightly
747 // over-count the pin charge of back-to-back pins touching the same page,
748 // but better that than undercounting. Besides: nothing stops pin metafile
749 // creators from making the actual regions page-aligned.
750 pinLength += pinStart % PAGE_SIZE;
751 pinStart -= pinStart % PAGE_SIZE;
752 if (pinLength % PAGE_SIZE != 0) {
753 pinLength += PAGE_SIZE - pinLength % PAGE_SIZE;
754 }
755 pinLength = clamp(0, pinLength, maxBytesToPin - bytesPinned);
756
757 if (pinLength > 0) {
758 if (DEBUG) {
759 Slog.d(TAG,
760 String.format(
761 "pinning at %s %s bytes of %s",
762 pinStart, pinLength, fileToPin));
763 }
764 Os.mlock(address + pinStart, pinLength);
765 }
766 bytesPinned += pinLength;
767 }
768
769 PinnedFile pinnedFile = new PinnedFile(address, mapSize, fileToPin, bytesPinned);
770 address = -1; // Ownership transferred
771 return pinnedFile;
772 } catch (ErrnoException ex) {
773 Slog.e(TAG, "Could not pin file " + fileToPin, ex);
774 return null;
775 } finally {
776 safeClose(fd);
777 if (address >= 0) {
778 safeMunmap(address, mapSize);
779 }
780 }
781 }
782
783 private static int clamp(int min, int value, int max) {
784 return Math.max(min, Math.min(value, max));
785 }
786
787 private static void safeMunmap(long address, long mapSize) {
788 try {
789 Os.munmap(address, mapSize);
790 } catch (ErrnoException ex) {
791 Slog.w(TAG, "ignoring error in unmap", ex);
792 }
793 }
794
795 /**
796 * Close FD, swallowing irrelevant errors.
797 */
798 private static void safeClose(@Nullable FileDescriptor fd) {
799 if (fd != null && fd.valid()) {
800 try {
801 Os.close(fd);
802 } catch (ErrnoException ex) {
803 // Swallow the exception: non-EBADF errors in close(2)
804 // indicate deferred paging write errors, which we
805 // don't care about here. The underlying file
806 // descriptor is always closed.
807 if (ex.errno == OsConstants.EBADF) {
808 throw new AssertionError(ex);
809 }
810 }
811 }
812 }
813
814 /**
815 * Close closeable thing, swallowing errors.
816 */
817 private static void safeClose(@Nullable Closeable thing) {
818 if (thing != null) {
819 try {
820 thing.close();
821 } catch (IOException ex) {
822 Slog.w(TAG, "ignoring error closing resource: " + thing, ex);
823 }
824 }
825 }
826
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700827 private final class BinderService extends Binder {
828 @Override
829 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -0600830 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200831 synchronized (PinnerService.this) {
832 long totalSize = 0;
833 for (PinnedFile pinnedFile : mPinnedFiles) {
834 pw.format("%s %s\n", pinnedFile.fileName, pinnedFile.bytesPinned);
835 totalSize += pinnedFile.bytesPinned;
836 }
837 pw.println();
838 for (int key : mPinnedApps.keySet()) {
839 PinnedApp app = mPinnedApps.get(key);
840 pw.print(getNameForKey(key));
841 pw.print(" uid="); pw.print(app.uid);
842 pw.print(" active="); pw.print(app.active);
843 pw.println();
844 for (PinnedFile pf : mPinnedApps.get(key).mFiles) {
845 pw.print(" "); pw.format("%s %s\n", pf.fileName, pf.bytesPinned);
846 totalSize += pf.bytesPinned;
847 }
848 }
849 pw.format("Total size: %s\n", totalSize);
850 pw.println();
851 if (!mPendingRepin.isEmpty()) {
852 pw.print("Pending repin: ");
853 for (int key : mPendingRepin.values()) {
854 pw.print(getNameForKey(key)); pw.print(' ');
855 }
856 pw.println();
857 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700858 }
859 }
860 }
861
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700862 private static final class PinnedFile implements AutoCloseable {
863 private long mAddress;
864 final int mapSize;
865 final String fileName;
866 final int bytesPinned;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700867
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700868 PinnedFile(long address, int mapSize, String fileName, int bytesPinned) {
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700869 mAddress = address;
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700870 this.mapSize = mapSize;
871 this.fileName = fileName;
872 this.bytesPinned = bytesPinned;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700873 }
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700874
875 @Override
876 public void close() {
877 if (mAddress >= 0) {
878 safeMunmap(mAddress, mapSize);
879 mAddress = -1;
880 }
881 }
882
883 @Override
884 public void finalize() {
885 close();
886 }
887 }
888
889 final static class PinRange {
890 int start;
891 int length;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700892 }
Philip Cuadraa95cea02016-07-06 16:00:32 -0700893
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200894 /**
895 * Represents an app that was pinned.
896 */
897 private final class PinnedApp {
898
899 /**
900 * The uid of the package being pinned. This stays constant while the package stays
901 * installed.
902 */
903 final int uid;
904
905 /** Whether it is currently active, i.e. there is a running process from that package. */
906 boolean active;
907
908 /** List of pinned files. */
909 final ArrayList<PinnedFile> mFiles = new ArrayList<>();
910
911 private PinnedApp(ApplicationInfo appInfo) {
912 uid = appInfo.uid;
913 active = mAmInternal.isUidActive(uid);
914 }
915 }
916
Philip Cuadraa95cea02016-07-06 16:00:32 -0700917 final class PinnerHandler extends Handler {
Philip Cuadraa95cea02016-07-06 16:00:32 -0700918 static final int PIN_ONSTART_MSG = 4001;
919
920 public PinnerHandler(Looper looper) {
921 super(looper, null, true);
922 }
923
924 @Override
925 public void handleMessage(Message msg) {
926 switch (msg.what) {
Philip Cuadraa95cea02016-07-06 16:00:32 -0700927 case PIN_ONSTART_MSG:
928 {
929 handlePinOnStart();
930 }
931 break;
932
933 default:
934 super.handleMessage(msg);
935 }
936 }
937 }
938
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700939}