blob: 526aebe2e3e3081541f9a98d358ab0302289847b [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
Wale Ogunwale214f3482018-10-04 11:00:47 -070062import com.android.server.wm.ActivityTaskManagerInternal;
Philip Cuadrad9bd8842016-07-12 17:29:38 -070063import dalvik.system.DexFile;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070064import dalvik.system.VMRuntime;
65
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070066import java.io.FileDescriptor;
Daniel Colascione9779b5e2018-03-21 19:13:57 -070067import java.io.Closeable;
68import java.io.InputStream;
69import java.io.DataInputStream;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070070import java.io.IOException;
71import java.io.PrintWriter;
Jorim Jaggi7119800f2018-07-09 17:57:10 +020072import java.lang.annotation.Retention;
73import java.lang.annotation.RetentionPolicy;
Carmen Jackson879fb682018-07-20 16:43:09 -070074import java.util.List;
Jeff Sharkey847bd852016-08-03 17:20:03 -060075import java.util.ArrayList;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070076
Daniel Colascione9779b5e2018-03-21 19:13:57 -070077import java.util.zip.ZipFile;
Daniel Colascione9779b5e2018-03-21 19:13:57 -070078import java.util.zip.ZipEntry;
79
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070080/**
81 * <p>PinnerService pins important files for key processes in memory.</p>
82 * <p>Files to pin are specified in the config_defaultPinnerServiceFiles
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -070083 * overlay.</p>
84 * <p>Pin the default camera application if specified in config_pinnerCameraApp.</p>
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -070085 */
86public final class PinnerService extends SystemService {
87 private static final boolean DEBUG = false;
88 private static final String TAG = "PinnerService";
Jorim Jaggi7119800f2018-07-09 17:57:10 +020089
Daniel Colascione9779b5e2018-03-21 19:13:57 -070090 private static final String PIN_META_FILENAME = "pinlist.meta";
91 private static final int PAGE_SIZE = (int) Os.sysconf(OsConstants._SC_PAGESIZE);
Jorim Jaggi7119800f2018-07-09 17:57:10 +020092 private static final int MATCH_FLAGS = PackageManager.MATCH_DEFAULT_ONLY
93 | PackageManager.MATCH_DIRECT_BOOT_AWARE
94 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
95
96 private static final int KEY_CAMERA = 0;
97 private static final int KEY_HOME = 1;
98
99 private static final int MAX_CAMERA_PIN_SIZE = 80 * (1 << 20); // 80MB max for camera app.
100 private static final int MAX_HOME_PIN_SIZE = 6 * (1 << 20); // 6MB max for home app.
101
102 @IntDef({KEY_CAMERA, KEY_HOME})
103 @Retention(RetentionPolicy.SOURCE)
104 public @interface AppKey {}
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700105
106 private final Context mContext;
Wale Ogunwale214f3482018-10-04 11:00:47 -0700107 private final ActivityTaskManagerInternal mAtmInternal;
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200108 private final ActivityManagerInternal mAmInternal;
109 private final IActivityManager mAm;
Carmen Jackson0ff99462018-08-09 14:51:39 -0700110 private final UserManager mUserManager;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700111
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200112 /** The list of the statically pinned files. */
113 @GuardedBy("this")
114 private final ArrayList<PinnedFile> mPinnedFiles = new ArrayList<>();
115
116 /** The list of the pinned apps. This is a map from {@link AppKey} to a pinned app. */
117 @GuardedBy("this")
118 private final ArrayMap<Integer, PinnedApp> mPinnedApps = new ArrayMap<>();
119
120 /**
121 * The list of the pinned apps that need to be repinned as soon as the all processes of a given
122 * uid are no longer active. Note that with background dex opt, the new dex/vdex files are only
123 * loaded into the processes once it restarts. So in case background dex opt recompiled these
124 * files, we still need to keep the old ones pinned until the processes restart.
125 * <p>
126 * This is a map from uid to {@link AppKey}
127 */
128 @GuardedBy("this")
129 private final ArrayMap<Integer, Integer> mPendingRepin = new ArrayMap<>();
130
131 /**
132 * A set of {@link AppKey} that are configured to be pinned.
133 */
134 private final ArraySet<Integer> mPinKeys = new ArraySet<>();
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700135
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700136 private BinderService mBinderService;
Philip Cuadraa95cea02016-07-06 16:00:32 -0700137 private PinnerHandler mPinnerHandler = null;
138
Carmen Jacksonf107a232017-05-16 10:37:26 -0700139 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
140 @Override
141 public void onReceive(Context context, Intent intent) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200142 // If an app has updated, update pinned files accordingly.
143 if (Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())) {
Carmen Jacksonf107a232017-05-16 10:37:26 -0700144 Uri packageUri = intent.getData();
Calin Juravle31ce3a82017-05-22 17:49:01 -0700145 String packageName = packageUri.getSchemeSpecificPart();
146 ArraySet<String> updatedPackages = new ArraySet<>();
147 updatedPackages.add(packageName);
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200148 update(updatedPackages, true /* force */);
Carmen Jacksonf107a232017-05-16 10:37:26 -0700149 }
150 }
151 };
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700152
153 public PinnerService(Context context) {
154 super(context);
155
156 mContext = context;
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200157 boolean shouldPinCamera = context.getResources().getBoolean(
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700158 com.android.internal.R.bool.config_pinnerCameraApp);
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200159 boolean shouldPinHome = context.getResources().getBoolean(
160 com.android.internal.R.bool.config_pinnerHomeApp);
161 if (shouldPinCamera) {
162 mPinKeys.add(KEY_CAMERA);
163 }
164 if (shouldPinHome) {
165 mPinKeys.add(KEY_HOME);
166 }
Philip Cuadraa95cea02016-07-06 16:00:32 -0700167 mPinnerHandler = new PinnerHandler(BackgroundThread.get().getLooper());
Carmen Jacksonf107a232017-05-16 10:37:26 -0700168
Wale Ogunwale214f3482018-10-04 11:00:47 -0700169 mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200170 mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
171 mAm = ActivityManager.getService();
172
Carmen Jackson0ff99462018-08-09 14:51:39 -0700173 mUserManager = mContext.getSystemService(UserManager.class);
174
Carmen Jacksonf107a232017-05-16 10:37:26 -0700175 IntentFilter filter = new IntentFilter();
176 filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
177 filter.addDataScheme("package");
178 mContext.registerReceiver(mBroadcastReceiver, filter);
Jorim Jaggi0c849962018-07-15 14:24:38 +0200179
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200180 registerUidListener();
Jorim Jaggi0c849962018-07-15 14:24:38 +0200181 registerUserSetupCompleteListener();
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700182 }
183
184 @Override
185 public void onStart() {
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700186 if (DEBUG) {
187 Slog.i(TAG, "Starting PinnerService");
188 }
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700189 mBinderService = new BinderService();
190 publishBinderService("pinner", mBinderService);
Carmen Jacksonf107a232017-05-16 10:37:26 -0700191 publishLocalService(PinnerService.class, this);
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700192
Jeff Sharkey847bd852016-08-03 17:20:03 -0600193 mPinnerHandler.obtainMessage(PinnerHandler.PIN_ONSTART_MSG).sendToTarget();
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200194 sendPinAppsMessage(UserHandle.USER_SYSTEM);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700195 }
196
197 /**
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200198 * Repin apps on user switch.
199 * <p>
200 * If more than one user is using the device each user may set a different preference for the
201 * individual apps. Make sure that user's preference is pinned into memory.
Philip Cuadraa95cea02016-07-06 16:00:32 -0700202 */
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700203 @Override
204 public void onSwitchUser(int userHandle) {
Carmen Jackson0ff99462018-08-09 14:51:39 -0700205 if (!mUserManager.isManagedProfile(userHandle)) {
206 sendPinAppsMessage(userHandle);
207 }
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200208 }
209
210 @Override
211 public void onUnlockUser(int userHandle) {
Carmen Jackson0ff99462018-08-09 14:51:39 -0700212 if (!mUserManager.isManagedProfile(userHandle)) {
213 sendPinAppsMessage(userHandle);
214 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700215 }
216
Philip Cuadraa95cea02016-07-06 16:00:32 -0700217 /**
Carmen Jacksonf107a232017-05-16 10:37:26 -0700218 * Update the currently pinned files.
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200219 * Specifically, this only updates pinning for the apps that need to be pinned.
Carmen Jacksonf107a232017-05-16 10:37:26 -0700220 * The other files pinned in onStart will not need to be updated.
221 */
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200222 public void update(ArraySet<String> updatedPackages, boolean force) {
223 int currentUser = ActivityManager.getCurrentUser();
224 for (int i = mPinKeys.size() - 1; i >= 0; i--) {
225 int key = mPinKeys.valueAt(i);
226 ApplicationInfo info = getInfoForKey(key, currentUser);
227 if (info != null && updatedPackages.contains(info.packageName)) {
228 Slog.i(TAG, "Updating pinned files for " + info.packageName + " force=" + force);
229 sendPinAppMessage(key, currentUser, force);
230 }
Calin Juravle31ce3a82017-05-22 17:49:01 -0700231 }
Carmen Jacksonf107a232017-05-16 10:37:26 -0700232 }
233
234 /**
Philip Cuadraa95cea02016-07-06 16:00:32 -0700235 * Handler for on start pinning message
236 */
237 private void handlePinOnStart() {
238 // Files to pin come from the overlay and can be specified per-device config
239 String[] filesToPin = mContext.getResources().getStringArray(
240 com.android.internal.R.array.config_defaultPinnerServiceFiles);
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700241 // Continue trying to pin each file even if we fail to pin some of them
242 for (String fileToPin : filesToPin) {
243 PinnedFile pf = pinFile(fileToPin,
244 Integer.MAX_VALUE,
245 /*attemptPinIntrospection=*/false);
246 if (pf == null) {
247 Slog.e(TAG, "Failed to pin file = " + fileToPin);
248 continue;
249 }
250
251 synchronized (this) {
252 mPinnedFiles.add(pf);
Philip Cuadraa95cea02016-07-06 16:00:32 -0700253 }
254 }
255 }
256
Jorim Jaggi0c849962018-07-15 14:24:38 +0200257 /**
258 * Registers a listener to repin the home app when user setup is complete, as the home intent
259 * initially resolves to setup wizard, but once setup is complete, it will resolve to the
260 * regular home app.
261 */
262 private void registerUserSetupCompleteListener() {
263 Uri userSetupCompleteUri = Settings.Secure.getUriFor(
264 Settings.Secure.USER_SETUP_COMPLETE);
265 mContext.getContentResolver().registerContentObserver(userSetupCompleteUri,
266 false, new ContentObserver(null) {
267 @Override
268 public void onChange(boolean selfChange, Uri uri) {
269 if (userSetupCompleteUri.equals(uri)) {
270 sendPinAppMessage(KEY_HOME, ActivityManager.getCurrentUser(),
271 true /* force */);
272 }
273 }
274 }, UserHandle.USER_ALL);
275 }
276
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200277 private void registerUidListener() {
278 try {
279 mAm.registerUidObserver(new IUidObserver.Stub() {
280 @Override
281 public void onUidGone(int uid, boolean disabled) throws RemoteException {
282 mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
283 PinnerService::handleUidGone, PinnerService.this, uid));
284 }
285
286 @Override
287 public void onUidActive(int uid) throws RemoteException {
288 mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
289 PinnerService::handleUidActive, PinnerService.this, uid));
290 }
291
292 @Override
293 public void onUidIdle(int uid, boolean disabled) throws RemoteException {
294 }
295
296 @Override
297 public void onUidStateChanged(int uid, int procState, long procStateSeq)
298 throws RemoteException {
299 }
300
301 @Override
302 public void onUidCachedChanged(int uid, boolean cached) throws RemoteException {
303 }
304 }, UID_OBSERVER_GONE | UID_OBSERVER_ACTIVE, 0, "system");
305 } catch (RemoteException e) {
306 Slog.e(TAG, "Failed to register uid observer", e);
307 }
308 }
309
310 private void handleUidGone(int uid) {
311 updateActiveState(uid, false /* active */);
312 int key;
313 synchronized (this) {
314
315 // In case we have a pending repin, repin now. See mPendingRepin for more information.
316 key = mPendingRepin.getOrDefault(uid, -1);
317 if (key == -1) {
318 return;
319 }
320 mPendingRepin.remove(uid);
321 }
322 pinApp(key, ActivityManager.getCurrentUser(), false /* force */);
323 }
324
325 private void handleUidActive(int uid) {
326 updateActiveState(uid, true /* active */);
327 }
328
329 private void updateActiveState(int uid, boolean active) {
330 synchronized (this) {
331 for (int i = mPinnedApps.size() - 1; i >= 0; i--) {
332 PinnedApp app = mPinnedApps.valueAt(i);
333 if (app.uid == uid) {
334 app.active = active;
335 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700336 }
337 }
338 }
339
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200340 private void unpinApp(@AppKey int key) {
341 ArrayList<PinnedFile> pinnedAppFiles;
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700342 synchronized (this) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200343 PinnedApp app = mPinnedApps.get(key);
344 if (app == null) {
345 return;
346 }
347 mPinnedApps.remove(key);
348 pinnedAppFiles = new ArrayList<>(app.mFiles);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700349 }
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200350 for (PinnedFile pinnedFile : pinnedAppFiles) {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700351 pinnedFile.close();
352 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700353 }
354
355 private boolean isResolverActivity(ActivityInfo info) {
356 return ResolverActivity.class.getName().equals(info.name);
357 }
358
359 private ApplicationInfo getCameraInfo(int userHandle) {
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700360 Intent cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
Carmen Jackson879fb682018-07-20 16:43:09 -0700361 ApplicationInfo info = getApplicationInfoForIntent(cameraIntent, userHandle,
362 false /* defaultToSystemApp */);
363
364 // If the STILL_IMAGE_CAMERA intent doesn't resolve, try the _SECURE intent.
365 // We don't use _SECURE first because it will never get set on a device
366 // without File-based Encryption. But if the user has only set the intent
367 // before unlocking their device, we may still be able to identify their
368 // preference using this intent.
369 if (info == null) {
370 cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE);
371 info = getApplicationInfoForIntent(cameraIntent, userHandle,
372 false /* defaultToSystemApp */);
373 }
374
375 // If the _SECURE intent doesn't resolve, try the original intent but request
376 // the system app for camera if there was more than one result.
377 if (info == null) {
378 cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
379 info = getApplicationInfoForIntent(cameraIntent, userHandle,
380 true /* defaultToSystemApp */);
381 }
382 return info;
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200383 }
384
385 private ApplicationInfo getHomeInfo(int userHandle) {
Wale Ogunwale214f3482018-10-04 11:00:47 -0700386 Intent intent = mAtmInternal.getHomeIntent();
Carmen Jackson879fb682018-07-20 16:43:09 -0700387 return getApplicationInfoForIntent(intent, userHandle, false);
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200388 }
389
Carmen Jackson879fb682018-07-20 16:43:09 -0700390 private ApplicationInfo getApplicationInfoForIntent(Intent intent, int userHandle,
391 boolean defaultToSystemApp) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200392 if (intent == null) {
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700393 return null;
394 }
Carmen Jackson879fb682018-07-20 16:43:09 -0700395
396 ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivityAsUser(intent,
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200397 MATCH_FLAGS, userHandle);
Carmen Jackson879fb682018-07-20 16:43:09 -0700398
399 // If this intent can resolve to only one app, choose that one.
400 // Otherwise, if we've requested to default to the system app, return it;
401 // if we have not requested that default, return null if there's more than one option.
402 // If there's more than one system app, return null since we don't know which to pick.
403 if (resolveInfo == null) {
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700404 return null;
405 }
Carmen Jackson879fb682018-07-20 16:43:09 -0700406
407 if (!isResolverActivity(resolveInfo.activityInfo)) {
408 return resolveInfo.activityInfo.applicationInfo;
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200409 }
Carmen Jackson879fb682018-07-20 16:43:09 -0700410
411 if (defaultToSystemApp) {
412 List<ResolveInfo> infoList = mContext.getPackageManager()
413 .queryIntentActivitiesAsUser(intent, MATCH_FLAGS, userHandle);
414 ApplicationInfo systemAppInfo = null;
415 for (ResolveInfo info : infoList) {
416 if ((info.activityInfo.applicationInfo.flags
417 & ApplicationInfo.FLAG_SYSTEM) != 0) {
418 if (systemAppInfo == null) {
419 systemAppInfo = info.activityInfo.applicationInfo;
420 } else {
421 // If there's more than one system app, return null due to ambiguity.
422 return null;
423 }
424 }
425 }
426 return systemAppInfo;
427 }
428
429 return null;
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200430 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700431
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200432 private void sendPinAppsMessage(int userHandle) {
433 mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinApps, this,
434 userHandle));
435 }
436
437 private void pinApps(int userHandle) {
438 for (int i = mPinKeys.size() - 1; i >= 0; i--) {
439 int key = mPinKeys.valueAt(i);
440 pinApp(key, userHandle, true /* force */);
441 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700442 }
443
Carmen Jacksonf107a232017-05-16 10:37:26 -0700444 /**
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200445 * @see #pinApp(int, int, boolean)
Carmen Jacksonf107a232017-05-16 10:37:26 -0700446 */
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200447 private void sendPinAppMessage(int key, int userHandle, boolean force) {
448 mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinApp, this,
449 key, userHandle, force));
450 }
451
452 /**
453 * Pins an app of a specific type {@code key}.
454 *
455 * @param force If false, this will not repin the app if it's currently active. See
456 * {@link #mPendingRepin}.
457 */
458 private void pinApp(int key, int userHandle, boolean force) {
459 int uid = getUidForKey(key);
460
461 // In case the app is currently active, don't repin until next process restart. See
462 // mPendingRepin for more information.
463 if (!force && uid != -1) {
464 synchronized (this) {
465 mPendingRepin.put(uid, key);
466 }
467 return;
468 }
469 unpinApp(key);
470 ApplicationInfo info = getInfoForKey(key, userHandle);
471 if (info != null) {
472 pinApp(key, info);
473 }
474 }
475
476 /**
477 * Checks whether the pinned package with {@code key} is active or not.
478
479 * @return The uid of the pinned app, or {@code -1} otherwise.
480 */
481 private int getUidForKey(@AppKey int key) {
482 synchronized (this) {
483 PinnedApp existing = mPinnedApps.get(key);
484 return existing != null && existing.active
485 ? existing.uid
486 : -1;
487 }
488 }
489
490 /**
491 * Retrieves the current application info for the given app type.
492 *
493 * @param key The app type to retrieve the info for.
494 * @param userHandle The user id of the current user.
495 */
496 private @Nullable ApplicationInfo getInfoForKey(@AppKey int key, int userHandle) {
497 switch (key) {
498 case KEY_CAMERA:
499 return getCameraInfo(userHandle);
500 case KEY_HOME:
501 return getHomeInfo(userHandle);
502 default:
503 return null;
504 }
505 }
506
507 /**
508 * @return The app type name for {@code key}.
509 */
510 private String getNameForKey(@AppKey int key) {
511 switch (key) {
512 case KEY_CAMERA:
513 return "Camera";
514 case KEY_HOME:
515 return "Home";
516 default:
517 return null;
518 }
519 }
520
521 /**
522 * @return The maximum amount of bytes to be pinned for an app of type {@code key}.
523 */
524 private int getSizeLimitForKey(@AppKey int key) {
525 switch (key) {
526 case KEY_CAMERA:
527 return MAX_CAMERA_PIN_SIZE;
528 case KEY_HOME:
529 return MAX_HOME_PIN_SIZE;
530 default:
531 return 0;
532 }
533 }
534
535 /**
536 * Pins an application.
537 *
538 * @param key The key of the app to pin.
539 * @param appInfo The corresponding app info.
540 */
541 private void pinApp(@AppKey int key, @Nullable ApplicationInfo appInfo) {
542 if (appInfo == null) {
543 return;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700544 }
545
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200546 PinnedApp pinnedApp = new PinnedApp(appInfo);
547 synchronized (this) {
548 mPinnedApps.put(key, pinnedApp);
549 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700550
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200551 // pin APK
552 int pinSizeLimit = getSizeLimitForKey(key);
553 String apk = appInfo.sourceDir;
554 PinnedFile pf = pinFile(apk, pinSizeLimit, /*attemptPinIntrospection=*/true);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700555 if (pf == null) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200556 Slog.e(TAG, "Failed to pin " + apk);
557 return;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700558 }
559 if (DEBUG) {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700560 Slog.i(TAG, "Pinned " + pf.fileName);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700561 }
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700562 synchronized (this) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200563 pinnedApp.mFiles.add(pf);
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700564 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700565
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700566 // determine the ABI from either ApplicationInfo or Build
567 String arch = "arm";
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200568 if (appInfo.primaryCpuAbi != null) {
569 if (VMRuntime.is64BitAbi(appInfo.primaryCpuAbi)) {
John Eckerdal60b07cd2016-11-03 14:04:47 +0100570 arch = arch + "64";
571 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700572 } else {
573 if (VMRuntime.is64BitAbi(Build.SUPPORTED_ABIS[0])) {
574 arch = arch + "64";
575 }
576 }
Philip Cuadrad9bd8842016-07-12 17:29:38 -0700577
578 // get the path to the odex or oat file
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200579 String baseCodePath = appInfo.getBaseCodePath();
Calin Juravle128721a2017-05-15 20:20:50 -0700580 String[] files = null;
Philip Cuadrad9bd8842016-07-12 17:29:38 -0700581 try {
Calin Juravle128721a2017-05-15 20:20:50 -0700582 files = DexFile.getDexFileOutputPaths(baseCodePath, arch);
Philip Cuadrad9bd8842016-07-12 17:29:38 -0700583 } catch (IOException ioe) {}
Calin Juravle128721a2017-05-15 20:20:50 -0700584 if (files == null) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200585 return;
Philip Cuadrad9bd8842016-07-12 17:29:38 -0700586 }
587
588 //not pinning the oat/odex is not a fatal error
Calin Juravle128721a2017-05-15 20:20:50 -0700589 for (String file : files) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200590 pf = pinFile(file, pinSizeLimit, /*attemptPinIntrospection=*/false);
Calin Juravle128721a2017-05-15 20:20:50 -0700591 if (pf != null) {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700592 synchronized (this) {
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200593 pinnedApp.mFiles.add(pf);
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700594 }
Calin Juravle128721a2017-05-15 20:20:50 -0700595 if (DEBUG) {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700596 Slog.i(TAG, "Pinned " + pf.fileName);
Calin Juravle128721a2017-05-15 20:20:50 -0700597 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700598 }
599 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700600 }
601
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700602 /** mlock length bytes of fileToPin in memory
603 *
604 * If attemptPinIntrospection is true, then treat the file to pin as a zip file and
605 * look for a "pinlist.meta" file in the archive root directory. The structure of this
606 * file is a PINLIST_META as described below:
607 *
608 * <pre>
609 * PINLIST_META: PIN_RANGE*
610 * PIN_RANGE: PIN_START PIN_LENGTH
611 * PIN_START: big endian i32: offset in bytes of pin region from file start
612 * PIN_LENGTH: big endian i32: length of pin region in bytes
613 * </pre>
614 *
615 * (We use big endian because that's what DataInputStream is hardcoded to use.)
616 *
617 * If attemptPinIntrospection is false, then we use a single implicit PIN_RANGE of (0,
618 * maxBytesToPin); that is, we attempt to pin the first maxBytesToPin bytes of the file.
619 *
620 * After we open a file, we march through the list of pin ranges and attempt to pin
621 * each one, stopping after we've pinned maxBytesToPin bytes. (We may truncate the last
622 * pinned range to fit.) In this way, by choosing to emit certain PIN_RANGE pairs
623 * before others, file generators can express pins in priority order, making most
624 * effective use of the pinned-page quota.
625 *
626 * N.B. Each PIN_RANGE is clamped to the actual bounds of the file; all inputs have a
627 * meaningful interpretation. Also, a range locking a single byte of a page locks the
628 * whole page. Any truncated PIN_RANGE at EOF is ignored. Overlapping pinned entries
629 * are legal, but each pin of a byte counts toward the pin quota regardless of whether
630 * that byte has already been pinned, so the generator of PINLIST_META ought to ensure
631 * that ranges are non-overlapping.
632 *
633 * @param fileToPin Path to file to pin
634 * @param maxBytesToPin Maximum number of bytes to pin
635 * @param attemptPinIntrospection If true, try to open file as a
636 * zip in order to extract the
637 * @return Pinned memory resource owner thing or null on error
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700638 */
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700639 private static PinnedFile pinFile(String fileToPin,
640 int maxBytesToPin,
641 boolean attemptPinIntrospection) {
642 ZipFile fileAsZip = null;
643 InputStream pinRangeStream = null;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700644 try {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700645 if (attemptPinIntrospection) {
646 fileAsZip = maybeOpenZip(fileToPin);
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700647 }
648
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700649 if (fileAsZip != null) {
650 pinRangeStream = maybeOpenPinMetaInZip(fileAsZip, fileToPin);
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700651 }
652
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700653 Slog.d(TAG, "pinRangeStream: " + pinRangeStream);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700654
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700655 PinRangeSource pinRangeSource = (pinRangeStream != null)
656 ? new PinRangeSourceStream(pinRangeStream)
657 : new PinRangeSourceStatic(0, Integer.MAX_VALUE /* will be clipped */);
658 return pinFileRanges(fileToPin, maxBytesToPin, pinRangeSource);
659 } finally {
660 safeClose(pinRangeStream);
661 safeClose(fileAsZip); // Also closes any streams we've opened
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700662 }
663 }
664
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700665 /**
666 * Attempt to open a file as a zip file. On any sort of corruption, log, swallow the
667 * error, and return null.
668 */
669 private static ZipFile maybeOpenZip(String fileName) {
670 ZipFile zip = null;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700671 try {
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700672 zip = new ZipFile(fileName);
673 } catch (IOException ex) {
674 Slog.w(TAG,
675 String.format(
676 "could not open \"%s\" as zip: pinning as blob",
677 fileName),
678 ex);
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700679 }
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700680 return zip;
681 }
682
683 /**
684 * Open a pin metadata file in the zip if one is present.
685 *
686 * @param zipFile Zip file to search
687 * @return Open input stream or null on any error
688 */
689 private static InputStream maybeOpenPinMetaInZip(ZipFile zipFile, String fileName) {
690 ZipEntry pinMetaEntry = zipFile.getEntry(PIN_META_FILENAME);
691 InputStream pinMetaStream = null;
692 if (pinMetaEntry != null) {
693 try {
694 pinMetaStream = zipFile.getInputStream(pinMetaEntry);
695 } catch (IOException ex) {
696 Slog.w(TAG,
697 String.format("error reading pin metadata \"%s\": pinning as blob",
698 fileName),
699 ex);
700 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700701 }
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700702 return pinMetaStream;
703 }
704
705 private static abstract class PinRangeSource {
706 /** Retrive a range to pin.
707 *
708 * @param outPinRange Receives the pin region
709 * @return True if we filled in outPinRange or false if we're out of pin entries
710 */
711 abstract boolean read(PinRange outPinRange);
712 }
713
714 private static final class PinRangeSourceStatic extends PinRangeSource {
715 private final int mPinStart;
716 private final int mPinLength;
717 private boolean mDone = false;
718
719 PinRangeSourceStatic(int pinStart, int pinLength) {
720 mPinStart = pinStart;
721 mPinLength = pinLength;
722 }
723
724 @Override
725 boolean read(PinRange outPinRange) {
726 outPinRange.start = mPinStart;
727 outPinRange.length = mPinLength;
728 boolean done = mDone;
729 mDone = true;
730 return !done;
731 }
732 }
733
734 private static final class PinRangeSourceStream extends PinRangeSource {
735 private final DataInputStream mStream;
736 private boolean mDone = false;
737
738 PinRangeSourceStream(InputStream stream) {
739 mStream = new DataInputStream(stream);
740 }
741
742 @Override
743 boolean read(PinRange outPinRange) {
744 if (!mDone) {
745 try {
746 outPinRange.start = mStream.readInt();
747 outPinRange.length = mStream.readInt();
748 } catch (IOException ex) {
749 mDone = true;
750 }
751 }
752 return !mDone;
753 }
754 }
755
756 /**
757 * Helper for pinFile.
758 *
759 * @param fileToPin Name of file to pin
760 * @param maxBytesToPin Maximum number of bytes to pin
761 * @param pinRangeSource Read PIN_RANGE entries from this stream to tell us what bytes
762 * to pin.
763 * @return PinnedFile or null on error
764 */
765 private static PinnedFile pinFileRanges(
766 String fileToPin,
767 int maxBytesToPin,
768 PinRangeSource pinRangeSource)
769 {
770 FileDescriptor fd = new FileDescriptor();
771 long address = -1;
772 int mapSize = 0;
773
774 try {
775 int openFlags = (OsConstants.O_RDONLY |
776 OsConstants.O_CLOEXEC |
777 OsConstants.O_NOFOLLOW);
778 fd = Os.open(fileToPin, openFlags, 0);
779 mapSize = (int) Math.min(Os.fstat(fd).st_size, Integer.MAX_VALUE);
780 address = Os.mmap(0, mapSize,
781 OsConstants.PROT_READ,
782 OsConstants.MAP_SHARED,
783 fd, /*offset=*/0);
784
785 PinRange pinRange = new PinRange();
786 int bytesPinned = 0;
787
788 // We pin at page granularity, so make sure the limit is page-aligned
789 if (maxBytesToPin % PAGE_SIZE != 0) {
790 maxBytesToPin -= maxBytesToPin % PAGE_SIZE;
791 }
792
793 while (bytesPinned < maxBytesToPin && pinRangeSource.read(pinRange)) {
794 int pinStart = pinRange.start;
795 int pinLength = pinRange.length;
796 pinStart = clamp(0, pinStart, mapSize);
797 pinLength = clamp(0, pinLength, mapSize - pinStart);
798 pinLength = Math.min(maxBytesToPin - bytesPinned, pinLength);
799
800 // mlock doesn't require the region to be page-aligned, but we snap the
801 // lock region to page boundaries anyway so that we don't under-count
802 // locking a single byte of a page as a charge of one byte even though the
803 // OS will retain the whole page. Thanks to this adjustment, we slightly
804 // over-count the pin charge of back-to-back pins touching the same page,
805 // but better that than undercounting. Besides: nothing stops pin metafile
806 // creators from making the actual regions page-aligned.
807 pinLength += pinStart % PAGE_SIZE;
808 pinStart -= pinStart % PAGE_SIZE;
809 if (pinLength % PAGE_SIZE != 0) {
810 pinLength += PAGE_SIZE - pinLength % PAGE_SIZE;
811 }
812 pinLength = clamp(0, pinLength, maxBytesToPin - bytesPinned);
813
814 if (pinLength > 0) {
815 if (DEBUG) {
816 Slog.d(TAG,
817 String.format(
818 "pinning at %s %s bytes of %s",
819 pinStart, pinLength, fileToPin));
820 }
821 Os.mlock(address + pinStart, pinLength);
822 }
823 bytesPinned += pinLength;
824 }
825
826 PinnedFile pinnedFile = new PinnedFile(address, mapSize, fileToPin, bytesPinned);
827 address = -1; // Ownership transferred
828 return pinnedFile;
829 } catch (ErrnoException ex) {
830 Slog.e(TAG, "Could not pin file " + fileToPin, ex);
831 return null;
832 } finally {
833 safeClose(fd);
834 if (address >= 0) {
835 safeMunmap(address, mapSize);
836 }
837 }
838 }
839
840 private static int clamp(int min, int value, int max) {
841 return Math.max(min, Math.min(value, max));
842 }
843
844 private static void safeMunmap(long address, long mapSize) {
845 try {
846 Os.munmap(address, mapSize);
847 } catch (ErrnoException ex) {
848 Slog.w(TAG, "ignoring error in unmap", ex);
849 }
850 }
851
852 /**
853 * Close FD, swallowing irrelevant errors.
854 */
855 private static void safeClose(@Nullable FileDescriptor fd) {
856 if (fd != null && fd.valid()) {
857 try {
858 Os.close(fd);
859 } catch (ErrnoException ex) {
860 // Swallow the exception: non-EBADF errors in close(2)
861 // indicate deferred paging write errors, which we
862 // don't care about here. The underlying file
863 // descriptor is always closed.
864 if (ex.errno == OsConstants.EBADF) {
865 throw new AssertionError(ex);
866 }
867 }
868 }
869 }
870
871 /**
872 * Close closeable thing, swallowing errors.
873 */
874 private static void safeClose(@Nullable Closeable thing) {
875 if (thing != null) {
876 try {
877 thing.close();
878 } catch (IOException ex) {
879 Slog.w(TAG, "ignoring error closing resource: " + thing, ex);
880 }
881 }
882 }
883
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700884 private final class BinderService extends Binder {
885 @Override
886 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -0600887 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200888 synchronized (PinnerService.this) {
889 long totalSize = 0;
890 for (PinnedFile pinnedFile : mPinnedFiles) {
891 pw.format("%s %s\n", pinnedFile.fileName, pinnedFile.bytesPinned);
892 totalSize += pinnedFile.bytesPinned;
893 }
894 pw.println();
895 for (int key : mPinnedApps.keySet()) {
896 PinnedApp app = mPinnedApps.get(key);
897 pw.print(getNameForKey(key));
898 pw.print(" uid="); pw.print(app.uid);
899 pw.print(" active="); pw.print(app.active);
900 pw.println();
901 for (PinnedFile pf : mPinnedApps.get(key).mFiles) {
902 pw.print(" "); pw.format("%s %s\n", pf.fileName, pf.bytesPinned);
903 totalSize += pf.bytesPinned;
904 }
905 }
906 pw.format("Total size: %s\n", totalSize);
907 pw.println();
908 if (!mPendingRepin.isEmpty()) {
909 pw.print("Pending repin: ");
910 for (int key : mPendingRepin.values()) {
911 pw.print(getNameForKey(key)); pw.print(' ');
912 }
913 pw.println();
914 }
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700915 }
916 }
917 }
918
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700919 private static final class PinnedFile implements AutoCloseable {
920 private long mAddress;
921 final int mapSize;
922 final String fileName;
923 final int bytesPinned;
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700924
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700925 PinnedFile(long address, int mapSize, String fileName, int bytesPinned) {
Philip Cuadra7cb2f8b2016-06-15 16:23:43 -0700926 mAddress = address;
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700927 this.mapSize = mapSize;
928 this.fileName = fileName;
929 this.bytesPinned = bytesPinned;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700930 }
Daniel Colascione9779b5e2018-03-21 19:13:57 -0700931
932 @Override
933 public void close() {
934 if (mAddress >= 0) {
935 safeMunmap(mAddress, mapSize);
936 mAddress = -1;
937 }
938 }
939
940 @Override
941 public void finalize() {
942 close();
943 }
944 }
945
946 final static class PinRange {
947 int start;
948 int length;
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700949 }
Philip Cuadraa95cea02016-07-06 16:00:32 -0700950
Jorim Jaggi7119800f2018-07-09 17:57:10 +0200951 /**
952 * Represents an app that was pinned.
953 */
954 private final class PinnedApp {
955
956 /**
957 * The uid of the package being pinned. This stays constant while the package stays
958 * installed.
959 */
960 final int uid;
961
962 /** Whether it is currently active, i.e. there is a running process from that package. */
963 boolean active;
964
965 /** List of pinned files. */
966 final ArrayList<PinnedFile> mFiles = new ArrayList<>();
967
968 private PinnedApp(ApplicationInfo appInfo) {
969 uid = appInfo.uid;
970 active = mAmInternal.isUidActive(uid);
971 }
972 }
973
Philip Cuadraa95cea02016-07-06 16:00:32 -0700974 final class PinnerHandler extends Handler {
Philip Cuadraa95cea02016-07-06 16:00:32 -0700975 static final int PIN_ONSTART_MSG = 4001;
976
977 public PinnerHandler(Looper looper) {
978 super(looper, null, true);
979 }
980
981 @Override
982 public void handleMessage(Message msg) {
983 switch (msg.what) {
Philip Cuadraa95cea02016-07-06 16:00:32 -0700984 case PIN_ONSTART_MSG:
985 {
986 handlePinOnStart();
987 }
988 break;
989
990 default:
991 super.handleMessage(msg);
992 }
993 }
994 }
995
Philip Cuadra7bd0fdd2016-04-28 15:26:49 -0700996}