1/* 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.retaildemo; 18 19import android.app.AppGlobals; 20import android.app.PackageInstallObserver; 21import android.content.Context; 22import android.content.pm.IPackageManager; 23import android.content.pm.PackageManager; 24import android.os.Bundle; 25import android.os.Environment; 26import android.os.RemoteException; 27import android.os.UserHandle; 28import android.provider.Settings; 29import android.util.ArrayMap; 30import android.util.Log; 31import android.util.Slog; 32 33import com.android.internal.annotations.VisibleForTesting; 34import com.android.internal.util.ArrayUtils; 35 36import java.io.File; 37import java.io.IOException; 38import java.util.Collections; 39import java.util.Map; 40 41/** 42 * Helper class for installing preloaded APKs 43 */ 44class PreloadAppsInstaller { 45 private static final String SYSTEM_SERVER_PACKAGE_NAME = "android"; 46 private static String TAG = PreloadAppsInstaller.class.getSimpleName(); 47 private static final String PRELOAD_APK_EXT = ".apk.preload"; 48 private static boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 49 50 private final IPackageManager mPackageManager; 51 private final File preloadsAppsDirectory; 52 private final Context mContext; 53 54 private final Map<String, String> mApkToPackageMap; 55 56 PreloadAppsInstaller(Context context) { 57 this(context, AppGlobals.getPackageManager(), Environment.getDataPreloadsAppsDirectory()); 58 } 59 60 @VisibleForTesting 61 PreloadAppsInstaller(Context context, IPackageManager packageManager, File preloadsAppsDirectory) { 62 mContext = context; 63 mPackageManager = packageManager; 64 mApkToPackageMap = Collections.synchronizedMap(new ArrayMap<>()); 65 this.preloadsAppsDirectory = preloadsAppsDirectory; 66 } 67 68 void installApps(int userId) { 69 File[] files = preloadsAppsDirectory.listFiles(); 70 AppInstallCounter counter = new AppInstallCounter(mContext, userId); 71 if (ArrayUtils.isEmpty(files)) { 72 counter.setExpectedAppsCount(0); 73 return; 74 } 75 int expectedCount = 0; 76 for (File file : files) { 77 String apkName = file.getName(); 78 if (apkName.endsWith(PRELOAD_APK_EXT) && file.isFile()) { 79 String packageName = mApkToPackageMap.get(apkName); 80 if (packageName != null) { 81 try { 82 expectedCount++; 83 installExistingPackage(packageName, userId, counter); 84 } catch (Exception e) { 85 Slog.e(TAG, "Failed to install existing package " + packageName, e); 86 } 87 } else { 88 try { 89 installPackage(file, userId, counter); 90 expectedCount++; 91 } catch (Exception e) { 92 Slog.e(TAG, "Failed to install package from " + file, e); 93 } 94 } 95 } 96 } 97 counter.setExpectedAppsCount(expectedCount); 98 } 99 100 private void installExistingPackage(String packageName, int userId, 101 AppInstallCounter counter) { 102 if (DEBUG) { 103 Log.d(TAG, "installExistingPackage " + packageName + " u" + userId); 104 } 105 try { 106 mPackageManager.installExistingPackageAsUser(packageName, userId, 107 0 /*installFlags*/, PackageManager.INSTALL_REASON_UNKNOWN); 108 } catch (RemoteException e) { 109 throw e.rethrowFromSystemServer(); 110 } finally { 111 counter.appInstallFinished(); 112 } 113 } 114 115 private void installPackage(File file, final int userId, AppInstallCounter counter) 116 throws IOException, RemoteException { 117 final String apkName = file.getName(); 118 if (DEBUG) { 119 Log.d(TAG, "installPackage " + apkName + " u" + userId); 120 } 121 mPackageManager.installPackageAsUser(file.getPath(), new PackageInstallObserver() { 122 @Override 123 public void onPackageInstalled(String basePackageName, int returnCode, String msg, 124 Bundle extras) { 125 if (DEBUG) { 126 Log.d(TAG, "Package " + basePackageName + " installed u" + userId 127 + " returnCode: " + returnCode + " msg: " + msg); 128 } 129 // Don't notify the counter for now, we'll do it in installExistingPackage 130 if (returnCode == PackageManager.INSTALL_SUCCEEDED) { 131 mApkToPackageMap.put(apkName, basePackageName); 132 // Install on user 0 so that the package is cached when demo user is re-created 133 installExistingPackage(basePackageName, UserHandle.USER_SYSTEM, counter); 134 } else if (returnCode == PackageManager.INSTALL_FAILED_ALREADY_EXISTS) { 135 // This can only happen in first session after a reboot 136 if (!mApkToPackageMap.containsKey(apkName)) { 137 mApkToPackageMap.put(apkName, basePackageName); 138 } 139 installExistingPackage(basePackageName, userId, counter); 140 } else { 141 Log.e(TAG, "Package " + basePackageName + " cannot be installed from " 142 + apkName + ": " + msg + " (returnCode " + returnCode + ")"); 143 counter.appInstallFinished(); 144 } 145 } 146 }.getBinder(), 0, SYSTEM_SERVER_PACKAGE_NAME, userId); 147 } 148 149 private static class AppInstallCounter { 150 private int expectedCount = -1; // -1 means expectedCount not set 151 private int finishedCount; 152 private final Context mContext; 153 private final int userId; 154 155 AppInstallCounter(Context context, int userId) { 156 mContext = context; 157 this.userId = userId; 158 } 159 160 synchronized void appInstallFinished() { 161 this.finishedCount++; 162 checkIfAllFinished(); 163 } 164 165 synchronized void setExpectedAppsCount(int expectedCount) { 166 this.expectedCount = expectedCount; 167 checkIfAllFinished(); 168 } 169 170 private void checkIfAllFinished() { 171 if (expectedCount == finishedCount) { 172 Log.i(TAG, "All preloads finished installing for user " + userId); 173 Settings.Secure.putStringForUser(mContext.getContentResolver(), 174 Settings.Secure.DEMO_USER_SETUP_COMPLETE, "1", userId); 175 } 176 } 177 } 178} 179