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 
17 package com.android.packageinstaller;
18 
19 import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_APP_SNIPPET;
20 import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID;
21 
22 import android.app.PendingIntent;
23 import android.content.DialogInterface;
24 import android.content.Intent;
25 import android.content.pm.ApplicationInfo;
26 import android.content.pm.PackageInstaller;
27 import android.content.pm.PackageManager;
28 import android.net.Uri;
29 import android.os.AsyncTask;
30 import android.os.Bundle;
31 import android.util.Log;
32 import android.view.View;
33 import android.widget.Button;
34 
35 import androidx.annotation.Nullable;
36 
37 import java.io.File;
38 import java.io.IOException;
39 
40 /**
41  * Send package to the package manager and handle results from package manager. Once the
42  * installation succeeds, start {@link InstallSuccess} or {@link InstallFailed}.
43  * <p>This has two phases: First send the data to the package manager, then wait until the package
44  * manager processed the result.</p>
45  */
46 public class InstallInstalling extends AlertActivity {
47     private static final String LOG_TAG = InstallInstalling.class.getSimpleName();
48 
49     private static final String SESSION_ID = "com.android.packageinstaller.SESSION_ID";
50     private static final String INSTALL_ID = "com.android.packageinstaller.INSTALL_ID";
51 
52     private static final String BROADCAST_ACTION =
53             "com.android.packageinstaller.ACTION_INSTALL_COMMIT";
54 
55     /** Task that sends the package to the package installer */
56     private InstallingAsyncTask mInstallingTask;
57 
58     /** Id of the session to install the package */
59     private int mSessionId;
60 
61     /** Id of the install event we wait for */
62     private int mInstallId;
63 
64     /** URI of package to install */
65     private Uri mPackageURI;
66 
67     /** The button that can cancel this dialog */
68     private Button mCancelButton;
69 
70     @Override
onCreate(@ullable Bundle savedInstanceState)71     protected void onCreate(@Nullable Bundle savedInstanceState) {
72         super.onCreate(savedInstanceState);
73 
74         ApplicationInfo appInfo = getIntent()
75                 .getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
76         mPackageURI = getIntent().getData();
77 
78         if (PackageInstallerActivity.SCHEME_PACKAGE.equals(mPackageURI.getScheme())) {
79             try {
80                 getPackageManager().installExistingPackage(appInfo.packageName);
81                 launchSuccess();
82             } catch (PackageManager.NameNotFoundException e) {
83                 launchFailure(PackageInstaller.STATUS_FAILURE,
84                         PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
85             }
86         } else {
87             // ContentResolver.SCHEME_FILE
88             // STAGED_SESSION_ID extra contains an ID of a previously staged install session.
89             final File sourceFile = new File(mPackageURI.getPath());
90             PackageUtil.AppSnippet as = getIntent()
91                     .getParcelableExtra(EXTRA_APP_SNIPPET, PackageUtil.AppSnippet.class);
92 
93             mAlert.setIcon(as.icon);
94             mAlert.setTitle(as.label);
95             mAlert.setView(R.layout.install_content_view);
96             mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel),
97                     (ignored, ignored2) -> {
98                         if (mInstallingTask != null) {
99                             mInstallingTask.cancel(true);
100                         }
101 
102                         if (mSessionId > 0) {
103                             getPackageManager().getPackageInstaller().abandonSession(mSessionId);
104                             mSessionId = 0;
105                         }
106 
107                         setResult(RESULT_CANCELED);
108                         finish();
109                     }, null);
110             setupAlert();
111             requireViewById(R.id.installing).setVisibility(View.VISIBLE);
112 
113             if (savedInstanceState != null) {
114                 mSessionId = savedInstanceState.getInt(SESSION_ID);
115                 mInstallId = savedInstanceState.getInt(INSTALL_ID);
116 
117                 // Reregister for result; might instantly call back if result was delivered while
118                 // activity was destroyed
119                 try {
120                     InstallEventReceiver.addObserver(this, mInstallId,
121                             this::launchFinishBasedOnResult);
122                 } catch (EventResultPersister.OutOfIdsException e) {
123                     // Does not happen
124                 }
125             } else {
126                 try {
127                     mInstallId = InstallEventReceiver
128                             .addObserver(this, EventResultPersister.GENERATE_NEW_ID,
129                                     this::launchFinishBasedOnResult);
130                 } catch (EventResultPersister.OutOfIdsException e) {
131                     launchFailure(PackageInstaller.STATUS_FAILURE,
132                             PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
133                 }
134 
135                 mSessionId = getIntent().getIntExtra(EXTRA_STAGED_SESSION_ID, 0);
136                 // Try to open session previously staged in InstallStaging.
137                 try (PackageInstaller.Session ignored =
138                              getPackageManager().getPackageInstaller().openSession(
139                         mSessionId)) {
140                     Log.d(LOG_TAG, "Staged session is valid, proceeding with the install");
141                 } catch (IOException | SecurityException e) {
142                     Log.e(LOG_TAG, "Invalid session id passed", e);
143                     launchFailure(PackageInstaller.STATUS_FAILURE,
144                             PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
145                 }
146             }
147 
148             mCancelButton = mAlert.getButton(DialogInterface.BUTTON_NEGATIVE);
149         }
150     }
151 
152     /**
153      * Launch the "success" version of the final package installer dialog
154      */
launchSuccess()155     private void launchSuccess() {
156         Intent successIntent = new Intent(getIntent());
157         successIntent.setClass(this, InstallSuccess.class);
158         successIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
159 
160         startActivity(successIntent);
161         finish();
162     }
163 
164     /**
165      * Launch the "failure" version of the final package installer dialog
166      *
167      * @param statusCode    The generic status code as returned by the package installer.
168      * @param legacyStatus  The status as used internally in the package manager.
169      * @param statusMessage The status description.
170      */
launchFailure(int statusCode, int legacyStatus, String statusMessage)171     private void launchFailure(int statusCode, int legacyStatus, String statusMessage) {
172         Intent failureIntent = new Intent(getIntent());
173         failureIntent.setClass(this, InstallFailed.class);
174         failureIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
175         failureIntent.putExtra(PackageInstaller.EXTRA_STATUS, statusCode);
176         failureIntent.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, legacyStatus);
177         failureIntent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, statusMessage);
178 
179         startActivity(failureIntent);
180         finish();
181     }
182 
183     @Override
onResume()184     protected void onResume() {
185         super.onResume();
186 
187         // This is the first onResume in a single life of the activity
188         if (mInstallingTask == null) {
189             PackageInstaller installer = getPackageManager().getPackageInstaller();
190             PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId);
191 
192             if (sessionInfo != null && !sessionInfo.isActive()) {
193                 mInstallingTask = new InstallingAsyncTask();
194                 mInstallingTask.execute();
195             } else {
196                 // we will receive a broadcast when the install is finished
197                 mCancelButton.setEnabled(false);
198                 setFinishOnTouchOutside(false);
199             }
200         }
201     }
202 
203     @Override
onSaveInstanceState(Bundle outState)204     protected void onSaveInstanceState(Bundle outState) {
205         super.onSaveInstanceState(outState);
206 
207         outState.putInt(SESSION_ID, mSessionId);
208         outState.putInt(INSTALL_ID, mInstallId);
209     }
210 
211     @Override
onBackPressed()212     public void onBackPressed() {
213         if (mCancelButton.isEnabled()) {
214             super.onBackPressed();
215         }
216     }
217 
218     @Override
onDestroy()219     protected void onDestroy() {
220         if (mInstallingTask != null) {
221             mInstallingTask.cancel(true);
222             synchronized (mInstallingTask) {
223                 while (!mInstallingTask.isDone) {
224                     try {
225                         mInstallingTask.wait();
226                     } catch (InterruptedException e) {
227                         Log.i(LOG_TAG, "Interrupted while waiting for installing task to cancel",
228                                 e);
229                     }
230                 }
231             }
232         }
233 
234         InstallEventReceiver.removeObserver(this, mInstallId);
235 
236         super.onDestroy();
237     }
238 
239     /**
240      * Launch the appropriate finish activity (success or failed) for the installation result.
241      *
242      * @param statusCode    The installation result.
243      * @param legacyStatus  The installation as used internally in the package manager.
244      * @param statusMessage The detailed installation result.
245      * @param serviceId     Id for PowerManager.WakeLock service. Used only by Wear devices
246      *                      during an uninstall.
247      */
launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage, int serviceId )248     private void launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage,
249             int serviceId /* ignore */) {
250         if (statusCode == PackageInstaller.STATUS_SUCCESS) {
251             launchSuccess();
252         } else {
253             launchFailure(statusCode, legacyStatus, statusMessage);
254         }
255     }
256 
257     /**
258      * Send the package to the package installer and then register a event result observer that
259      * will call {@link #launchFinishBasedOnResult(int, int, String, int)}
260      */
261     private final class InstallingAsyncTask extends AsyncTask<Void, Void,
262             PackageInstaller.Session> {
263         volatile boolean isDone;
264 
265         @Override
doInBackground(Void... params)266         protected PackageInstaller.Session doInBackground(Void... params) {
267             try {
268                 return getPackageManager().getPackageInstaller().openSession(mSessionId);
269             } catch (IOException e) {
270                 return null;
271             } finally {
272                 synchronized (this) {
273                     isDone = true;
274                     notifyAll();
275                 }
276             }
277         }
278 
279         @Override
onPostExecute(PackageInstaller.Session session)280         protected void onPostExecute(PackageInstaller.Session session) {
281             if (session != null) {
282                 Intent broadcastIntent = new Intent(BROADCAST_ACTION);
283                 broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
284                 broadcastIntent.setPackage(getPackageName());
285                 broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);
286 
287                 PendingIntent pendingIntent = PendingIntent.getBroadcast(
288                         InstallInstalling.this,
289                         mInstallId,
290                         broadcastIntent,
291                         PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
292 
293                 session.commit(pendingIntent.getIntentSender());
294                 mCancelButton.setEnabled(false);
295                 setFinishOnTouchOutside(false);
296             } else {
297                 getPackageManager().getPackageInstaller().abandonSession(mSessionId);
298 
299                 if (!isCancelled()) {
300                     launchFailure(PackageInstaller.STATUS_FAILURE,
301                             PackageManager.INSTALL_FAILED_INVALID_APK, null);
302                 }
303             }
304         }
305     }
306 }
307