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 android.content.pm.PackageInstaller.SessionParams.UID_UNKNOWN;
20 
21 import android.annotation.Nullable;
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.content.pm.parsing.ApkLiteParseUtils;
29 import android.content.pm.parsing.PackageLite;
30 import android.content.pm.parsing.result.ParseResult;
31 import android.content.pm.parsing.result.ParseTypeImpl;
32 import android.net.Uri;
33 import android.os.AsyncTask;
34 import android.os.Bundle;
35 import android.util.Log;
36 import android.view.View;
37 import android.widget.Button;
38 
39 import com.android.internal.app.AlertActivity;
40 import com.android.internal.content.PackageHelper;
41 
42 import java.io.File;
43 import java.io.FileInputStream;
44 import java.io.IOException;
45 import java.io.InputStream;
46 import java.io.OutputStream;
47 
48 /**
49  * Send package to the package manager and handle results from package manager. Once the
50  * installation succeeds, start {@link InstallSuccess} or {@link InstallFailed}.
51  * <p>This has two phases: First send the data to the package manager, then wait until the package
52  * manager processed the result.</p>
53  */
54 public class InstallInstalling extends AlertActivity {
55     private static final String LOG_TAG = InstallInstalling.class.getSimpleName();
56 
57     private static final String SESSION_ID = "com.android.packageinstaller.SESSION_ID";
58     private static final String INSTALL_ID = "com.android.packageinstaller.INSTALL_ID";
59 
60     private static final String BROADCAST_ACTION =
61             "com.android.packageinstaller.ACTION_INSTALL_COMMIT";
62 
63     /** Task that sends the package to the package installer */
64     private InstallingAsyncTask mInstallingTask;
65 
66     /** Id of the session to install the package */
67     private int mSessionId;
68 
69     /** Id of the install event we wait for */
70     private int mInstallId;
71 
72     /** URI of package to install */
73     private Uri mPackageURI;
74 
75     /** The button that can cancel this dialog */
76     private Button mCancelButton;
77 
78     @Override
onCreate(@ullable Bundle savedInstanceState)79     protected void onCreate(@Nullable Bundle savedInstanceState) {
80         super.onCreate(savedInstanceState);
81 
82         ApplicationInfo appInfo = getIntent()
83                 .getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
84         mPackageURI = getIntent().getData();
85 
86         if ("package".equals(mPackageURI.getScheme())) {
87             try {
88                 getPackageManager().installExistingPackage(appInfo.packageName);
89                 launchSuccess();
90             } catch (PackageManager.NameNotFoundException e) {
91                 launchFailure(PackageInstaller.STATUS_FAILURE,
92                         PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
93             }
94         } else {
95             final File sourceFile = new File(mPackageURI.getPath());
96             PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, appInfo, sourceFile);
97 
98             mAlert.setIcon(as.icon);
99             mAlert.setTitle(as.label);
100             mAlert.setView(R.layout.install_content_view);
101             mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel),
102                     (ignored, ignored2) -> {
103                         if (mInstallingTask != null) {
104                             mInstallingTask.cancel(true);
105                         }
106 
107                         if (mSessionId > 0) {
108                             getPackageManager().getPackageInstaller().abandonSession(mSessionId);
109                             mSessionId = 0;
110                         }
111 
112                         setResult(RESULT_CANCELED);
113                         finish();
114                     }, null);
115             setupAlert();
116             requireViewById(R.id.installing).setVisibility(View.VISIBLE);
117 
118             if (savedInstanceState != null) {
119                 mSessionId = savedInstanceState.getInt(SESSION_ID);
120                 mInstallId = savedInstanceState.getInt(INSTALL_ID);
121 
122                 // Reregister for result; might instantly call back if result was delivered while
123                 // activity was destroyed
124                 try {
125                     InstallEventReceiver.addObserver(this, mInstallId,
126                             this::launchFinishBasedOnResult);
127                 } catch (EventResultPersister.OutOfIdsException e) {
128                     // Does not happen
129                 }
130             } else {
131                 PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
132                         PackageInstaller.SessionParams.MODE_FULL_INSTALL);
133                 params.setInstallAsInstantApp(false);
134                 params.setReferrerUri(getIntent().getParcelableExtra(Intent.EXTRA_REFERRER));
135                 params.setOriginatingUri(getIntent()
136                         .getParcelableExtra(Intent.EXTRA_ORIGINATING_URI));
137                 params.setOriginatingUid(getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
138                         UID_UNKNOWN));
139                 params.setInstallerPackageName(getIntent().getStringExtra(
140                         Intent.EXTRA_INSTALLER_PACKAGE_NAME));
141                 params.setInstallReason(PackageManager.INSTALL_REASON_USER);
142 
143                 File file = new File(mPackageURI.getPath());
144                 try {
145                     final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
146                     final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
147                             input.reset(), file, /* flags */ 0);
148                     if (result.isError()) {
149                         Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults.");
150                         Log.e(LOG_TAG,
151                                 "Cannot calculate installed size " + file + ". Try only apk size.");
152                         params.setSize(file.length());
153                     } else {
154                         final PackageLite pkg = result.getResult();
155                         params.setAppPackageName(pkg.getPackageName());
156                         params.setInstallLocation(pkg.getInstallLocation());
157                         params.setSize(
158                                 PackageHelper.calculateInstalledSize(pkg, params.abiOverride));
159                     }
160                 } catch (IOException e) {
161                     Log.e(LOG_TAG,
162                             "Cannot calculate installed size " + file + ". Try only apk size.");
163                     params.setSize(file.length());
164                 }
165 
166                 try {
167                     mInstallId = InstallEventReceiver
168                             .addObserver(this, EventResultPersister.GENERATE_NEW_ID,
169                                     this::launchFinishBasedOnResult);
170                 } catch (EventResultPersister.OutOfIdsException e) {
171                     launchFailure(PackageInstaller.STATUS_FAILURE,
172                             PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
173                 }
174 
175                 try {
176                     mSessionId = getPackageManager().getPackageInstaller().createSession(params);
177                 } catch (IOException e) {
178                     launchFailure(PackageInstaller.STATUS_FAILURE,
179                             PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
180                 }
181             }
182 
183             mCancelButton = mAlert.getButton(DialogInterface.BUTTON_NEGATIVE);
184         }
185     }
186 
187     /**
188      * Launch the "success" version of the final package installer dialog
189      */
launchSuccess()190     private void launchSuccess() {
191         Intent successIntent = new Intent(getIntent());
192         successIntent.setClass(this, InstallSuccess.class);
193         successIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
194 
195         startActivity(successIntent);
196         finish();
197     }
198 
199     /**
200      * Launch the "failure" version of the final package installer dialog
201      *
202      * @param statusCode    The generic status code as returned by the package installer.
203      * @param legacyStatus  The status as used internally in the package manager.
204      * @param statusMessage The status description.
205      */
launchFailure(int statusCode, int legacyStatus, String statusMessage)206     private void launchFailure(int statusCode, int legacyStatus, String statusMessage) {
207         Intent failureIntent = new Intent(getIntent());
208         failureIntent.setClass(this, InstallFailed.class);
209         failureIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
210         failureIntent.putExtra(PackageInstaller.EXTRA_STATUS, statusCode);
211         failureIntent.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, legacyStatus);
212         failureIntent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, statusMessage);
213 
214         startActivity(failureIntent);
215         finish();
216     }
217 
218     @Override
onResume()219     protected void onResume() {
220         super.onResume();
221 
222         // This is the first onResume in a single life of the activity
223         if (mInstallingTask == null) {
224             PackageInstaller installer = getPackageManager().getPackageInstaller();
225             PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId);
226 
227             if (sessionInfo != null && !sessionInfo.isActive()) {
228                 mInstallingTask = new InstallingAsyncTask();
229                 mInstallingTask.execute();
230             } else {
231                 // we will receive a broadcast when the install is finished
232                 mCancelButton.setEnabled(false);
233                 setFinishOnTouchOutside(false);
234             }
235         }
236     }
237 
238     @Override
onSaveInstanceState(Bundle outState)239     protected void onSaveInstanceState(Bundle outState) {
240         super.onSaveInstanceState(outState);
241 
242         outState.putInt(SESSION_ID, mSessionId);
243         outState.putInt(INSTALL_ID, mInstallId);
244     }
245 
246     @Override
onBackPressed()247     public void onBackPressed() {
248         if (mCancelButton.isEnabled()) {
249             super.onBackPressed();
250         }
251     }
252 
253     @Override
onDestroy()254     protected void onDestroy() {
255         if (mInstallingTask != null) {
256             mInstallingTask.cancel(true);
257             synchronized (mInstallingTask) {
258                 while (!mInstallingTask.isDone) {
259                     try {
260                         mInstallingTask.wait();
261                     } catch (InterruptedException e) {
262                         Log.i(LOG_TAG, "Interrupted while waiting for installing task to cancel",
263                                 e);
264                     }
265                 }
266             }
267         }
268 
269         InstallEventReceiver.removeObserver(this, mInstallId);
270 
271         super.onDestroy();
272     }
273 
274     /**
275      * Launch the appropriate finish activity (success or failed) for the installation result.
276      *
277      * @param statusCode    The installation result.
278      * @param legacyStatus  The installation as used internally in the package manager.
279      * @param statusMessage The detailed installation result.
280      */
launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage)281     private void launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage) {
282         if (statusCode == PackageInstaller.STATUS_SUCCESS) {
283             launchSuccess();
284         } else {
285             launchFailure(statusCode, legacyStatus, statusMessage);
286         }
287     }
288 
289     /**
290      * Send the package to the package installer and then register a event result observer that
291      * will call {@link #launchFinishBasedOnResult(int, int, String)}
292      */
293     private final class InstallingAsyncTask extends AsyncTask<Void, Void,
294             PackageInstaller.Session> {
295         volatile boolean isDone;
296 
297         @Override
doInBackground(Void... params)298         protected PackageInstaller.Session doInBackground(Void... params) {
299             PackageInstaller.Session session;
300             try {
301                 session = getPackageManager().getPackageInstaller().openSession(mSessionId);
302             } catch (IOException e) {
303                 synchronized (this) {
304                     isDone = true;
305                     notifyAll();
306                 }
307                 return null;
308             }
309 
310             session.setStagingProgress(0);
311 
312             try {
313                 File file = new File(mPackageURI.getPath());
314 
315                 try (InputStream in = new FileInputStream(file)) {
316                     long sizeBytes = file.length();
317                     try (OutputStream out = session
318                             .openWrite("PackageInstaller", 0, sizeBytes)) {
319                         byte[] buffer = new byte[1024 * 1024];
320                         while (true) {
321                             int numRead = in.read(buffer);
322 
323                             if (numRead == -1) {
324                                 session.fsync(out);
325                                 break;
326                             }
327 
328                             if (isCancelled()) {
329                                 session.close();
330                                 break;
331                             }
332 
333                             out.write(buffer, 0, numRead);
334                             if (sizeBytes > 0) {
335                                 float fraction = ((float) numRead / (float) sizeBytes);
336                                 session.addProgress(fraction);
337                             }
338                         }
339                     }
340                 }
341 
342                 return session;
343             } catch (IOException | SecurityException e) {
344                 Log.e(LOG_TAG, "Could not write package", e);
345 
346                 session.close();
347 
348                 return null;
349             } finally {
350                 synchronized (this) {
351                     isDone = true;
352                     notifyAll();
353                 }
354             }
355         }
356 
357         @Override
onPostExecute(PackageInstaller.Session session)358         protected void onPostExecute(PackageInstaller.Session session) {
359             if (session != null) {
360                 Intent broadcastIntent = new Intent(BROADCAST_ACTION);
361                 broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
362                 broadcastIntent.setPackage(getPackageName());
363                 broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);
364 
365                 PendingIntent pendingIntent = PendingIntent.getBroadcast(
366                         InstallInstalling.this,
367                         mInstallId,
368                         broadcastIntent,
369                         PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
370 
371                 session.commit(pendingIntent.getIntentSender());
372                 mCancelButton.setEnabled(false);
373                 setFinishOnTouchOutside(false);
374             } else {
375                 getPackageManager().getPackageInstaller().abandonSession(mSessionId);
376 
377                 if (!isCancelled()) {
378                     launchFailure(PackageInstaller.STATUS_FAILURE,
379                             PackageManager.INSTALL_FAILED_INVALID_APK, null);
380                 }
381             }
382         }
383     }
384 }
385