1 /*
2  * Copyright 2014, 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 package com.android.managedprovisioning.task;
17 
18 import static android.app.PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT;
19 import static android.app.PendingIntent.FLAG_MUTABLE;
20 import static android.app.PendingIntent.FLAG_ONE_SHOT;
21 import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
22 import static android.content.pm.PackageManager.INSTALL_REPLACE_EXISTING;
23 
24 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_INSTALL_PACKAGE_TASK_MS;
25 
26 import static java.util.Objects.requireNonNull;
27 
28 import android.annotation.NonNull;
29 import android.app.PendingIntent;
30 import android.app.admin.DevicePolicyManager;
31 import android.content.BroadcastReceiver;
32 import android.content.Context;
33 import android.content.Intent;
34 import android.content.IntentFilter;
35 import android.content.pm.PackageInstaller;
36 import android.content.pm.PackageManager;
37 import androidx.core.os.BuildCompat;
38 
39 import com.android.internal.annotations.VisibleForTesting;
40 import com.android.managedprovisioning.analytics.MetricsWriterFactory;
41 import com.android.managedprovisioning.analytics.ProvisioningAnalyticsTracker;
42 import com.android.managedprovisioning.common.ManagedProvisioningSharedPreferences;
43 import com.android.managedprovisioning.common.ProvisionLogger;
44 import com.android.managedprovisioning.common.SettingsFacade;
45 import com.android.managedprovisioning.common.Utils;
46 import com.android.managedprovisioning.model.ProvisioningParams;
47 
48 import java.io.File;
49 import java.io.FileInputStream;
50 import java.io.IOException;
51 import java.io.InputStream;
52 import java.io.OutputStream;
53 import java.util.HashSet;
54 import java.util.Set;
55 
56 
57 /**
58  * Installs the management app apk from a download location provided by
59  * {@link PackageLocationProvider#getPackageLocation()}.
60  */
61 public class InstallPackageTask extends AbstractProvisioningTask {
62     private static final String ACTION_INSTALL_DONE = InstallPackageTask.class.getName() + ".DONE.";
63 
64     public static final int ERROR_PACKAGE_INVALID = 0;
65     public static final int ERROR_INSTALLATION_FAILED = 1;
66 
67     private final PackageLocationProvider mPackageLocationProvider;
68 
69     private final PackageManager mPm;
70     private final DevicePolicyManager mDpm;
71     private final PackageInstaller.SessionCallback mSessionCallback =  new SessionCallback();
72     private final String mPackageName;
73     private final Utils mUtils;
74     private int mSessionId = -1;
75 
76     private static final int SUCCESS_INSTALLED_BROADCAST = 1;
77     private static final int SUCCESS_INSTALLED_CALLBACK = 2;
78     private final Set<Integer> mSuccessCodes = new HashSet<>();
79 
80     /**
81      * Create an InstallPackageTask. When run, this will attempt to install the device admin package
82      * if it is non-null.
83      *
84      * {@see #run(String, String)} for more detail on package installation.
85      */
InstallPackageTask( PackageLocationProvider packageLocationProvider, Context context, ProvisioningParams params, Callback callback, String packageName)86     public InstallPackageTask(
87             PackageLocationProvider packageLocationProvider,
88             Context context,
89             ProvisioningParams params,
90             Callback callback,
91             String packageName) {
92         this(packageLocationProvider, context, params, callback,
93                 new ProvisioningAnalyticsTracker(
94                         MetricsWriterFactory.getMetricsWriter(context, new SettingsFacade()),
95                         new ManagedProvisioningSharedPreferences(context)),
96                 new Utils(),
97                 packageName);
98     }
99 
100     @VisibleForTesting
InstallPackageTask( PackageLocationProvider packageLocationProvider, Context context, ProvisioningParams params, Callback callback, ProvisioningAnalyticsTracker provisioningAnalyticsTracker, Utils utils, String packageName)101     InstallPackageTask(
102             PackageLocationProvider packageLocationProvider,
103             Context context,
104             ProvisioningParams params,
105             Callback callback,
106             ProvisioningAnalyticsTracker provisioningAnalyticsTracker,
107             Utils utils,
108             String packageName) {
109         super(context, params, callback, provisioningAnalyticsTracker);
110 
111         mPm = context.getPackageManager();
112         mDpm = context.getSystemService(DevicePolicyManager.class);
113         mPackageLocationProvider = requireNonNull(packageLocationProvider);
114         mPackageName = requireNonNull(packageName);
115         mUtils = requireNonNull(utils);
116     }
117 
copyStream(@onNull InputStream in, @NonNull OutputStream out)118     private static void copyStream(@NonNull InputStream in, @NonNull OutputStream out)
119             throws IOException {
120         byte[] buffer = new byte[16 * 1024];
121         int numRead;
122         while ((numRead = in.read(buffer)) != -1) {
123             out.write(buffer, 0, numRead);
124         }
125     }
126 
127     /**
128      * Installs a package. The package will be installed from the given location if one is provided.
129      * If a null or empty location is provided, and the package is installed for a different user,
130      * it will be enabled for the calling user. If the package location is not provided and the
131      * package is not installed for any other users, this task will produce an error.
132      *
133      * Errors will be indicated if a downloaded package is invalid, or installation fails.
134      */
135     @Override
run(int userId)136     public void run(int userId) {
137         startTaskTimer();
138 
139         File packageLocation = mPackageLocationProvider.getPackageLocation();
140         ProvisionLogger.logi("Installing package " + mPackageName + " on user " + userId + " from "
141                 + packageLocation);
142         if (packageLocation == null) {
143             success();
144             return;
145         }
146 
147         int installFlags = INSTALL_REPLACE_EXISTING;
148         // Current device owner (if exists) must be test-only, so it is fine to replace it with a
149         // test-only package of same package name. No need to further verify signature as
150         // installation will fail if signatures don't match.
151         if (mDpm.isDeviceOwnerApp(mPackageName)) {
152             installFlags |= PackageManager.INSTALL_ALLOW_TEST;
153         }
154 
155         PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
156                 PackageInstaller.SessionParams.MODE_FULL_INSTALL);
157         params.installFlags |= installFlags;
158 
159         try {
160             installPackage(packageLocation, mPackageName, params, mContext, mSessionCallback);
161         } catch (IOException e) {
162             ProvisionLogger.loge("Installing package " + mPackageName + " failed.", e);
163             error(ERROR_INSTALLATION_FAILED);
164         } finally {
165             packageLocation.delete();
166         }
167     }
168     /*
169     The reason why we have both SessionCallback and BroadcastReceiver is as follows:
170     Initially we were just listening for the ACTION_INSTALL_DONE broadcast
171     but this was being received too early causing the bug(b/185897624).
172     In (ag/14971042) we reworked the logic to register for session callback,
173     it still ran into the race condition(b/165012101) causing the bug(b/15160090)
174     But it was still fine to keep that logic since it's the recommended approach.
175     In current state(ag/15160090), we've now added Intent#ACTION_PACKAGE_ADDED receiver
176     as that's the latest possible callback.
177     */
installPackage( File source, String packageName, PackageInstaller.SessionParams params, Context context, PackageInstaller.SessionCallback sessionCallback)178     private void installPackage(
179             File source,
180             String packageName,
181             PackageInstaller.SessionParams params,
182             Context context,
183             PackageInstaller.SessionCallback sessionCallback)
184             throws IOException {
185         PackageInstaller pi = context.getPackageManager().getPackageInstaller();
186         context.registerReceiver(
187                 new PackageAddedReceiver(packageName),
188                 createPackageAddedIntentFilter(), Context.RECEIVER_EXPORTED/*UNAUDITED*/);
189         pi.registerSessionCallback(sessionCallback);
190         mSessionId  = pi.createSession(params);
191         try (PackageInstaller.Session session = pi.openSession(mSessionId)) {
192             try (FileInputStream in = new FileInputStream(source);
193                  OutputStream out = session.openWrite(source.getName(), 0, -1)) {
194                 copyStream(in, out);
195             } catch (IOException e) {
196                 session.abandon();
197                 throw e;
198             }
199 
200             String action = ACTION_INSTALL_DONE + mSessionId;
201             int pendingIntentFlags = FLAG_ONE_SHOT | FLAG_UPDATE_CURRENT | FLAG_MUTABLE;
202             if (BuildCompat.isAtLeastU()) {
203                 pendingIntentFlags |= PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT;
204             }
205             PendingIntent pendingIntent =
206                 PendingIntent.getBroadcast(context, mSessionId, new Intent(action), pendingIntentFlags);
207 
208             session.commit(pendingIntent.getIntentSender());
209         }
210     }
211 
createPackageAddedIntentFilter()212     private IntentFilter createPackageAddedIntentFilter() {
213         IntentFilter intentFilter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
214         intentFilter.addDataScheme("package");
215         return intentFilter;
216     }
217 
218     @Override
getMetricsCategory()219     protected int getMetricsCategory() {
220         return PROVISIONING_INSTALL_PACKAGE_TASK_MS;
221     }
222 
addSuccessStatus(int successStatus)223     private void addSuccessStatus(int successStatus) {
224         mSuccessCodes.add(successStatus);
225         if (mSuccessCodes.contains(SUCCESS_INSTALLED_BROADCAST)
226                 && mSuccessCodes.contains(SUCCESS_INSTALLED_CALLBACK)) {
227             ProvisionLogger.logd("Package " + mPackageName + " is successfully installed.");
228             stopTaskTimer();
229             success();
230         }
231     }
232 
233     private class PackageAddedReceiver extends BroadcastReceiver {
234 
235         private final String mPackageName;
236 
PackageAddedReceiver(String packageName)237         PackageAddedReceiver(String packageName) {
238             mPackageName = requireNonNull(packageName);
239         }
240 
241         @Override
onReceive(Context context, Intent intent)242         public void onReceive(Context context, Intent intent) {
243             ProvisionLogger.logd("PACKAGE_ADDED broadcast received with intent data "
244                     + intent.getDataString());
245             if (!mPackageName.equals(extractPackageNameFromDataString(intent.getDataString()))) {
246                 ProvisionLogger.logd("The package name provided in the intent data does not equal "
247                         + mPackageName);
248                 return;
249             }
250             addSuccessStatus(SUCCESS_INSTALLED_BROADCAST);
251             context.unregisterReceiver(this);
252         }
253 
extractPackageNameFromDataString(String dataString)254         private String extractPackageNameFromDataString(String dataString) {
255             return dataString.substring("package:".length());
256         }
257     }
258 
259     private class SessionCallback extends PackageInstaller.SessionCallback {
260 
261         @Override
onCreated(int sessionId)262         public void onCreated(int sessionId) {}
263 
264         @Override
onBadgingChanged(int sessionId)265         public void onBadgingChanged(int sessionId) {}
266 
267         @Override
onActiveChanged(int sessionId, boolean active)268         public void onActiveChanged(int sessionId, boolean active) {}
269 
270         @Override
onProgressChanged(int sessionId, float progress)271         public void onProgressChanged(int sessionId, float progress) {}
272 
273         @Override
onFinished(int sessionId, boolean success)274         public void onFinished(int sessionId, boolean success) {
275             if (sessionId != mSessionId) {
276                 return;
277             }
278             PackageInstaller packageInstaller = mPm.getPackageInstaller();
279             packageInstaller.unregisterSessionCallback(mSessionCallback);
280             if (!success) {
281                 boolean packageInstalled =
282                         mUtils.isPackageInstalled(mPackageName, mContext.getPackageManager());
283                 if (packageInstalled) {
284                     ProvisionLogger.logd("Current version of " + mPackageName
285                             + " higher than the version to be installed. It was not reinstalled.");
286                     // If the package is already at a higher version: success.
287                     // Do not log time if package is already at a higher version, as that isn't
288                     // useful.
289                     success();
290                     return;
291                 } else {
292                     ProvisionLogger.logd("Installing package " + mPackageName + " failed.");
293                     error(ERROR_INSTALLATION_FAILED);
294                     return;
295                 }
296             }
297             ProvisionLogger.logd("Install package callback received for " + mPackageName);
298             addSuccessStatus(SUCCESS_INSTALLED_CALLBACK);
299         }
300     }
301 }
302