/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.server.retaildemo; import android.app.AppGlobals; import android.app.PackageInstallObserver; import android.content.Context; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.os.Bundle; import android.os.Environment; import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; import android.util.ArrayMap; import android.util.Log; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.Map; /** * Helper class for installing preloaded APKs */ class PreloadAppsInstaller { private static final String SYSTEM_SERVER_PACKAGE_NAME = "android"; private static String TAG = PreloadAppsInstaller.class.getSimpleName(); private static final String PRELOAD_APK_EXT = ".apk.preload"; private static boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final IPackageManager mPackageManager; private final File preloadsAppsDirectory; private final Context mContext; private final Map mApkToPackageMap; PreloadAppsInstaller(Context context) { this(context, AppGlobals.getPackageManager(), Environment.getDataPreloadsAppsDirectory()); } @VisibleForTesting PreloadAppsInstaller(Context context, IPackageManager packageManager, File preloadsAppsDirectory) { mContext = context; mPackageManager = packageManager; mApkToPackageMap = Collections.synchronizedMap(new ArrayMap<>()); this.preloadsAppsDirectory = preloadsAppsDirectory; } void installApps(int userId) { File[] files = preloadsAppsDirectory.listFiles(); AppInstallCounter counter = new AppInstallCounter(mContext, userId); if (ArrayUtils.isEmpty(files)) { counter.setExpectedAppsCount(0); return; } int expectedCount = 0; for (File file : files) { String apkName = file.getName(); if (apkName.endsWith(PRELOAD_APK_EXT) && file.isFile()) { String packageName = mApkToPackageMap.get(apkName); if (packageName != null) { try { expectedCount++; installExistingPackage(packageName, userId, counter); } catch (Exception e) { Slog.e(TAG, "Failed to install existing package " + packageName, e); } } else { try { installPackage(file, userId, counter); expectedCount++; } catch (Exception e) { Slog.e(TAG, "Failed to install package from " + file, e); } } } } counter.setExpectedAppsCount(expectedCount); } private void installExistingPackage(String packageName, int userId, AppInstallCounter counter) { if (DEBUG) { Log.d(TAG, "installExistingPackage " + packageName + " u" + userId); } try { mPackageManager.installExistingPackageAsUser(packageName, userId, 0 /*installFlags*/, PackageManager.INSTALL_REASON_UNKNOWN); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } finally { counter.appInstallFinished(); } } private void installPackage(File file, final int userId, AppInstallCounter counter) throws IOException, RemoteException { final String apkName = file.getName(); if (DEBUG) { Log.d(TAG, "installPackage " + apkName + " u" + userId); } mPackageManager.installPackageAsUser(file.getPath(), new PackageInstallObserver() { @Override public void onPackageInstalled(String basePackageName, int returnCode, String msg, Bundle extras) { if (DEBUG) { Log.d(TAG, "Package " + basePackageName + " installed u" + userId + " returnCode: " + returnCode + " msg: " + msg); } // Don't notify the counter for now, we'll do it in installExistingPackage if (returnCode == PackageManager.INSTALL_SUCCEEDED) { mApkToPackageMap.put(apkName, basePackageName); // Install on user 0 so that the package is cached when demo user is re-created installExistingPackage(basePackageName, UserHandle.USER_SYSTEM, counter); } else if (returnCode == PackageManager.INSTALL_FAILED_ALREADY_EXISTS) { // This can only happen in first session after a reboot if (!mApkToPackageMap.containsKey(apkName)) { mApkToPackageMap.put(apkName, basePackageName); } installExistingPackage(basePackageName, userId, counter); } else { Log.e(TAG, "Package " + basePackageName + " cannot be installed from " + apkName + ": " + msg + " (returnCode " + returnCode + ")"); counter.appInstallFinished(); } } }.getBinder(), 0, SYSTEM_SERVER_PACKAGE_NAME, userId); } private static class AppInstallCounter { private int expectedCount = -1; // -1 means expectedCount not set private int finishedCount; private final Context mContext; private final int userId; AppInstallCounter(Context context, int userId) { mContext = context; this.userId = userId; } synchronized void appInstallFinished() { this.finishedCount++; checkIfAllFinished(); } synchronized void setExpectedAppsCount(int expectedCount) { this.expectedCount = expectedCount; checkIfAllFinished(); } private void checkIfAllFinished() { if (expectedCount == finishedCount) { Log.i(TAG, "All preloads finished installing for user " + userId); Settings.Secure.putStringForUser(mContext.getContentResolver(), Settings.Secure.DEMO_USER_SETUP_COMPLETE, "1", userId); } } } }