blob: 0deaee7f787824222cfe679282c9b6794a9f4a62 [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;
Carmen Jackson0ff99462018-08-09 14:51:39 -070046import android.os.UserManager;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070047import android.provider.MediaStore;
Jorim Jaggi0c849962018-07-15 14:24:38 +020048import android.provider.Settings;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070049import android.system.ErrnoException;
50import android.system.Os;
51import android.system.OsConstants;
Jorim Jaggi7119800f2018-07-09 17:57:10 +020052import android.util.ArrayMap;
Calin Juravle31ce3a82017-05-22 17:49:01 -070053import android.util.ArraySet;
Jeff Sharkey847bd852016-08-03 17:20:03 -060054import android.util.Slog;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070055
Jorim Jaggi7119800f2018-07-09 17:57:10 +020056import com.android.internal.annotations.GuardedBy;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070057import com.android.internal.app.ResolverActivity;
Philip Cuadraa95cea02016-07-06 16:00:32 -070058import com.android.internal.os.BackgroundThread;
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -060059import com.android.internal.util.DumpUtils;
Jorim Jaggi7119800f2018-07-09 17:57:10 +020060import com.android.internal.util.function.pooled.PooledLambda;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070061
Philip Cuadrad9bd8842016-07-12 17:29:38 -070062import dalvik.system.DexFile;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070063import dalvik.system.VMRuntime;
64
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070065import java.io.FileDescriptor;
Daniel Colascione9779b5e2018-03-21 19:13:57 -070066import java.io.Closeable;
67import java.io.InputStream;
68import java.io.DataInputStream;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070069import java.io.IOException;
70import java.io.PrintWriter;
Jorim Jaggi7119800f2018-07-09 17:57:10 +020071import java.lang.annotation.Retention;
72import java.lang.annotation.RetentionPolicy;
Carmen Jackson879fb682018-07-20 16:43:09 -070073import java.util.List;
Jeff Sharkey847bd852016-08-03 17:20:03 -060074import java.util.ArrayList;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070075
Daniel Colascione9779b5e2018-03-21 19:13:57 -070076import java.util.zip.ZipFile;
Daniel Colascione9779b5e2018-03-21 19:13:57 -070077import java.util.zip.ZipEntry;
78
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070079/**
80 * <p>PinnerService pins important files for key processes in memory.</p>
81 * <p>Files to pin are specified in the config_defaultPinnerServiceFiles
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070082 * overlay.</p>
83 * <p>Pin the default camera application if specified in config_pinnerCameraApp.</p>
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070084 */
85public final class PinnerService extends SystemService {
86 private static final boolean DEBUG = false;
87 private static final String TAG = "PinnerService";
Jorim Jaggi7119800f2018-07-09 17:57:10 +020088
Daniel Colascione9779b5e2018-03-21 19:13:57 -070089 private static final String PIN_META_FILENAME = "pinlist.meta";
90 private static final int PAGE_SIZE = (int) Os.sysconf(OsConstants._SC_PAGESIZE);
Jorim Jaggi7119800f2018-07-09 17:57:10 +020091 private static final int MATCH_FLAGS = PackageManager.MATCH_DEFAULT_ONLY
92 | PackageManager.MATCH_DIRECT_BOOT_AWARE
93 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
94
95 private static final int KEY_CAMERA = 0;
96 private static final int KEY_HOME = 1;
97
98 private static final int MAX_CAMERA_PIN_SIZE = 80 * (1 << 20); // 80MB max for camera app.
99 private static final int MAX_HOME_PIN_SIZE = 6 * (1 << 20); // 6MB max for home app.
100
101 @IntDef({KEY_CAMERA, KEY_HOME})
102 @Retention(RetentionPolicy.SOURCE)
103 public @interface AppKey {}
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700104
105 private final Context mContext;
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200106 private final ActivityManagerInternal mAmInternal;
107 private final IActivityManager mAm;
Carmen Jackson0ff99462018-08-09 14:51:39 -0700108 private final UserManager mUserManager;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700109
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200110 /** The list of the statically pinned files. */
111 @GuardedBy("this")
112 private final ArrayList<PinnedFile> mPinnedFiles = new ArrayList<>();
113
114 /** The list of the pinned apps. This is a map from {@link AppKey} to a pinned app. */
115 @GuardedBy("this")
116 private final ArrayMap<Integer, PinnedApp> mPinnedApps = new ArrayMap<>();
117
118 /**
119 * The list of the pinned apps that need to be repinned as soon as the all processes of a given
120 * uid are no longer active. Note that with background dex opt, the new dex/vdex files are only
121 * loaded into the processes once it restarts. So in case background dex opt recompiled these
122 * files, we still need to keep the old ones pinned until the processes restart.
123 * <p>
124 * This is a map from uid to {@link AppKey}
125 */
126 @GuardedBy("this")
127 private final ArrayMap<Integer, Integer> mPendingRepin = new ArrayMap<>();
128
129 /**
130 * A set of {@link AppKey} that are configured to be pinned.
131 */
132 private final ArraySet<Integer> mPinKeys = new ArraySet<>();
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700133
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700134 private BinderService mBinderService;
Philip Cuadraa95cea02016-07-06 16:00:32 -0700135 private PinnerHandler mPinnerHandler = null;
136
Carmen Jacksonf107a232017-05-16 10:37:26 -0700137 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
138 @Override
139 public void onReceive(Context context, Intent intent) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200140 // If an app has updated, update pinned files accordingly.
141 if (Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())) {
Carmen Jacksonf107a232017-05-16 10:37:26 -0700142 Uri packageUri = intent.getData();
Calin Juravle31ce3a82017-05-22 17:49:01 -0700143 String packageName = packageUri.getSchemeSpecificPart();
144 ArraySet<String> updatedPackages = new ArraySet<>();
145 updatedPackages.add(packageName);
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200146 update(updatedPackages, true /* force */);
Carmen Jacksonf107a232017-05-16 10:37:26 -0700147 }
148 }
149 };
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700150
151 public PinnerService(Context context) {
152 super(context);
153
154 mContext = context;
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200155 boolean shouldPinCamera = context.getResources().getBoolean(
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700156 com.android.internal.R.bool.config_pinnerCameraApp);
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200157 boolean shouldPinHome = context.getResources().getBoolean(
158 com.android.internal.R.bool.config_pinnerHomeApp);
159 if (shouldPinCamera) {
160 mPinKeys.add(KEY_CAMERA);
161 }
162 if (shouldPinHome) {
163 mPinKeys.add(KEY_HOME);
164 }
Philip Cuadraa95cea02016-07-06 16:00:32 -0700165 mPinnerHandler = new PinnerHandler(BackgroundThread.get().getLooper());
Carmen Jacksonf107a232017-05-16 10:37:26 -0700166
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200167 mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
168 mAm = ActivityManager.getService();
169
Carmen Jackson0ff99462018-08-09 14:51:39 -0700170 mUserManager = mContext.getSystemService(UserManager.class);
171
Carmen Jacksonf107a232017-05-16 10:37:26 -0700172 IntentFilter filter = new IntentFilter();
173 filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
174 filter.addDataScheme("package");
175 mContext.registerReceiver(mBroadcastReceiver, filter);
Jorim Jaggi0c849962018-07-15 14:24:38 +0200176
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200177 registerUidListener();
Jorim Jaggi0c849962018-07-15 14:24:38 +0200178 registerUserSetupCompleteListener();
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700179 }
180
181 @Override
182 public void onStart() {
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700183 if (DEBUG) {
184 Slog.i(TAG, "Starting PinnerService");
185 }
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700186 mBinderService = new BinderService();
187 publishBinderService("pinner", mBinderService);
Carmen Jacksonf107a232017-05-16 10:37:26 -0700188 publishLocalService(PinnerService.class, this);
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700189
Jeff Sharkey847bd852016-08-03 17:20:03 -0600190 mPinnerHandler.obtainMessage(PinnerHandler.PIN_ONSTART_MSG).sendToTarget();
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200191 sendPinAppsMessage(UserHandle.USER_SYSTEM);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700192 }
193
194 /**
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200195 * Repin apps on user switch.
196 * <p>
197 * If more than one user is using the device each user may set a different preference for the
198 * individual apps. Make sure that user's preference is pinned into memory.
Philip Cuadraa95cea02016-07-06 16:00:32 -0700199 */
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700200 @Override
201 public void onSwitchUser(int userHandle) {
Carmen Jackson0ff99462018-08-09 14:51:39 -0700202 if (!mUserManager.isManagedProfile(userHandle)) {
203 sendPinAppsMessage(userHandle);
204 }
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200205 }
206
207 @Override
208 public void onUnlockUser(int userHandle) {
Carmen Jackson0ff99462018-08-09 14:51:39 -0700209 if (!mUserManager.isManagedProfile(userHandle)) {
210 sendPinAppsMessage(userHandle);
211 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700212 }
213
Philip Cuadraa95cea02016-07-06 16:00:32 -0700214 /**
Carmen Jacksonf107a232017-05-16 10:37:26 -0700215 * Update the currently pinned files.
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200216 * Specifically, this only updates pinning for the apps that need to be pinned.
Carmen Jacksonf107a232017-05-16 10:37:26 -0700217 * The other files pinned in onStart will not need to be updated.
218 */
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200219 public void update(ArraySet<String> updatedPackages, boolean force) {
220 int currentUser = ActivityManager.getCurrentUser();
221 for (int i = mPinKeys.size() - 1; i >= 0; i--) {
222 int key = mPinKeys.valueAt(i);
223 ApplicationInfo info = getInfoForKey(key, currentUser);
224 if (info != null && updatedPackages.contains(info.packageName)) {
225 Slog.i(TAG, "Updating pinned files for " + info.packageName + " force=" + force);
226 sendPinAppMessage(key, currentUser, force);
227 }
Calin Juravle31ce3a82017-05-22 17:49:01 -0700228 }
Carmen Jacksonf107a232017-05-16 10:37:26 -0700229 }
230
231 /**
Philip Cuadraa95cea02016-07-06 16:00:32 -0700232 * Handler for on start pinning message
233 */
234 private void handlePinOnStart() {
235 // Files to pin come from the overlay and can be specified per-device config
236 String[] filesToPin = mContext.getResources().getStringArray(
237 com.android.internal.R.array.config_defaultPinnerServiceFiles);
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700238 // Continue trying to pin each file even if we fail to pin some of them
239 for (String fileToPin : filesToPin) {
240 PinnedFile pf = pinFile(fileToPin,
241 Integer.MAX_VALUE,
242 /*attemptPinIntrospection=*/false);
243 if (pf == null) {
244 Slog.e(TAG, "Failed to pin file = " + fileToPin);
245 continue;
246 }
247
248 synchronized (this) {
249 mPinnedFiles.add(pf);
Philip Cuadraa95cea02016-07-06 16:00:32 -0700250 }
251 }
252 }
253
Jorim Jaggi0c849962018-07-15 14:24:38 +0200254 /**
255 * Registers a listener to repin the home app when user setup is complete, as the home intent
256 * initially resolves to setup wizard, but once setup is complete, it will resolve to the
257 * regular home app.
258 */
259 private void registerUserSetupCompleteListener() {
260 Uri userSetupCompleteUri = Settings.Secure.getUriFor(
261 Settings.Secure.USER_SETUP_COMPLETE);
262 mContext.getContentResolver().registerContentObserver(userSetupCompleteUri,
263 false, new ContentObserver(null) {
264 @Override
265 public void onChange(boolean selfChange, Uri uri) {
266 if (userSetupCompleteUri.equals(uri)) {
267 sendPinAppMessage(KEY_HOME, ActivityManager.getCurrentUser(),
268 true /* force */);
269 }
270 }
271 }, UserHandle.USER_ALL);
272 }
273
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200274 private void registerUidListener() {
275 try {
276 mAm.registerUidObserver(new IUidObserver.Stub() {
277 @Override
278 public void onUidGone(int uid, boolean disabled) throws RemoteException {
279 mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
280 PinnerService::handleUidGone, PinnerService.this, uid));
281 }
282
283 @Override
284 public void onUidActive(int uid) throws RemoteException {
285 mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
286 PinnerService::handleUidActive, PinnerService.this, uid));
287 }
288
289 @Override
290 public void onUidIdle(int uid, boolean disabled) throws RemoteException {
291 }
292
293 @Override
294 public void onUidStateChanged(int uid, int procState, long procStateSeq)
295 throws RemoteException {
296 }
297
298 @Override
299 public void onUidCachedChanged(int uid, boolean cached) throws RemoteException {
300 }
301 }, UID_OBSERVER_GONE | UID_OBSERVER_ACTIVE, 0, "system");
302 } catch (RemoteException e) {
303 Slog.e(TAG, "Failed to register uid observer", e);
304 }
305 }
306
307 private void handleUidGone(int uid) {
308 updateActiveState(uid, false /* active */);
309 int key;
310 synchronized (this) {
311
312 // In case we have a pending repin, repin now. See mPendingRepin for more information.
313 key = mPendingRepin.getOrDefault(uid, -1);
314 if (key == -1) {
315 return;
316 }
317 mPendingRepin.remove(uid);
318 }
319 pinApp(key, ActivityManager.getCurrentUser(), false /* force */);
320 }
321
322 private void handleUidActive(int uid) {
323 updateActiveState(uid, true /* active */);
324 }
325
326 private void updateActiveState(int uid, boolean active) {
327 synchronized (this) {
328 for (int i = mPinnedApps.size() - 1; i >= 0; i--) {
329 PinnedApp app = mPinnedApps.valueAt(i);
330 if (app.uid == uid) {
331 app.active = active;
332 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700333 }
334 }
335 }
336
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200337 private void unpinApp(@AppKey int key) {
338 ArrayList<PinnedFile> pinnedAppFiles;
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700339 synchronized (this) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200340 PinnedApp app = mPinnedApps.get(key);
341 if (app == null) {
342 return;
343 }
344 mPinnedApps.remove(key);
345 pinnedAppFiles = new ArrayList<>(app.mFiles);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700346 }
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200347 for (PinnedFile pinnedFile : pinnedAppFiles) {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700348 pinnedFile.close();
349 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700350 }
351
352 private boolean isResolverActivity(ActivityInfo info) {
353 return ResolverActivity.class.getName().equals(info.name);
354 }
355
356 private ApplicationInfo getCameraInfo(int userHandle) {
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700357 Intent cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
Carmen Jackson879fb682018-07-20 16:43:09 -0700358 ApplicationInfo info = getApplicationInfoForIntent(cameraIntent, userHandle,
359 false /* defaultToSystemApp */);
360
361 // If the STILL_IMAGE_CAMERA intent doesn't resolve, try the _SECURE intent.
362 // We don't use _SECURE first because it will never get set on a device
363 // without File-based Encryption. But if the user has only set the intent
364 // before unlocking their device, we may still be able to identify their
365 // preference using this intent.
366 if (info == null) {
367 cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE);
368 info = getApplicationInfoForIntent(cameraIntent, userHandle,
369 false /* defaultToSystemApp */);
370 }
371
372 // If the _SECURE intent doesn't resolve, try the original intent but request
373 // the system app for camera if there was more than one result.
374 if (info == null) {
375 cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
376 info = getApplicationInfoForIntent(cameraIntent, userHandle,
377 true /* defaultToSystemApp */);
378 }
379 return info;
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200380 }
381
382 private ApplicationInfo getHomeInfo(int userHandle) {
383 Intent intent = mAmInternal.getHomeIntent();
Carmen Jackson879fb682018-07-20 16:43:09 -0700384 return getApplicationInfoForIntent(intent, userHandle, false);
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200385 }
386
Carmen Jackson879fb682018-07-20 16:43:09 -0700387 private ApplicationInfo getApplicationInfoForIntent(Intent intent, int userHandle,
388 boolean defaultToSystemApp) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200389 if (intent == null) {
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700390 return null;
391 }
Carmen Jackson879fb682018-07-20 16:43:09 -0700392
393 ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivityAsUser(intent,
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200394 MATCH_FLAGS, userHandle);
Carmen Jackson879fb682018-07-20 16:43:09 -0700395
396 // If this intent can resolve to only one app, choose that one.
397 // Otherwise, if we've requested to default to the system app, return it;
398 // if we have not requested that default, return null if there's more than one option.
399 // If there's more than one system app, return null since we don't know which to pick.
400 if (resolveInfo == null) {
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700401 return null;
402 }
Carmen Jackson879fb682018-07-20 16:43:09 -0700403
404 if (!isResolverActivity(resolveInfo.activityInfo)) {
405 return resolveInfo.activityInfo.applicationInfo;
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200406 }
Carmen Jackson879fb682018-07-20 16:43:09 -0700407
408 if (defaultToSystemApp) {
409 List<ResolveInfo> infoList = mContext.getPackageManager()
410 .queryIntentActivitiesAsUser(intent, MATCH_FLAGS, userHandle);
411 ApplicationInfo systemAppInfo = null;
412 for (ResolveInfo info : infoList) {
413 if ((info.activityInfo.applicationInfo.flags
414 & ApplicationInfo.FLAG_SYSTEM) != 0) {
415 if (systemAppInfo == null) {
416 systemAppInfo = info.activityInfo.applicationInfo;
417 } else {
418 // If there's more than one system app, return null due to ambiguity.
419 return null;
420 }
421 }
422 }
423 return systemAppInfo;
424 }
425
426 return null;
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200427 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700428
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200429 private void sendPinAppsMessage(int userHandle) {
430 mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinApps, this,
431 userHandle));
432 }
433
434 private void pinApps(int userHandle) {
435 for (int i = mPinKeys.size() - 1; i >= 0; i--) {
436 int key = mPinKeys.valueAt(i);
437 pinApp(key, userHandle, true /* force */);
438 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700439 }
440
Carmen Jacksonf107a232017-05-16 10:37:26 -0700441 /**
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200442 * @see #pinApp(int, int, boolean)
Carmen Jacksonf107a232017-05-16 10:37:26 -0700443 */
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200444 private void sendPinAppMessage(int key, int userHandle, boolean force) {
445 mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinApp, this,
446 key, userHandle, force));
447 }
448
449 /**
450 * Pins an app of a specific type {@code key}.
451 *
452 * @param force If false, this will not repin the app if it's currently active. See
453 * {@link #mPendingRepin}.
454 */
455 private void pinApp(int key, int userHandle, boolean force) {
456 int uid = getUidForKey(key);
457
458 // In case the app is currently active, don't repin until next process restart. See
459 // mPendingRepin for more information.
460 if (!force && uid != -1) {
461 synchronized (this) {
462 mPendingRepin.put(uid, key);
463 }
464 return;
465 }
466 unpinApp(key);
467 ApplicationInfo info = getInfoForKey(key, userHandle);
468 if (info != null) {
469 pinApp(key, info);
470 }
471 }
472
473 /**
474 * Checks whether the pinned package with {@code key} is active or not.
475
476 * @return The uid of the pinned app, or {@code -1} otherwise.
477 */
478 private int getUidForKey(@AppKey int key) {
479 synchronized (this) {
480 PinnedApp existing = mPinnedApps.get(key);
481 return existing != null && existing.active
482 ? existing.uid
483 : -1;
484 }
485 }
486
487 /**
488 * Retrieves the current application info for the given app type.
489 *
490 * @param key The app type to retrieve the info for.
491 * @param userHandle The user id of the current user.
492 */
493 private @Nullable ApplicationInfo getInfoForKey(@AppKey int key, int userHandle) {
494 switch (key) {
495 case KEY_CAMERA:
496 return getCameraInfo(userHandle);
497 case KEY_HOME:
498 return getHomeInfo(userHandle);
499 default:
500 return null;
501 }
502 }
503
504 /**
505 * @return The app type name for {@code key}.
506 */
507 private String getNameForKey(@AppKey int key) {
508 switch (key) {
509 case KEY_CAMERA:
510 return "Camera";
511 case KEY_HOME:
512 return "Home";
513 default:
514 return null;
515 }
516 }
517
518 /**
519 * @return The maximum amount of bytes to be pinned for an app of type {@code key}.
520 */
521 private int getSizeLimitForKey(@AppKey int key) {
522 switch (key) {
523 case KEY_CAMERA:
524 return MAX_CAMERA_PIN_SIZE;
525 case KEY_HOME:
526 return MAX_HOME_PIN_SIZE;
527 default:
528 return 0;
529 }
530 }
531
532 /**
533 * Pins an application.
534 *
535 * @param key The key of the app to pin.
536 * @param appInfo The corresponding app info.
537 */
538 private void pinApp(@AppKey int key, @Nullable ApplicationInfo appInfo) {
539 if (appInfo == null) {
540 return;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700541 }
542
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200543 PinnedApp pinnedApp = new PinnedApp(appInfo);
544 synchronized (this) {
545 mPinnedApps.put(key, pinnedApp);
546 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700547
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200548 // pin APK
549 int pinSizeLimit = getSizeLimitForKey(key);
550 String apk = appInfo.sourceDir;
551 PinnedFile pf = pinFile(apk, pinSizeLimit, /*attemptPinIntrospection=*/true);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700552 if (pf == null) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200553 Slog.e(TAG, "Failed to pin " + apk);
554 return;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700555 }
556 if (DEBUG) {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700557 Slog.i(TAG, "Pinned " + pf.fileName);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700558 }
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700559 synchronized (this) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200560 pinnedApp.mFiles.add(pf);
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700561 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700562
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700563 // determine the ABI from either ApplicationInfo or Build
564 String arch = "arm";
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200565 if (appInfo.primaryCpuAbi != null) {
566 if (VMRuntime.is64BitAbi(appInfo.primaryCpuAbi)) {
John Eckerdal60b07cd2016-11-03 14:04:47 +0100567 arch = arch + "64";
568 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700569 } else {
570 if (VMRuntime.is64BitAbi(Build.SUPPORTED_ABIS[0])) {
571 arch = arch + "64";
572 }
573 }
Philip Cuadrad9bd8842016-07-12 17:29:38 -0700574
575 // get the path to the odex or oat file
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200576 String baseCodePath = appInfo.getBaseCodePath();
Calin Juravle128721a2017-05-15 20:20:50 -0700577 String[] files = null;
Philip Cuadrad9bd8842016-07-12 17:29:38 -0700578 try {
Calin Juravle128721a2017-05-15 20:20:50 -0700579 files = DexFile.getDexFileOutputPaths(baseCodePath, arch);
Philip Cuadrad9bd8842016-07-12 17:29:38 -0700580 } catch (IOException ioe) {}
Calin Juravle128721a2017-05-15 20:20:50 -0700581 if (files == null) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200582 return;
Philip Cuadrad9bd8842016-07-12 17:29:38 -0700583 }
584
585 //not pinning the oat/odex is not a fatal error
Calin Juravle128721a2017-05-15 20:20:50 -0700586 for (String file : files) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200587 pf = pinFile(file, pinSizeLimit, /*attemptPinIntrospection=*/false);
Calin Juravle128721a2017-05-15 20:20:50 -0700588 if (pf != null) {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700589 synchronized (this) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200590 pinnedApp.mFiles.add(pf);
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700591 }
Calin Juravle128721a2017-05-15 20:20:50 -0700592 if (DEBUG) {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700593 Slog.i(TAG, "Pinned " + pf.fileName);
Calin Juravle128721a2017-05-15 20:20:50 -0700594 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700595 }
596 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700597 }
598
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700599 /** mlock length bytes of fileToPin in memory
600 *
601 * If attemptPinIntrospection is true, then treat the file to pin as a zip file and
602 * look for a "pinlist.meta" file in the archive root directory. The structure of this
603 * file is a PINLIST_META as described below:
604 *
605 * <pre>
606 * PINLIST_META: PIN_RANGE*
607 * PIN_RANGE: PIN_START PIN_LENGTH
608 * PIN_START: big endian i32: offset in bytes of pin region from file start
609 * PIN_LENGTH: big endian i32: length of pin region in bytes
610 * </pre>
611 *
612 * (We use big endian because that's what DataInputStream is hardcoded to use.)
613 *
614 * If attemptPinIntrospection is false, then we use a single implicit PIN_RANGE of (0,
615 * maxBytesToPin); that is, we attempt to pin the first maxBytesToPin bytes of the file.
616 *
617 * After we open a file, we march through the list of pin ranges and attempt to pin
618 * each one, stopping after we've pinned maxBytesToPin bytes. (We may truncate the last
619 * pinned range to fit.) In this way, by choosing to emit certain PIN_RANGE pairs
620 * before others, file generators can express pins in priority order, making most
621 * effective use of the pinned-page quota.
622 *
623 * N.B. Each PIN_RANGE is clamped to the actual bounds of the file; all inputs have a
624 * meaningful interpretation. Also, a range locking a single byte of a page locks the
625 * whole page. Any truncated PIN_RANGE at EOF is ignored. Overlapping pinned entries
626 * are legal, but each pin of a byte counts toward the pin quota regardless of whether
627 * that byte has already been pinned, so the generator of PINLIST_META ought to ensure
628 * that ranges are non-overlapping.
629 *
630 * @param fileToPin Path to file to pin
631 * @param maxBytesToPin Maximum number of bytes to pin
632 * @param attemptPinIntrospection If true, try to open file as a
633 * zip in order to extract the
634 * @return Pinned memory resource owner thing or null on error
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700635 */
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700636 private static PinnedFile pinFile(String fileToPin,
637 int maxBytesToPin,
638 boolean attemptPinIntrospection) {
639 ZipFile fileAsZip = null;
640 InputStream pinRangeStream = null;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700641 try {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700642 if (attemptPinIntrospection) {
643 fileAsZip = maybeOpenZip(fileToPin);
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700644 }
645
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700646 if (fileAsZip != null) {
647 pinRangeStream = maybeOpenPinMetaInZip(fileAsZip, fileToPin);
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700648 }
649
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700650 Slog.d(TAG, "pinRangeStream: " + pinRangeStream);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700651
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700652 PinRangeSource pinRangeSource = (pinRangeStream != null)
653 ? new PinRangeSourceStream(pinRangeStream)
654 : new PinRangeSourceStatic(0, Integer.MAX_VALUE /* will be clipped */);
655 return pinFileRanges(fileToPin, maxBytesToPin, pinRangeSource);
656 } finally {
657 safeClose(pinRangeStream);
658 safeClose(fileAsZip); // Also closes any streams we've opened
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700659 }
660 }
661
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700662 /**
663 * Attempt to open a file as a zip file. On any sort of corruption, log, swallow the
664 * error, and return null.
665 */
666 private static ZipFile maybeOpenZip(String fileName) {
667 ZipFile zip = null;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700668 try {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700669 zip = new ZipFile(fileName);
670 } catch (IOException ex) {
671 Slog.w(TAG,
672 String.format(
673 "could not open \"%s\" as zip: pinning as blob",
674 fileName),
675 ex);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700676 }
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700677 return zip;
678 }
679
680 /**
681 * Open a pin metadata file in the zip if one is present.
682 *
683 * @param zipFile Zip file to search
684 * @return Open input stream or null on any error
685 */
686 private static InputStream maybeOpenPinMetaInZip(ZipFile zipFile, String fileName) {
687 ZipEntry pinMetaEntry = zipFile.getEntry(PIN_META_FILENAME);
688 InputStream pinMetaStream = null;
689 if (pinMetaEntry != null) {
690 try {
691 pinMetaStream = zipFile.getInputStream(pinMetaEntry);
692 } catch (IOException ex) {
693 Slog.w(TAG,
694 String.format("error reading pin metadata \"%s\": pinning as blob",
695 fileName),
696 ex);
697 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700698 }
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700699 return pinMetaStream;
700 }
701
702 private static abstract class PinRangeSource {
703 /** Retrive a range to pin.
704 *
705 * @param outPinRange Receives the pin region
706 * @return True if we filled in outPinRange or false if we're out of pin entries
707 */
708 abstract boolean read(PinRange outPinRange);
709 }
710
711 private static final class PinRangeSourceStatic extends PinRangeSource {
712 private final int mPinStart;
713 private final int mPinLength;
714 private boolean mDone = false;
715
716 PinRangeSourceStatic(int pinStart, int pinLength) {
717 mPinStart = pinStart;
718 mPinLength = pinLength;
719 }
720
721 @Override
722 boolean read(PinRange outPinRange) {
723 outPinRange.start = mPinStart;
724 outPinRange.length = mPinLength;
725 boolean done = mDone;
726 mDone = true;
727 return !done;
728 }
729 }
730
731 private static final class PinRangeSourceStream extends PinRangeSource {
732 private final DataInputStream mStream;
733 private boolean mDone = false;
734
735 PinRangeSourceStream(InputStream stream) {
736 mStream = new DataInputStream(stream);
737 }
738
739 @Override
740 boolean read(PinRange outPinRange) {
741 if (!mDone) {
742 try {
743 outPinRange.start = mStream.readInt();
744 outPinRange.length = mStream.readInt();
745 } catch (IOException ex) {
746 mDone = true;
747 }
748 }
749 return !mDone;
750 }
751 }
752
753 /**
754 * Helper for pinFile.
755 *
756 * @param fileToPin Name of file to pin
757 * @param maxBytesToPin Maximum number of bytes to pin
758 * @param pinRangeSource Read PIN_RANGE entries from this stream to tell us what bytes
759 * to pin.
760 * @return PinnedFile or null on error
761 */
762 private static PinnedFile pinFileRanges(
763 String fileToPin,
764 int maxBytesToPin,
765 PinRangeSource pinRangeSource)
766 {
767 FileDescriptor fd = new FileDescriptor();
768 long address = -1;
769 int mapSize = 0;
770
771 try {
772 int openFlags = (OsConstants.O_RDONLY |
773 OsConstants.O_CLOEXEC |
774 OsConstants.O_NOFOLLOW);
775 fd = Os.open(fileToPin, openFlags, 0);
776 mapSize = (int) Math.min(Os.fstat(fd).st_size, Integer.MAX_VALUE);
777 address = Os.mmap(0, mapSize,
778 OsConstants.PROT_READ,
779 OsConstants.MAP_SHARED,
780 fd, /*offset=*/0);
781
782 PinRange pinRange = new PinRange();
783 int bytesPinned = 0;
784
785 // We pin at page granularity, so make sure the limit is page-aligned
786 if (maxBytesToPin % PAGE_SIZE != 0) {
787 maxBytesToPin -= maxBytesToPin % PAGE_SIZE;
788 }
789
790 while (bytesPinned < maxBytesToPin && pinRangeSource.read(pinRange)) {
791 int pinStart = pinRange.start;
792 int pinLength = pinRange.length;
793 pinStart = clamp(0, pinStart, mapSize);
794 pinLength = clamp(0, pinLength, mapSize - pinStart);
795 pinLength = Math.min(maxBytesToPin - bytesPinned, pinLength);
796
797 // mlock doesn't require the region to be page-aligned, but we snap the
798 // lock region to page boundaries anyway so that we don't under-count
799 // locking a single byte of a page as a charge of one byte even though the
800 // OS will retain the whole page. Thanks to this adjustment, we slightly
801 // over-count the pin charge of back-to-back pins touching the same page,
802 // but better that than undercounting. Besides: nothing stops pin metafile
803 // creators from making the actual regions page-aligned.
804 pinLength += pinStart % PAGE_SIZE;
805 pinStart -= pinStart % PAGE_SIZE;
806 if (pinLength % PAGE_SIZE != 0) {
807 pinLength += PAGE_SIZE - pinLength % PAGE_SIZE;
808 }
809 pinLength = clamp(0, pinLength, maxBytesToPin - bytesPinned);
810
811 if (pinLength > 0) {
812 if (DEBUG) {
813 Slog.d(TAG,
814 String.format(
815 "pinning at %s %s bytes of %s",
816 pinStart, pinLength, fileToPin));
817 }
818 Os.mlock(address + pinStart, pinLength);
819 }
820 bytesPinned += pinLength;
821 }
822
823 PinnedFile pinnedFile = new PinnedFile(address, mapSize, fileToPin, bytesPinned);
824 address = -1; // Ownership transferred
825 return pinnedFile;
826 } catch (ErrnoException ex) {
827 Slog.e(TAG, "Could not pin file " + fileToPin, ex);
828 return null;
829 } finally {
830 safeClose(fd);
831 if (address >= 0) {
832 safeMunmap(address, mapSize);
833 }
834 }
835 }
836
837 private static int clamp(int min, int value, int max) {
838 return Math.max(min, Math.min(value, max));
839 }
840
841 private static void safeMunmap(long address, long mapSize) {
842 try {
843 Os.munmap(address, mapSize);
844 } catch (ErrnoException ex) {
845 Slog.w(TAG, "ignoring error in unmap", ex);
846 }
847 }
848
849 /**
850 * Close FD, swallowing irrelevant errors.
851 */
852 private static void safeClose(@Nullable FileDescriptor fd) {
853 if (fd != null && fd.valid()) {
854 try {
855 Os.close(fd);
856 } catch (ErrnoException ex) {
857 // Swallow the exception: non-EBADF errors in close(2)
858 // indicate deferred paging write errors, which we
859 // don't care about here. The underlying file
860 // descriptor is always closed.
861 if (ex.errno == OsConstants.EBADF) {
862 throw new AssertionError(ex);
863 }
864 }
865 }
866 }
867
868 /**
869 * Close closeable thing, swallowing errors.
870 */
871 private static void safeClose(@Nullable Closeable thing) {
872 if (thing != null) {
873 try {
874 thing.close();
875 } catch (IOException ex) {
876 Slog.w(TAG, "ignoring error closing resource: " + thing, ex);
877 }
878 }
879 }
880
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700881 private final class BinderService extends Binder {
882 @Override
883 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -0600884 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200885 synchronized (PinnerService.this) {
886 long totalSize = 0;
887 for (PinnedFile pinnedFile : mPinnedFiles) {
888 pw.format("%s %s\n", pinnedFile.fileName, pinnedFile.bytesPinned);
889 totalSize += pinnedFile.bytesPinned;
890 }
891 pw.println();
892 for (int key : mPinnedApps.keySet()) {
893 PinnedApp app = mPinnedApps.get(key);
894 pw.print(getNameForKey(key));
895 pw.print(" uid="); pw.print(app.uid);
896 pw.print(" active="); pw.print(app.active);
897 pw.println();
898 for (PinnedFile pf : mPinnedApps.get(key).mFiles) {
899 pw.print(" "); pw.format("%s %s\n", pf.fileName, pf.bytesPinned);
900 totalSize += pf.bytesPinned;
901 }
902 }
903 pw.format("Total size: %s\n", totalSize);
904 pw.println();
905 if (!mPendingRepin.isEmpty()) {
906 pw.print("Pending repin: ");
907 for (int key : mPendingRepin.values()) {
908 pw.print(getNameForKey(key)); pw.print(' ');
909 }
910 pw.println();
911 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700912 }
913 }
914 }
915
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700916 private static final class PinnedFile implements AutoCloseable {
917 private long mAddress;
918 final int mapSize;
919 final String fileName;
920 final int bytesPinned;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700921
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700922 PinnedFile(long address, int mapSize, String fileName, int bytesPinned) {
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700923 mAddress = address;
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700924 this.mapSize = mapSize;
925 this.fileName = fileName;
926 this.bytesPinned = bytesPinned;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700927 }
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700928
929 @Override
930 public void close() {
931 if (mAddress >= 0) {
932 safeMunmap(mAddress, mapSize);
933 mAddress = -1;
934 }
935 }
936
937 @Override
938 public void finalize() {
939 close();
940 }
941 }
942
943 final static class PinRange {
944 int start;
945 int length;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700946 }
Philip Cuadraa95cea02016-07-06 16:00:32 -0700947
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200948 /**
949 * Represents an app that was pinned.
950 */
951 private final class PinnedApp {
952
953 /**
954 * The uid of the package being pinned. This stays constant while the package stays
955 * installed.
956 */
957 final int uid;
958
959 /** Whether it is currently active, i.e. there is a running process from that package. */
960 boolean active;
961
962 /** List of pinned files. */
963 final ArrayList<PinnedFile> mFiles = new ArrayList<>();
964
965 private PinnedApp(ApplicationInfo appInfo) {
966 uid = appInfo.uid;
967 active = mAmInternal.isUidActive(uid);
968 }
969 }
970
Philip Cuadraa95cea02016-07-06 16:00:32 -0700971 final class PinnerHandler extends Handler {
Philip Cuadraa95cea02016-07-06 16:00:32 -0700972 static final int PIN_ONSTART_MSG = 4001;
973
974 public PinnerHandler(Looper looper) {
975 super(looper, null, true);
976 }
977
978 @Override
979 public void handleMessage(Message msg) {
980 switch (msg.what) {
Philip Cuadraa95cea02016-07-06 16:00:32 -0700981 case PIN_ONSTART_MSG:
982 {
983 handlePinOnStart();
984 }
985 break;
986
987 default:
988 super.handleMessage(msg);
989 }
990 }
991 }
992
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700993}