blob: 8b9af7a435e4ae749f5b7b8049459e3ae1d22e56 [file] [log] [blame]
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001/*
2 * Copyright (C) 2015 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.pm;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.annotation.UserIdInt;
22import android.content.Intent;
23import android.content.pm.InstantAppInfo;
Svet Ganovf36d53c2017-05-24 00:27:21 -070024import android.content.pm.PackageManager;
Svetoslav Ganov096d3042017-01-30 16:34:13 -080025import android.content.pm.PackageParser;
26import android.graphics.Bitmap;
27import android.graphics.BitmapFactory;
28import android.graphics.Canvas;
29import android.graphics.drawable.BitmapDrawable;
30import android.graphics.drawable.Drawable;
31import android.os.Binder;
32import android.os.Environment;
33import android.os.Handler;
34import android.os.Looper;
35import android.os.Message;
Svet Ganovf36d53c2017-05-24 00:27:21 -070036import android.os.UserHandle;
37import android.os.storage.StorageManager;
Svetoslav Ganov096d3042017-01-30 16:34:13 -080038import android.provider.Settings;
39import android.util.ArrayMap;
40import android.util.AtomicFile;
41import android.util.PackageUtils;
42import android.util.Slog;
43import android.util.SparseArray;
44import android.util.SparseBooleanArray;
45import android.util.Xml;
Dan Cashman1dbe6d02018-01-23 11:18:28 -080046
Svetoslav Ganov096d3042017-01-30 16:34:13 -080047import com.android.internal.annotations.GuardedBy;
48import com.android.internal.os.BackgroundThread;
Svet Ganovee2028c2017-02-17 17:23:29 -080049import com.android.internal.os.SomeArgs;
Svetoslav Ganov096d3042017-01-30 16:34:13 -080050import com.android.internal.util.ArrayUtils;
51import com.android.internal.util.XmlUtils;
Todd Kennedy91a39d12017-09-27 12:37:04 -070052
Svetoslav Ganov096d3042017-01-30 16:34:13 -080053import libcore.io.IoUtils;
Neil Fullerc3dfd082019-04-08 22:06:12 +010054import libcore.util.HexEncoding;
Dan Cashman1dbe6d02018-01-23 11:18:28 -080055
Svetoslav Ganov096d3042017-01-30 16:34:13 -080056import org.xmlpull.v1.XmlPullParser;
57import org.xmlpull.v1.XmlPullParserException;
58import org.xmlpull.v1.XmlSerializer;
59
60import java.io.File;
61import java.io.FileInputStream;
62import java.io.FileNotFoundException;
63import java.io.FileOutputStream;
64import java.io.IOException;
65import java.nio.charset.StandardCharsets;
Chad Brubaker0d277a72017-04-12 16:56:53 -070066import java.security.SecureRandom;
Svetoslav Ganov096d3042017-01-30 16:34:13 -080067import java.util.ArrayList;
68import java.util.List;
69import java.util.Set;
70import java.util.function.Predicate;
71
72/**
73 * This class is a part of the package manager service that is responsible
74 * for managing data associated with instant apps such as cached uninstalled
75 * instant apps and instant apps' cookies. In addition it is responsible for
76 * pruning installed instant apps and meta-data for uninstalled instant apps
77 * when free space is needed.
78 */
79class InstantAppRegistry {
80 private static final boolean DEBUG = false;
81
82 private static final String LOG_TAG = "InstantAppRegistry";
83
Svet Ganovf36d53c2017-05-24 00:27:21 -070084 static final long DEFAULT_INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD =
85 DEBUG ? 30 * 1000L /* thirty seconds */ : 7 * 24 * 60 * 60 * 1000L; /* one week */
86
87 private static final long DEFAULT_INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD =
88 DEBUG ? 60 * 1000L /* one min */ : 6 * 30 * 24 * 60 * 60 * 1000L; /* six months */
89
90 static final long DEFAULT_UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD =
91 DEBUG ? 30 * 1000L /* thirty seconds */ : 7 * 24 * 60 * 60 * 1000L; /* one week */
92
93 private static final long DEFAULT_UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD =
Svetoslav Ganov096d3042017-01-30 16:34:13 -080094 DEBUG ? 60 * 1000L /* one min */ : 6 * 30 * 24 * 60 * 60 * 1000L; /* six months */
95
96 private static final String INSTANT_APPS_FOLDER = "instant";
97 private static final String INSTANT_APP_ICON_FILE = "icon.png";
98 private static final String INSTANT_APP_COOKIE_FILE_PREFIX = "cookie_";
99 private static final String INSTANT_APP_COOKIE_FILE_SIFFIX = ".dat";
100 private static final String INSTANT_APP_METADATA_FILE = "metadata.xml";
Chad Brubaker0d277a72017-04-12 16:56:53 -0700101 private static final String INSTANT_APP_ANDROID_ID_FILE = "android_id";
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800102
103 private static final String TAG_PACKAGE = "package";
104 private static final String TAG_PERMISSIONS = "permissions";
105 private static final String TAG_PERMISSION = "permission";
106
107 private static final String ATTR_LABEL = "label";
108 private static final String ATTR_NAME = "name";
109 private static final String ATTR_GRANTED = "granted";
110
111 private final PackageManagerService mService;
112 private final CookiePersistence mCookiePersistence;
113
114 /** State for uninstalled instant apps */
115 @GuardedBy("mService.mPackages")
116 private SparseArray<List<UninstalledInstantAppState>> mUninstalledInstantApps;
117
118 /**
119 * Automatic grants for access to instant app metadata.
120 * The key is the target application UID.
121 * The value is a set of instant app UIDs.
122 * UserID -> TargetAppId -> InstantAppId
123 */
124 @GuardedBy("mService.mPackages")
125 private SparseArray<SparseArray<SparseBooleanArray>> mInstantGrants;
126
127 /** The set of all installed instant apps. UserID -> AppID */
128 @GuardedBy("mService.mPackages")
129 private SparseArray<SparseBooleanArray> mInstalledInstantAppUids;
130
131 public InstantAppRegistry(PackageManagerService service) {
132 mService = service;
133 mCookiePersistence = new CookiePersistence(BackgroundThread.getHandler().getLooper());
134 }
135
Andreas Gampe2e8c7672018-07-20 13:01:08 -0700136 @GuardedBy("mService.mPackages")
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800137 public byte[] getInstantAppCookieLPw(@NonNull String packageName,
Svet Ganovee2028c2017-02-17 17:23:29 -0800138 @UserIdInt int userId) {
Svet Ganov312c6cc2017-02-17 20:48:24 -0800139 // Only installed packages can get their own cookie
140 PackageParser.Package pkg = mService.mPackages.get(packageName);
141 if (pkg == null) {
142 return null;
143 }
144
145 byte[] pendingCookie = mCookiePersistence.getPendingPersistCookieLPr(pkg, userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800146 if (pendingCookie != null) {
147 return pendingCookie;
148 }
149 File cookieFile = peekInstantCookieFile(packageName, userId);
150 if (cookieFile != null && cookieFile.exists()) {
151 try {
152 return IoUtils.readFileAsByteArray(cookieFile.toString());
153 } catch (IOException e) {
154 Slog.w(LOG_TAG, "Error reading cookie file: " + cookieFile);
155 }
156 }
157 return null;
158 }
159
Andreas Gampe2e8c7672018-07-20 13:01:08 -0700160 @GuardedBy("mService.mPackages")
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800161 public boolean setInstantAppCookieLPw(@NonNull String packageName,
Svet Ganovee2028c2017-02-17 17:23:29 -0800162 @Nullable byte[] cookie, @UserIdInt int userId) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800163 if (cookie != null && cookie.length > 0) {
164 final int maxCookieSize = mService.mContext.getPackageManager()
Svetoslav Ganov345ffa52017-04-18 16:08:41 -0700165 .getInstantAppCookieMaxBytes();
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800166 if (cookie.length > maxCookieSize) {
167 Slog.e(LOG_TAG, "Instant app cookie for package " + packageName + " size "
168 + cookie.length + " bytes while max size is " + maxCookieSize);
169 return false;
170 }
171 }
172
Svet Ganov312c6cc2017-02-17 20:48:24 -0800173 // Only an installed package can set its own cookie
Svet Ganovee2028c2017-02-17 17:23:29 -0800174 PackageParser.Package pkg = mService.mPackages.get(packageName);
175 if (pkg == null) {
176 return false;
177 }
178
Svet Ganov312c6cc2017-02-17 20:48:24 -0800179 mCookiePersistence.schedulePersistLPw(userId, pkg, cookie);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800180 return true;
181 }
182
183 private void persistInstantApplicationCookie(@Nullable byte[] cookie,
Svet Ganovee2028c2017-02-17 17:23:29 -0800184 @NonNull String packageName, @NonNull File cookieFile, @UserIdInt int userId) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800185 synchronized (mService.mPackages) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800186 File appDir = getInstantApplicationDir(packageName, userId);
187 if (!appDir.exists() && !appDir.mkdirs()) {
188 Slog.e(LOG_TAG, "Cannot create instant app cookie directory");
189 return;
190 }
191
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800192 if (cookieFile.exists() && !cookieFile.delete()) {
193 Slog.e(LOG_TAG, "Cannot delete instant app cookie file");
194 }
195
196 // No cookie or an empty one means delete - done
197 if (cookie == null || cookie.length <= 0) {
198 return;
199 }
Svet Ganov312c6cc2017-02-17 20:48:24 -0800200 }
201 try (FileOutputStream fos = new FileOutputStream(cookieFile)) {
202 fos.write(cookie, 0, cookie.length);
203 } catch (IOException e) {
204 Slog.e(LOG_TAG, "Error writing instant app cookie file: " + cookieFile, e);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800205 }
206 }
207
208 public Bitmap getInstantAppIconLPw(@NonNull String packageName,
209 @UserIdInt int userId) {
210 File iconFile = new File(getInstantApplicationDir(packageName, userId),
211 INSTANT_APP_ICON_FILE);
212 if (iconFile.exists()) {
213 return BitmapFactory.decodeFile(iconFile.toString());
214 }
215 return null;
216 }
217
Chad Brubaker0d277a72017-04-12 16:56:53 -0700218 public String getInstantAppAndroidIdLPw(@NonNull String packageName,
219 @UserIdInt int userId) {
220 File idFile = new File(getInstantApplicationDir(packageName, userId),
221 INSTANT_APP_ANDROID_ID_FILE);
222 if (idFile.exists()) {
223 try {
224 return IoUtils.readFileAsString(idFile.getAbsolutePath());
225 } catch (IOException e) {
226 Slog.e(LOG_TAG, "Failed to read instant app android id file: " + idFile, e);
227 }
228 }
229 return generateInstantAppAndroidIdLPw(packageName, userId);
230 }
231
232 private String generateInstantAppAndroidIdLPw(@NonNull String packageName,
233 @UserIdInt int userId) {
234 byte[] randomBytes = new byte[8];
235 new SecureRandom().nextBytes(randomBytes);
Neil Fullerc3dfd082019-04-08 22:06:12 +0100236 String id = HexEncoding.encodeToString(randomBytes, false /* upperCase */);
Chad Brubaker64c8b792017-04-28 11:02:26 -0700237 File appDir = getInstantApplicationDir(packageName, userId);
238 if (!appDir.exists() && !appDir.mkdirs()) {
239 Slog.e(LOG_TAG, "Cannot create instant app cookie directory");
240 return id;
241 }
Chad Brubaker0d277a72017-04-12 16:56:53 -0700242 File idFile = new File(getInstantApplicationDir(packageName, userId),
243 INSTANT_APP_ANDROID_ID_FILE);
244 try (FileOutputStream fos = new FileOutputStream(idFile)) {
245 fos.write(id.getBytes());
246 } catch (IOException e) {
247 Slog.e(LOG_TAG, "Error writing instant app android id file: " + idFile, e);
248 }
249 return id;
250
251 }
252
Andreas Gampe2e8c7672018-07-20 13:01:08 -0700253 @GuardedBy("mService.mPackages")
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800254 public @Nullable List<InstantAppInfo> getInstantAppsLPr(@UserIdInt int userId) {
255 List<InstantAppInfo> installedApps = getInstalledInstantApplicationsLPr(userId);
256 List<InstantAppInfo> uninstalledApps = getUninstalledInstantApplicationsLPr(userId);
257 if (installedApps != null) {
258 if (uninstalledApps != null) {
259 installedApps.addAll(uninstalledApps);
260 }
261 return installedApps;
262 }
263 return uninstalledApps;
264 }
265
Andreas Gampe2e8c7672018-07-20 13:01:08 -0700266 @GuardedBy("mService.mPackages")
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800267 public void onPackageInstalledLPw(@NonNull PackageParser.Package pkg, @NonNull int[] userIds) {
268 PackageSetting ps = (PackageSetting) pkg.mExtras;
269 if (ps == null) {
270 return;
271 }
272
273 for (int userId : userIds) {
274 // Ignore not installed apps
275 if (mService.mPackages.get(pkg.packageName) == null || !ps.getInstalled(userId)) {
276 continue;
277 }
278
279 // Propagate permissions before removing any state
Todd Kennedy1a3216b2018-02-08 14:52:57 -0800280 propagateInstantAppPermissionsIfNeeded(pkg, userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800281
282 // Track instant apps
Todd Kennedybe0b8892017-02-15 14:13:52 -0800283 if (ps.getInstantApp(userId)) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800284 addInstantAppLPw(userId, ps.appId);
285 }
286
287 // Remove the in-memory state
288 removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) ->
289 state.mInstantAppInfo.getPackageName().equals(pkg.packageName),
290 userId);
291
292 // Remove the on-disk state except the cookie
293 File instantAppDir = getInstantApplicationDir(pkg.packageName, userId);
294 new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete();
295 new File(instantAppDir, INSTANT_APP_ICON_FILE).delete();
296
297 // If app signature changed - wipe the cookie
298 File currentCookieFile = peekInstantCookieFile(pkg.packageName, userId);
299 if (currentCookieFile == null) {
300 continue;
301 }
Svetoslav Ganovcdd685c2017-08-22 14:35:04 -0700302
Dan Cashman1dbe6d02018-01-23 11:18:28 -0800303 String cookieName = currentCookieFile.getName();
304 String currentCookieSha256 =
305 cookieName.substring(INSTANT_APP_COOKIE_FILE_PREFIX.length(),
306 cookieName.length() - INSTANT_APP_COOKIE_FILE_SIFFIX.length());
307
Svetoslav Ganovcdd685c2017-08-22 14:35:04 -0700308 // Before we used only the first signature to compute the SHA 256 but some
309 // apps could be singed by multiple certs and the cert order is undefined.
310 // We prefer the modern computation procedure where all certs are taken
311 // into account but also allow the value from the old computation to avoid
312 // data loss.
Dan Cashman1dbe6d02018-01-23 11:18:28 -0800313 if (pkg.mSigningDetails.checkCapability(currentCookieSha256,
314 PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA)) {
Svetoslav Ganovcdd685c2017-08-22 14:35:04 -0700315 return;
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800316 }
Svetoslav Ganovcdd685c2017-08-22 14:35:04 -0700317
Dan Cashman928b7032018-04-16 14:18:48 -0700318 // For backwards compatibility we accept match based on any signature, since we may have
319 // recorded only the first for multiply-signed packages
Dan Cashman1dbe6d02018-01-23 11:18:28 -0800320 final String[] signaturesSha256Digests =
321 PackageUtils.computeSignaturesSha256Digests(pkg.mSigningDetails.signatures);
Dan Cashman928b7032018-04-16 14:18:48 -0700322 for (String s : signaturesSha256Digests) {
323 if (s.equals(currentCookieSha256)) {
324 return;
325 }
Svetoslav Ganovcdd685c2017-08-22 14:35:04 -0700326 }
327
328 // Sorry, you are out of luck - different signatures - nuke data
329 Slog.i(LOG_TAG, "Signature for package " + pkg.packageName
330 + " changed - dropping cookie");
331 // Make sure a pending write for the old signed app is cancelled
332 mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
333 currentCookieFile.delete();
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800334 }
335 }
336
Andreas Gampe2e8c7672018-07-20 13:01:08 -0700337 @GuardedBy("mService.mPackages")
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800338 public void onPackageUninstalledLPw(@NonNull PackageParser.Package pkg,
339 @NonNull int[] userIds) {
340 PackageSetting ps = (PackageSetting) pkg.mExtras;
341 if (ps == null) {
342 return;
343 }
344
345 for (int userId : userIds) {
346 if (mService.mPackages.get(pkg.packageName) != null && ps.getInstalled(userId)) {
347 continue;
348 }
349
Todd Kennedybe0b8892017-02-15 14:13:52 -0800350 if (ps.getInstantApp(userId)) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800351 // Add a record for an uninstalled instant app
352 addUninstalledInstantAppLPw(pkg, userId);
353 removeInstantAppLPw(userId, ps.appId);
354 } else {
355 // Deleting an app prunes all instant state such as cookie
356 deleteDir(getInstantApplicationDir(pkg.packageName, userId));
Svetoslav Ganov8aa379e2017-04-04 18:12:27 -0700357 mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800358 removeAppLPw(userId, ps.appId);
359 }
360 }
361 }
362
Andreas Gampe2e8c7672018-07-20 13:01:08 -0700363 @GuardedBy("mService.mPackages")
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800364 public void onUserRemovedLPw(int userId) {
365 if (mUninstalledInstantApps != null) {
366 mUninstalledInstantApps.remove(userId);
367 if (mUninstalledInstantApps.size() <= 0) {
368 mUninstalledInstantApps = null;
369 }
370 }
371 if (mInstalledInstantAppUids != null) {
372 mInstalledInstantAppUids.remove(userId);
373 if (mInstalledInstantAppUids.size() <= 0) {
374 mInstalledInstantAppUids = null;
375 }
376 }
377 if (mInstantGrants != null) {
378 mInstantGrants.remove(userId);
379 if (mInstantGrants.size() <= 0) {
380 mInstantGrants = null;
381 }
382 }
383 deleteDir(getInstantApplicationsDir(userId));
384 }
385
386 public boolean isInstantAccessGranted(@UserIdInt int userId, int targetAppId,
387 int instantAppId) {
388 if (mInstantGrants == null) {
389 return false;
390 }
391 final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
392 if (targetAppList == null) {
393 return false;
394 }
395 final SparseBooleanArray instantGrantList = targetAppList.get(targetAppId);
396 if (instantGrantList == null) {
397 return false;
398 }
399 return instantGrantList.get(instantAppId);
400 }
401
Andreas Gampe2e8c7672018-07-20 13:01:08 -0700402 @GuardedBy("mService.mPackages")
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800403 public void grantInstantAccessLPw(@UserIdInt int userId, @Nullable Intent intent,
404 int targetAppId, int instantAppId) {
405 if (mInstalledInstantAppUids == null) {
406 return; // no instant apps installed; no need to grant
407 }
408 SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
409 if (instantAppList == null || !instantAppList.get(instantAppId)) {
410 return; // instant app id isn't installed; no need to grant
411 }
412 if (instantAppList.get(targetAppId)) {
413 return; // target app id is an instant app; no need to grant
414 }
415 if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
416 final Set<String> categories = intent.getCategories();
417 if (categories != null && categories.contains(Intent.CATEGORY_BROWSABLE)) {
418 return; // launched via VIEW/BROWSABLE intent; no need to grant
419 }
420 }
421 if (mInstantGrants == null) {
422 mInstantGrants = new SparseArray<>();
423 }
424 SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
425 if (targetAppList == null) {
426 targetAppList = new SparseArray<>();
427 mInstantGrants.put(userId, targetAppList);
428 }
429 SparseBooleanArray instantGrantList = targetAppList.get(targetAppId);
430 if (instantGrantList == null) {
431 instantGrantList = new SparseBooleanArray();
432 targetAppList.put(targetAppId, instantGrantList);
433 }
434 instantGrantList.put(instantAppId, true /*granted*/);
435 }
436
Andreas Gampe2e8c7672018-07-20 13:01:08 -0700437 @GuardedBy("mService.mPackages")
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800438 public void addInstantAppLPw(@UserIdInt int userId, int instantAppId) {
439 if (mInstalledInstantAppUids == null) {
440 mInstalledInstantAppUids = new SparseArray<>();
441 }
442 SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
443 if (instantAppList == null) {
444 instantAppList = new SparseBooleanArray();
445 mInstalledInstantAppUids.put(userId, instantAppList);
446 }
447 instantAppList.put(instantAppId, true /*installed*/);
448 }
449
Andreas Gampe2e8c7672018-07-20 13:01:08 -0700450 @GuardedBy("mService.mPackages")
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800451 private void removeInstantAppLPw(@UserIdInt int userId, int instantAppId) {
452 // remove from the installed list
453 if (mInstalledInstantAppUids == null) {
454 return; // no instant apps on the system
455 }
456 final SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
457 if (instantAppList == null) {
458 return;
459 }
460
461 instantAppList.delete(instantAppId);
462
463 // remove any grants
464 if (mInstantGrants == null) {
465 return; // no grants on the system
466 }
467 final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
468 if (targetAppList == null) {
469 return; // no grants for this user
470 }
471 for (int i = targetAppList.size() - 1; i >= 0; --i) {
472 targetAppList.valueAt(i).delete(instantAppId);
473 }
474 }
475
Andreas Gampe2e8c7672018-07-20 13:01:08 -0700476 @GuardedBy("mService.mPackages")
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800477 private void removeAppLPw(@UserIdInt int userId, int targetAppId) {
478 // remove from the installed list
479 if (mInstantGrants == null) {
480 return; // no grants on the system
481 }
482 final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
483 if (targetAppList == null) {
484 return; // no grants for this user
485 }
486 targetAppList.delete(targetAppId);
487 }
488
Andreas Gampe2e8c7672018-07-20 13:01:08 -0700489 @GuardedBy("mService.mPackages")
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800490 private void addUninstalledInstantAppLPw(@NonNull PackageParser.Package pkg,
491 @UserIdInt int userId) {
492 InstantAppInfo uninstalledApp = createInstantAppInfoForPackage(
493 pkg, userId, false);
494 if (uninstalledApp == null) {
495 return;
496 }
497 if (mUninstalledInstantApps == null) {
498 mUninstalledInstantApps = new SparseArray<>();
499 }
500 List<UninstalledInstantAppState> uninstalledAppStates =
501 mUninstalledInstantApps.get(userId);
502 if (uninstalledAppStates == null) {
503 uninstalledAppStates = new ArrayList<>();
504 mUninstalledInstantApps.put(userId, uninstalledAppStates);
505 }
506 UninstalledInstantAppState uninstalledAppState = new UninstalledInstantAppState(
507 uninstalledApp, System.currentTimeMillis());
508 uninstalledAppStates.add(uninstalledAppState);
509
510 writeUninstalledInstantAppMetadata(uninstalledApp, userId);
511 writeInstantApplicationIconLPw(pkg, userId);
512 }
513
514 private void writeInstantApplicationIconLPw(@NonNull PackageParser.Package pkg,
515 @UserIdInt int userId) {
516 File appDir = getInstantApplicationDir(pkg.packageName, userId);
517 if (!appDir.exists()) {
518 return;
519 }
520
521 Drawable icon = pkg.applicationInfo.loadIcon(mService.mContext.getPackageManager());
522
523 final Bitmap bitmap;
524 if (icon instanceof BitmapDrawable) {
525 bitmap = ((BitmapDrawable) icon).getBitmap();
526 } else {
527 bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(),
528 icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
529 Canvas canvas = new Canvas(bitmap);
Todd Kennedy03f336b2017-02-28 15:11:52 -0800530 icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800531 icon.draw(canvas);
532 }
533
534 File iconFile = new File(getInstantApplicationDir(pkg.packageName, userId),
535 INSTANT_APP_ICON_FILE);
536
537 try (FileOutputStream out = new FileOutputStream(iconFile)) {
538 bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
539 } catch (Exception e) {
540 Slog.e(LOG_TAG, "Error writing instant app icon", e);
541 }
542 }
543
Andreas Gampe2e8c7672018-07-20 13:01:08 -0700544 @GuardedBy("mService.mPackages")
Svet Ganovf935a702017-08-22 12:15:58 -0700545 boolean hasInstantApplicationMetadataLPr(String packageName, int userId) {
546 return hasUninstalledInstantAppStateLPr(packageName, userId)
547 || hasInstantAppMetadataLPr(packageName, userId);
548 }
549
Andreas Gampe2e8c7672018-07-20 13:01:08 -0700550 @GuardedBy("mService.mPackages")
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800551 public void deleteInstantApplicationMetadataLPw(@NonNull String packageName,
552 @UserIdInt int userId) {
553 removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) ->
554 state.mInstantAppInfo.getPackageName().equals(packageName),
555 userId);
556
557 File instantAppDir = getInstantApplicationDir(packageName, userId);
558 new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete();
559 new File(instantAppDir, INSTANT_APP_ICON_FILE).delete();
Chad Brubaker0d277a72017-04-12 16:56:53 -0700560 new File(instantAppDir, INSTANT_APP_ANDROID_ID_FILE).delete();
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800561 File cookie = peekInstantCookieFile(packageName, userId);
562 if (cookie != null) {
563 cookie.delete();
564 }
565 }
566
Andreas Gampe2e8c7672018-07-20 13:01:08 -0700567 @GuardedBy("mService.mPackages")
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800568 private void removeUninstalledInstantAppStateLPw(
569 @NonNull Predicate<UninstalledInstantAppState> criteria, @UserIdInt int userId) {
570 if (mUninstalledInstantApps == null) {
571 return;
572 }
573 List<UninstalledInstantAppState> uninstalledAppStates =
574 mUninstalledInstantApps.get(userId);
575 if (uninstalledAppStates == null) {
576 return;
577 }
578 final int appCount = uninstalledAppStates.size();
Todd Kennedy7a12f9f2017-01-31 12:49:28 -0800579 for (int i = appCount - 1; i >= 0; --i) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800580 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
581 if (!criteria.test(uninstalledAppState)) {
582 continue;
583 }
584 uninstalledAppStates.remove(i);
585 if (uninstalledAppStates.isEmpty()) {
586 mUninstalledInstantApps.remove(userId);
587 if (mUninstalledInstantApps.size() <= 0) {
588 mUninstalledInstantApps = null;
589 }
590 return;
591 }
592 }
593 }
594
Andreas Gampe2e8c7672018-07-20 13:01:08 -0700595 @GuardedBy("mService.mPackages")
Svet Ganovf935a702017-08-22 12:15:58 -0700596 private boolean hasUninstalledInstantAppStateLPr(String packageName, @UserIdInt int userId) {
597 if (mUninstalledInstantApps == null) {
598 return false;
599 }
600 final List<UninstalledInstantAppState> uninstalledAppStates =
601 mUninstalledInstantApps.get(userId);
602 if (uninstalledAppStates == null) {
603 return false;
604 }
605 final int appCount = uninstalledAppStates.size();
606 for (int i = 0; i < appCount; i++) {
607 final UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
608 if (packageName.equals(uninstalledAppState.mInstantAppInfo.getPackageName())) {
609 return true;
610 }
611 }
612 return false;
613 }
614
615 private boolean hasInstantAppMetadataLPr(String packageName, @UserIdInt int userId) {
616 final File instantAppDir = getInstantApplicationDir(packageName, userId);
617 return new File(instantAppDir, INSTANT_APP_METADATA_FILE).exists()
618 || new File(instantAppDir, INSTANT_APP_ICON_FILE).exists()
619 || new File(instantAppDir, INSTANT_APP_ANDROID_ID_FILE).exists()
620 || peekInstantCookieFile(packageName, userId) != null;
621 }
622
Svet Ganovf36d53c2017-05-24 00:27:21 -0700623 void pruneInstantApps() {
624 final long maxInstalledCacheDuration = Settings.Global.getLong(
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800625 mService.mContext.getContentResolver(),
Svet Ganovf36d53c2017-05-24 00:27:21 -0700626 Settings.Global.INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
627 DEFAULT_INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800628
Svet Ganovf36d53c2017-05-24 00:27:21 -0700629 final long maxUninstalledCacheDuration = Settings.Global.getLong(
630 mService.mContext.getContentResolver(),
631 Settings.Global.UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
632 DEFAULT_UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800633
Svet Ganovf36d53c2017-05-24 00:27:21 -0700634 try {
635 pruneInstantApps(Long.MAX_VALUE,
636 maxInstalledCacheDuration, maxUninstalledCacheDuration);
637 } catch (IOException e) {
638 Slog.e(LOG_TAG, "Error pruning installed and uninstalled instant apps", e);
639 }
640 }
641
642 boolean pruneInstalledInstantApps(long neededSpace, long maxInstalledCacheDuration) {
643 try {
644 return pruneInstantApps(neededSpace, maxInstalledCacheDuration, Long.MAX_VALUE);
645 } catch (IOException e) {
646 Slog.e(LOG_TAG, "Error pruning installed instant apps", e);
647 return false;
648 }
649 }
650
651 boolean pruneUninstalledInstantApps(long neededSpace, long maxUninstalledCacheDuration) {
652 try {
653 return pruneInstantApps(neededSpace, Long.MAX_VALUE, maxUninstalledCacheDuration);
654 } catch (IOException e) {
655 Slog.e(LOG_TAG, "Error pruning uninstalled instant apps", e);
656 return false;
657 }
658 }
659
660 /**
661 * Prunes instant apps until there is enough <code>neededSpace</code>. Both
662 * installed and uninstalled instant apps are pruned that are older than
663 * <code>maxInstalledCacheDuration</code> and <code>maxUninstalledCacheDuration</code>
664 * respectively. All times are in milliseconds.
665 *
666 * @param neededSpace The space to ensure is free.
667 * @param maxInstalledCacheDuration The max duration for caching installed apps in millis.
668 * @param maxUninstalledCacheDuration The max duration for caching uninstalled apps in millis.
669 * @return Whether enough space was freed.
670 *
671 * @throws IOException
672 */
673 private boolean pruneInstantApps(long neededSpace, long maxInstalledCacheDuration,
674 long maxUninstalledCacheDuration) throws IOException {
675 final StorageManager storage = mService.mContext.getSystemService(StorageManager.class);
676 final File file = storage.findPathForUuid(StorageManager.UUID_PRIVATE_INTERNAL);
677
678 if (file.getUsableSpace() >= neededSpace) {
679 return true;
680 }
681
682 List<String> packagesToDelete = null;
683
684 final int[] allUsers;
685 final long now = System.currentTimeMillis();
686
687 // Prune first installed instant apps
688 synchronized (mService.mPackages) {
689 allUsers = PackageManagerService.sUserManager.getUserIds();
690
691 final int packageCount = mService.mPackages.size();
692 for (int i = 0; i < packageCount; i++) {
693 final PackageParser.Package pkg = mService.mPackages.valueAt(i);
694 if (now - pkg.getLatestPackageUseTimeInMills() < maxInstalledCacheDuration) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800695 continue;
696 }
Svet Ganovf36d53c2017-05-24 00:27:21 -0700697 if (!(pkg.mExtras instanceof PackageSetting)) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800698 continue;
699 }
Svet Ganovf36d53c2017-05-24 00:27:21 -0700700 final PackageSetting ps = (PackageSetting) pkg.mExtras;
701 boolean installedOnlyAsInstantApp = false;
702 for (int userId : allUsers) {
703 if (ps.getInstalled(userId)) {
704 if (ps.getInstantApp(userId)) {
705 installedOnlyAsInstantApp = true;
706 } else {
707 installedOnlyAsInstantApp = false;
708 break;
709 }
710 }
711 }
712 if (installedOnlyAsInstantApp) {
713 if (packagesToDelete == null) {
714 packagesToDelete = new ArrayList<>();
715 }
716 packagesToDelete.add(pkg.packageName);
717 }
718 }
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800719
Svet Ganovf36d53c2017-05-24 00:27:21 -0700720 if (packagesToDelete != null) {
721 packagesToDelete.sort((String lhs, String rhs) -> {
722 final PackageParser.Package lhsPkg = mService.mPackages.get(lhs);
723 final PackageParser.Package rhsPkg = mService.mPackages.get(rhs);
724 if (lhsPkg == null && rhsPkg == null) {
725 return 0;
726 } else if (lhsPkg == null) {
727 return -1;
728 } else if (rhsPkg == null) {
729 return 1;
730 } else {
731 if (lhsPkg.getLatestPackageUseTimeInMills() >
732 rhsPkg.getLatestPackageUseTimeInMills()) {
733 return 1;
734 } else if (lhsPkg.getLatestPackageUseTimeInMills() <
735 rhsPkg.getLatestPackageUseTimeInMills()) {
736 return -1;
737 } else {
738 if (lhsPkg.mExtras instanceof PackageSetting
739 && rhsPkg.mExtras instanceof PackageSetting) {
740 final PackageSetting lhsPs = (PackageSetting) lhsPkg.mExtras;
741 final PackageSetting rhsPs = (PackageSetting) rhsPkg.mExtras;
742 if (lhsPs.firstInstallTime > rhsPs.firstInstallTime) {
743 return 1;
744 } else {
745 return -1;
746 }
747 } else {
748 return 0;
749 }
750 }
751 }
752 });
753 }
754 }
755
756 if (packagesToDelete != null) {
757 final int packageCount = packagesToDelete.size();
758 for (int i = 0; i < packageCount; i++) {
759 final String packageToDelete = packagesToDelete.get(i);
760 if (mService.deletePackageX(packageToDelete, PackageManager.VERSION_CODE_HIGHEST,
761 UserHandle.USER_SYSTEM, PackageManager.DELETE_ALL_USERS)
762 == PackageManager.DELETE_SUCCEEDED) {
763 if (file.getUsableSpace() >= neededSpace) {
764 return true;
765 }
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800766 }
767 }
768 }
Svet Ganovf36d53c2017-05-24 00:27:21 -0700769
770 // Prune uninstalled instant apps
771 synchronized (mService.mPackages) {
772 // TODO: Track last used time for uninstalled instant apps for better pruning
773 for (int userId : UserManagerService.getInstance().getUserIds()) {
774 // Prune in-memory state
775 removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) -> {
776 final long elapsedCachingMillis = System.currentTimeMillis() - state.mTimestamp;
777 return (elapsedCachingMillis > maxUninstalledCacheDuration);
778 }, userId);
779
780 // Prune on-disk state
781 File instantAppsDir = getInstantApplicationsDir(userId);
782 if (!instantAppsDir.exists()) {
783 continue;
784 }
785 File[] files = instantAppsDir.listFiles();
786 if (files == null) {
787 continue;
788 }
789 for (File instantDir : files) {
790 if (!instantDir.isDirectory()) {
791 continue;
792 }
793
794 File metadataFile = new File(instantDir, INSTANT_APP_METADATA_FILE);
795 if (!metadataFile.exists()) {
796 continue;
797 }
798
799 final long elapsedCachingMillis = System.currentTimeMillis()
800 - metadataFile.lastModified();
801 if (elapsedCachingMillis > maxUninstalledCacheDuration) {
802 deleteDir(instantDir);
803 if (file.getUsableSpace() >= neededSpace) {
804 return true;
805 }
806 }
807 }
808 }
809 }
810
811 return false;
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800812 }
813
Andreas Gampe2e8c7672018-07-20 13:01:08 -0700814 @GuardedBy("mService.mPackages")
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800815 private @Nullable List<InstantAppInfo> getInstalledInstantApplicationsLPr(
816 @UserIdInt int userId) {
817 List<InstantAppInfo> result = null;
818
819 final int packageCount = mService.mPackages.size();
820 for (int i = 0; i < packageCount; i++) {
Todd Kennedybe0b8892017-02-15 14:13:52 -0800821 final PackageParser.Package pkg = mService.mPackages.valueAt(i);
822 final PackageSetting ps = (PackageSetting) pkg.mExtras;
823 if (ps == null || !ps.getInstantApp(userId)) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800824 continue;
825 }
Todd Kennedybe0b8892017-02-15 14:13:52 -0800826 final InstantAppInfo info = createInstantAppInfoForPackage(
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800827 pkg, userId, true);
828 if (info == null) {
829 continue;
830 }
831 if (result == null) {
832 result = new ArrayList<>();
833 }
834 result.add(info);
835 }
836
837 return result;
838 }
839
840 private @NonNull
841 InstantAppInfo createInstantAppInfoForPackage(
842 @NonNull PackageParser.Package pkg, @UserIdInt int userId,
843 boolean addApplicationInfo) {
844 PackageSetting ps = (PackageSetting) pkg.mExtras;
845 if (ps == null) {
846 return null;
847 }
848 if (!ps.getInstalled(userId)) {
849 return null;
850 }
851
852 String[] requestedPermissions = new String[pkg.requestedPermissions.size()];
853 pkg.requestedPermissions.toArray(requestedPermissions);
854
855 Set<String> permissions = ps.getPermissionsState().getPermissions(userId);
856 String[] grantedPermissions = new String[permissions.size()];
857 permissions.toArray(grantedPermissions);
858
859 if (addApplicationInfo) {
860 return new InstantAppInfo(pkg.applicationInfo,
861 requestedPermissions, grantedPermissions);
862 } else {
863 return new InstantAppInfo(pkg.applicationInfo.packageName,
864 pkg.applicationInfo.loadLabel(mService.mContext.getPackageManager()),
865 requestedPermissions, grantedPermissions);
866 }
867 }
868
Andreas Gampe2e8c7672018-07-20 13:01:08 -0700869 @GuardedBy("mService.mPackages")
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800870 private @Nullable List<InstantAppInfo> getUninstalledInstantApplicationsLPr(
871 @UserIdInt int userId) {
872 List<UninstalledInstantAppState> uninstalledAppStates =
873 getUninstalledInstantAppStatesLPr(userId);
874 if (uninstalledAppStates == null || uninstalledAppStates.isEmpty()) {
875 return null;
876 }
877
878 List<InstantAppInfo> uninstalledApps = null;
879 final int stateCount = uninstalledAppStates.size();
880 for (int i = 0; i < stateCount; i++) {
881 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
882 if (uninstalledApps == null) {
883 uninstalledApps = new ArrayList<>();
884 }
885 uninstalledApps.add(uninstalledAppState.mInstantAppInfo);
886 }
887 return uninstalledApps;
888 }
889
Todd Kennedy1a3216b2018-02-08 14:52:57 -0800890 private void propagateInstantAppPermissionsIfNeeded(@NonNull PackageParser.Package pkg,
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800891 @UserIdInt int userId) {
892 InstantAppInfo appInfo = peekOrParseUninstalledInstantAppInfo(
Todd Kennedy1a3216b2018-02-08 14:52:57 -0800893 pkg.packageName, userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800894 if (appInfo == null) {
895 return;
896 }
897 if (ArrayUtils.isEmpty(appInfo.getGrantedPermissions())) {
898 return;
899 }
900 final long identity = Binder.clearCallingIdentity();
901 try {
902 for (String grantedPermission : appInfo.getGrantedPermissions()) {
Todd Kennedy0eb97382017-10-03 16:57:22 -0700903 final boolean propagatePermission =
904 mService.mSettings.canPropagatePermissionToInstantApp(grantedPermission);
Todd Kennedy1a3216b2018-02-08 14:52:57 -0800905 if (propagatePermission && pkg.requestedPermissions.contains(grantedPermission)) {
906 mService.grantRuntimePermission(pkg.packageName, grantedPermission, userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800907 }
908 }
909 } finally {
910 Binder.restoreCallingIdentity(identity);
911 }
912 }
913
914 private @NonNull
915 InstantAppInfo peekOrParseUninstalledInstantAppInfo(
916 @NonNull String packageName, @UserIdInt int userId) {
917 if (mUninstalledInstantApps != null) {
918 List<UninstalledInstantAppState> uninstalledAppStates =
919 mUninstalledInstantApps.get(userId);
920 if (uninstalledAppStates != null) {
921 final int appCount = uninstalledAppStates.size();
922 for (int i = 0; i < appCount; i++) {
923 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
924 if (uninstalledAppState.mInstantAppInfo
925 .getPackageName().equals(packageName)) {
926 return uninstalledAppState.mInstantAppInfo;
927 }
928 }
929 }
930 }
931
932 File metadataFile = new File(getInstantApplicationDir(packageName, userId),
933 INSTANT_APP_METADATA_FILE);
934 UninstalledInstantAppState uninstalledAppState = parseMetadataFile(metadataFile);
935 if (uninstalledAppState == null) {
936 return null;
937 }
938
939 return uninstalledAppState.mInstantAppInfo;
940 }
941
Andreas Gampe2e8c7672018-07-20 13:01:08 -0700942 @GuardedBy("mService.mPackages")
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800943 private @Nullable List<UninstalledInstantAppState> getUninstalledInstantAppStatesLPr(
944 @UserIdInt int userId) {
945 List<UninstalledInstantAppState> uninstalledAppStates = null;
946 if (mUninstalledInstantApps != null) {
947 uninstalledAppStates = mUninstalledInstantApps.get(userId);
948 if (uninstalledAppStates != null) {
949 return uninstalledAppStates;
950 }
951 }
952
953 File instantAppsDir = getInstantApplicationsDir(userId);
954 if (instantAppsDir.exists()) {
955 File[] files = instantAppsDir.listFiles();
956 if (files != null) {
957 for (File instantDir : files) {
958 if (!instantDir.isDirectory()) {
959 continue;
960 }
961 File metadataFile = new File(instantDir,
962 INSTANT_APP_METADATA_FILE);
963 UninstalledInstantAppState uninstalledAppState =
964 parseMetadataFile(metadataFile);
965 if (uninstalledAppState == null) {
966 continue;
967 }
968 if (uninstalledAppStates == null) {
969 uninstalledAppStates = new ArrayList<>();
970 }
971 uninstalledAppStates.add(uninstalledAppState);
972 }
973 }
974 }
975
976 if (uninstalledAppStates != null) {
977 if (mUninstalledInstantApps == null) {
978 mUninstalledInstantApps = new SparseArray<>();
979 }
980 mUninstalledInstantApps.put(userId, uninstalledAppStates);
981 }
982
983 return uninstalledAppStates;
984 }
985
986 private static @Nullable UninstalledInstantAppState parseMetadataFile(
987 @NonNull File metadataFile) {
988 if (!metadataFile.exists()) {
989 return null;
990 }
991 FileInputStream in;
992 try {
993 in = new AtomicFile(metadataFile).openRead();
994 } catch (FileNotFoundException fnfe) {
995 Slog.i(LOG_TAG, "No instant metadata file");
996 return null;
997 }
998
999 final File instantDir = metadataFile.getParentFile();
1000 final long timestamp = metadataFile.lastModified();
1001 final String packageName = instantDir.getName();
1002
1003 try {
1004 XmlPullParser parser = Xml.newPullParser();
1005 parser.setInput(in, StandardCharsets.UTF_8.name());
1006 return new UninstalledInstantAppState(
1007 parseMetadata(parser, packageName), timestamp);
1008 } catch (XmlPullParserException | IOException e) {
1009 throw new IllegalStateException("Failed parsing instant"
1010 + " metadata file: " + metadataFile, e);
1011 } finally {
1012 IoUtils.closeQuietly(in);
1013 }
1014 }
1015
Svetoslav Ganovcdd685c2017-08-22 14:35:04 -07001016 private static @NonNull File computeInstantCookieFile(@NonNull String packageName,
1017 @NonNull String sha256Digest, @UserIdInt int userId) {
1018 final File appDir = getInstantApplicationDir(packageName, userId);
1019 final String cookieFile = INSTANT_APP_COOKIE_FILE_PREFIX
1020 + sha256Digest + INSTANT_APP_COOKIE_FILE_SIFFIX;
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001021 return new File(appDir, cookieFile);
1022 }
1023
1024 private static @Nullable File peekInstantCookieFile(@NonNull String packageName,
1025 @UserIdInt int userId) {
1026 File appDir = getInstantApplicationDir(packageName, userId);
1027 if (!appDir.exists()) {
1028 return null;
1029 }
1030 File[] files = appDir.listFiles();
1031 if (files == null) {
1032 return null;
1033 }
1034 for (File file : files) {
1035 if (!file.isDirectory()
1036 && file.getName().startsWith(INSTANT_APP_COOKIE_FILE_PREFIX)
1037 && file.getName().endsWith(INSTANT_APP_COOKIE_FILE_SIFFIX)) {
1038 return file;
1039 }
1040 }
1041 return null;
1042 }
1043
1044 private static @Nullable
1045 InstantAppInfo parseMetadata(@NonNull XmlPullParser parser,
1046 @NonNull String packageName)
1047 throws IOException, XmlPullParserException {
1048 final int outerDepth = parser.getDepth();
1049 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
1050 if (TAG_PACKAGE.equals(parser.getName())) {
1051 return parsePackage(parser, packageName);
1052 }
1053 }
1054 return null;
1055 }
1056
1057 private static InstantAppInfo parsePackage(@NonNull XmlPullParser parser,
1058 @NonNull String packageName)
1059 throws IOException, XmlPullParserException {
1060 String label = parser.getAttributeValue(null, ATTR_LABEL);
1061
1062 List<String> outRequestedPermissions = new ArrayList<>();
1063 List<String> outGrantedPermissions = new ArrayList<>();
1064
1065 final int outerDepth = parser.getDepth();
1066 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
1067 if (TAG_PERMISSIONS.equals(parser.getName())) {
1068 parsePermissions(parser, outRequestedPermissions, outGrantedPermissions);
1069 }
1070 }
1071
1072 String[] requestedPermissions = new String[outRequestedPermissions.size()];
1073 outRequestedPermissions.toArray(requestedPermissions);
1074
1075 String[] grantedPermissions = new String[outGrantedPermissions.size()];
1076 outGrantedPermissions.toArray(grantedPermissions);
1077
1078 return new InstantAppInfo(packageName, label,
1079 requestedPermissions, grantedPermissions);
1080 }
1081
1082 private static void parsePermissions(@NonNull XmlPullParser parser,
1083 @NonNull List<String> outRequestedPermissions,
1084 @NonNull List<String> outGrantedPermissions)
1085 throws IOException, XmlPullParserException {
1086 final int outerDepth = parser.getDepth();
1087 while (XmlUtils.nextElementWithin(parser,outerDepth)) {
1088 if (TAG_PERMISSION.equals(parser.getName())) {
1089 String permission = XmlUtils.readStringAttribute(parser, ATTR_NAME);
1090 outRequestedPermissions.add(permission);
1091 if (XmlUtils.readBooleanAttribute(parser, ATTR_GRANTED)) {
1092 outGrantedPermissions.add(permission);
1093 }
1094 }
1095 }
1096 }
1097
1098 private void writeUninstalledInstantAppMetadata(
1099 @NonNull InstantAppInfo instantApp, @UserIdInt int userId) {
1100 File appDir = getInstantApplicationDir(instantApp.getPackageName(), userId);
1101 if (!appDir.exists() && !appDir.mkdirs()) {
1102 return;
1103 }
1104
1105 File metadataFile = new File(appDir, INSTANT_APP_METADATA_FILE);
1106
1107 AtomicFile destination = new AtomicFile(metadataFile);
1108 FileOutputStream out = null;
1109 try {
1110 out = destination.startWrite();
1111
1112 XmlSerializer serializer = Xml.newSerializer();
1113 serializer.setOutput(out, StandardCharsets.UTF_8.name());
1114 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
1115
1116 serializer.startDocument(null, true);
1117
1118 serializer.startTag(null, TAG_PACKAGE);
1119 serializer.attribute(null, ATTR_LABEL, instantApp.loadLabel(
1120 mService.mContext.getPackageManager()).toString());
1121
1122 serializer.startTag(null, TAG_PERMISSIONS);
1123 for (String permission : instantApp.getRequestedPermissions()) {
1124 serializer.startTag(null, TAG_PERMISSION);
1125 serializer.attribute(null, ATTR_NAME, permission);
1126 if (ArrayUtils.contains(instantApp.getGrantedPermissions(), permission)) {
1127 serializer.attribute(null, ATTR_GRANTED, String.valueOf(true));
1128 }
1129 serializer.endTag(null, TAG_PERMISSION);
1130 }
1131 serializer.endTag(null, TAG_PERMISSIONS);
1132
1133 serializer.endTag(null, TAG_PACKAGE);
1134
1135 serializer.endDocument();
1136 destination.finishWrite(out);
1137 } catch (Throwable t) {
1138 Slog.wtf(LOG_TAG, "Failed to write instant state, restoring backup", t);
1139 destination.failWrite(out);
1140 } finally {
1141 IoUtils.closeQuietly(out);
1142 }
1143 }
1144
1145 private static @NonNull File getInstantApplicationsDir(int userId) {
1146 return new File(Environment.getUserSystemDirectory(userId),
1147 INSTANT_APPS_FOLDER);
1148 }
1149
1150 private static @NonNull File getInstantApplicationDir(String packageName, int userId) {
Svet Ganovf935a702017-08-22 12:15:58 -07001151 return new File(getInstantApplicationsDir(userId), packageName);
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001152 }
1153
1154 private static void deleteDir(@NonNull File dir) {
1155 File[] files = dir.listFiles();
1156 if (files != null) {
1157 for (File file : files) {
1158 deleteDir(file);
1159 }
1160 }
1161 dir.delete();
1162 }
1163
1164 private static final class UninstalledInstantAppState {
1165 final InstantAppInfo mInstantAppInfo;
1166 final long mTimestamp;
1167
1168 public UninstalledInstantAppState(InstantAppInfo instantApp,
1169 long timestamp) {
1170 mInstantAppInfo = instantApp;
1171 mTimestamp = timestamp;
1172 }
1173 }
1174
1175 private final class CookiePersistence extends Handler {
1176 private static final long PERSIST_COOKIE_DELAY_MILLIS = 1000L; /* one second */
1177
Nandana Dutt0f07c5e2018-07-24 14:18:09 +01001178 // The cookies are cached per package name per user-id in this sparse
1179 // array. The caching is so that pending persistence can be canceled within
1180 // a short interval. To ensure we still return pending persist cookies
1181 // for a package that uninstalled and reinstalled while the persistence
1182 // was still pending, we use the package name as a key for
1183 // mPendingPersistCookies, since that stays stable across reinstalls.
1184 private final SparseArray<ArrayMap<String, SomeArgs>> mPendingPersistCookies
Svet Ganov312c6cc2017-02-17 20:48:24 -08001185 = new SparseArray<>();
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001186
1187 public CookiePersistence(Looper looper) {
1188 super(looper);
1189 }
1190
Svet Ganov312c6cc2017-02-17 20:48:24 -08001191 public void schedulePersistLPw(@UserIdInt int userId, @NonNull PackageParser.Package pkg,
1192 @NonNull byte[] cookie) {
Svetoslav Ganovcdd685c2017-08-22 14:35:04 -07001193 // Before we used only the first signature to compute the SHA 256 but some
1194 // apps could be singed by multiple certs and the cert order is undefined.
1195 // We prefer the modern computation procedure where all certs are taken
1196 // into account and delete the file derived via the legacy hash computation.
1197 File newCookieFile = computeInstantCookieFile(pkg.packageName,
Patrick Baumann420d58a2017-12-19 10:17:21 -08001198 PackageUtils.computeSignaturesSha256Digest(pkg.mSigningDetails.signatures), userId);
1199 if (!pkg.mSigningDetails.hasSignatures()) {
1200 Slog.wtf(LOG_TAG, "Parsed Instant App contains no valid signatures!");
1201 }
1202 File oldCookieFile = peekInstantCookieFile(pkg.packageName, userId);
1203 if (oldCookieFile != null && !newCookieFile.equals(oldCookieFile)) {
1204 oldCookieFile.delete();
Svetoslav Ganovcdd685c2017-08-22 14:35:04 -07001205 }
Svet Ganov312c6cc2017-02-17 20:48:24 -08001206 cancelPendingPersistLPw(pkg, userId);
Svetoslav Ganovcdd685c2017-08-22 14:35:04 -07001207 addPendingPersistCookieLPw(userId, pkg, cookie, newCookieFile);
Svet Ganov312c6cc2017-02-17 20:48:24 -08001208 sendMessageDelayed(obtainMessage(userId, pkg),
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001209 PERSIST_COOKIE_DELAY_MILLIS);
1210 }
1211
Svet Ganov312c6cc2017-02-17 20:48:24 -08001212 public @Nullable byte[] getPendingPersistCookieLPr(@NonNull PackageParser.Package pkg,
1213 @UserIdInt int userId) {
Nandana Dutt0f07c5e2018-07-24 14:18:09 +01001214 ArrayMap<String, SomeArgs> pendingWorkForUser =
Svet Ganov312c6cc2017-02-17 20:48:24 -08001215 mPendingPersistCookies.get(userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001216 if (pendingWorkForUser != null) {
Nandana Dutt0f07c5e2018-07-24 14:18:09 +01001217 SomeArgs state = pendingWorkForUser.get(pkg.packageName);
Svet Ganov312c6cc2017-02-17 20:48:24 -08001218 if (state != null) {
1219 return (byte[]) state.arg1;
1220 }
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001221 }
1222 return null;
1223 }
1224
Svet Ganov312c6cc2017-02-17 20:48:24 -08001225 public void cancelPendingPersistLPw(@NonNull PackageParser.Package pkg,
1226 @UserIdInt int userId) {
1227 removeMessages(userId, pkg);
1228 SomeArgs state = removePendingPersistCookieLPr(pkg, userId);
1229 if (state != null) {
1230 state.recycle();
1231 }
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001232 }
1233
Svet Ganov312c6cc2017-02-17 20:48:24 -08001234 private void addPendingPersistCookieLPw(@UserIdInt int userId,
1235 @NonNull PackageParser.Package pkg, @NonNull byte[] cookie,
1236 @NonNull File cookieFile) {
Nandana Dutt0f07c5e2018-07-24 14:18:09 +01001237 ArrayMap<String, SomeArgs> pendingWorkForUser =
Svet Ganov312c6cc2017-02-17 20:48:24 -08001238 mPendingPersistCookies.get(userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001239 if (pendingWorkForUser == null) {
1240 pendingWorkForUser = new ArrayMap<>();
1241 mPendingPersistCookies.put(userId, pendingWorkForUser);
1242 }
Svet Ganov312c6cc2017-02-17 20:48:24 -08001243 SomeArgs args = SomeArgs.obtain();
1244 args.arg1 = cookie;
1245 args.arg2 = cookieFile;
Nandana Dutt0f07c5e2018-07-24 14:18:09 +01001246 pendingWorkForUser.put(pkg.packageName, args);
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001247 }
1248
Svet Ganov312c6cc2017-02-17 20:48:24 -08001249 private SomeArgs removePendingPersistCookieLPr(@NonNull PackageParser.Package pkg,
1250 @UserIdInt int userId) {
Nandana Dutt0f07c5e2018-07-24 14:18:09 +01001251 ArrayMap<String, SomeArgs> pendingWorkForUser =
Svet Ganov312c6cc2017-02-17 20:48:24 -08001252 mPendingPersistCookies.get(userId);
1253 SomeArgs state = null;
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001254 if (pendingWorkForUser != null) {
Nandana Dutt0f07c5e2018-07-24 14:18:09 +01001255 state = pendingWorkForUser.remove(pkg.packageName);
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001256 if (pendingWorkForUser.isEmpty()) {
1257 mPendingPersistCookies.remove(userId);
1258 }
1259 }
Svet Ganov312c6cc2017-02-17 20:48:24 -08001260 return state;
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001261 }
1262
1263 @Override
1264 public void handleMessage(Message message) {
1265 int userId = message.what;
Svet Ganov312c6cc2017-02-17 20:48:24 -08001266 PackageParser.Package pkg = (PackageParser.Package) message.obj;
1267 SomeArgs state = removePendingPersistCookieLPr(pkg, userId);
1268 if (state == null) {
1269 return;
1270 }
1271 byte[] cookie = (byte[]) state.arg1;
1272 File cookieFile = (File) state.arg2;
1273 state.recycle();
1274 persistInstantApplicationCookie(cookie, pkg.packageName, cookieFile, userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001275 }
1276 }
1277}