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