• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.InstallLocationUtils;
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                 final Uri referrerUri = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER);
134                 params.setPackageSource(
135                         referrerUri != null ? PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE
136                                 : PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE);
137                 params.setInstallAsInstantApp(false);
138                 params.setReferrerUri(referrerUri);
139                 params.setOriginatingUri(getIntent()
140                         .getParcelableExtra(Intent.EXTRA_ORIGINATING_URI));
141                 params.setOriginatingUid(getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
142                         UID_UNKNOWN));
143                 params.setInstallerPackageName(getIntent().getStringExtra(
144                         Intent.EXTRA_INSTALLER_PACKAGE_NAME));
145                 params.setInstallReason(PackageManager.INSTALL_REASON_USER);
146 
147                 File file = new File(mPackageURI.getPath());
148                 try {
149                     final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
150                     final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
151                             input.reset(), file, /* flags */ 0);
152                     if (result.isError()) {
153                         Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults.");
154                         Log.e(LOG_TAG,
155                                 "Cannot calculate installed size " + file + ". Try only apk size.");
156                         params.setSize(file.length());
157                     } else {
158                         final PackageLite pkg = result.getResult();
159                         params.setAppPackageName(pkg.getPackageName());
160                         params.setInstallLocation(pkg.getInstallLocation());
161                         params.setSize(InstallLocationUtils.calculateInstalledSize(pkg,
162                                 params.abiOverride));
163                     }
164                 } catch (IOException e) {
165                     Log.e(LOG_TAG,
166                             "Cannot calculate installed size " + file + ". Try only apk size.");
167                     params.setSize(file.length());
168                 }
169 
170                 try {
171                     mInstallId = InstallEventReceiver
172                             .addObserver(this, EventResultPersister.GENERATE_NEW_ID,
173                                     this::launchFinishBasedOnResult);
174                 } catch (EventResultPersister.OutOfIdsException e) {
175                     launchFailure(PackageInstaller.STATUS_FAILURE,
176                             PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
177                 }
178 
179                 try {
180                     mSessionId = getPackageManager().getPackageInstaller().createSession(params);
181                 } catch (IOException e) {
182                     launchFailure(PackageInstaller.STATUS_FAILURE,
183                             PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
184                 }
185             }
186 
187             mCancelButton = mAlert.getButton(DialogInterface.BUTTON_NEGATIVE);
188         }
189     }
190 
191     /**
192      * Launch the "success" version of the final package installer dialog
193      */
launchSuccess()194     private void launchSuccess() {
195         Intent successIntent = new Intent(getIntent());
196         successIntent.setClass(this, InstallSuccess.class);
197         successIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
198 
199         startActivity(successIntent);
200         finish();
201     }
202 
203     /**
204      * Launch the "failure" version of the final package installer dialog
205      *
206      * @param statusCode    The generic status code as returned by the package installer.
207      * @param legacyStatus  The status as used internally in the package manager.
208      * @param statusMessage The status description.
209      */
launchFailure(int statusCode, int legacyStatus, String statusMessage)210     private void launchFailure(int statusCode, int legacyStatus, String statusMessage) {
211         Intent failureIntent = new Intent(getIntent());
212         failureIntent.setClass(this, InstallFailed.class);
213         failureIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
214         failureIntent.putExtra(PackageInstaller.EXTRA_STATUS, statusCode);
215         failureIntent.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, legacyStatus);
216         failureIntent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, statusMessage);
217 
218         startActivity(failureIntent);
219         finish();
220     }
221 
222     @Override
onResume()223     protected void onResume() {
224         super.onResume();
225 
226         // This is the first onResume in a single life of the activity
227         if (mInstallingTask == null) {
228             PackageInstaller installer = getPackageManager().getPackageInstaller();
229             PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId);
230 
231             if (sessionInfo != null && !sessionInfo.isActive()) {
232                 mInstallingTask = new InstallingAsyncTask();
233                 mInstallingTask.execute();
234             } else {
235                 // we will receive a broadcast when the install is finished
236                 mCancelButton.setEnabled(false);
237                 setFinishOnTouchOutside(false);
238             }
239         }
240     }
241 
242     @Override
onSaveInstanceState(Bundle outState)243     protected void onSaveInstanceState(Bundle outState) {
244         super.onSaveInstanceState(outState);
245 
246         outState.putInt(SESSION_ID, mSessionId);
247         outState.putInt(INSTALL_ID, mInstallId);
248     }
249 
250     @Override
onBackPressed()251     public void onBackPressed() {
252         if (mCancelButton.isEnabled()) {
253             super.onBackPressed();
254         }
255     }
256 
257     @Override
onDestroy()258     protected void onDestroy() {
259         if (mInstallingTask != null) {
260             mInstallingTask.cancel(true);
261             synchronized (mInstallingTask) {
262                 while (!mInstallingTask.isDone) {
263                     try {
264                         mInstallingTask.wait();
265                     } catch (InterruptedException e) {
266                         Log.i(LOG_TAG, "Interrupted while waiting for installing task to cancel",
267                                 e);
268                     }
269                 }
270             }
271         }
272 
273         InstallEventReceiver.removeObserver(this, mInstallId);
274 
275         super.onDestroy();
276     }
277 
278     /**
279      * Launch the appropriate finish activity (success or failed) for the installation result.
280      *
281      * @param statusCode    The installation result.
282      * @param legacyStatus  The installation as used internally in the package manager.
283      * @param statusMessage The detailed installation result.
284      */
launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage)285     private void launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage) {
286         if (statusCode == PackageInstaller.STATUS_SUCCESS) {
287             launchSuccess();
288         } else {
289             launchFailure(statusCode, legacyStatus, statusMessage);
290         }
291     }
292 
293     /**
294      * Send the package to the package installer and then register a event result observer that
295      * will call {@link #launchFinishBasedOnResult(int, int, String)}
296      */
297     private final class InstallingAsyncTask extends AsyncTask<Void, Void,
298             PackageInstaller.Session> {
299         volatile boolean isDone;
300 
301         @Override
doInBackground(Void... params)302         protected PackageInstaller.Session doInBackground(Void... params) {
303             PackageInstaller.Session session;
304             try {
305                 session = getPackageManager().getPackageInstaller().openSession(mSessionId);
306             } catch (IOException e) {
307                 synchronized (this) {
308                     isDone = true;
309                     notifyAll();
310                 }
311                 return null;
312             }
313 
314             session.setStagingProgress(0);
315 
316             try {
317                 File file = new File(mPackageURI.getPath());
318 
319                 try (InputStream in = new FileInputStream(file)) {
320                     long sizeBytes = file.length();
321                     try (OutputStream out = session
322                             .openWrite("PackageInstaller", 0, sizeBytes)) {
323                         byte[] buffer = new byte[1024 * 1024];
324                         while (true) {
325                             int numRead = in.read(buffer);
326 
327                             if (numRead == -1) {
328                                 session.fsync(out);
329                                 break;
330                             }
331 
332                             if (isCancelled()) {
333                                 session.close();
334                                 break;
335                             }
336 
337                             out.write(buffer, 0, numRead);
338                             if (sizeBytes > 0) {
339                                 float fraction = ((float) numRead / (float) sizeBytes);
340                                 session.addProgress(fraction);
341                             }
342                         }
343                     }
344                 }
345 
346                 return session;
347             } catch (IOException | SecurityException e) {
348                 Log.e(LOG_TAG, "Could not write package", e);
349 
350                 session.close();
351 
352                 return null;
353             } finally {
354                 synchronized (this) {
355                     isDone = true;
356                     notifyAll();
357                 }
358             }
359         }
360 
361         @Override
onPostExecute(PackageInstaller.Session session)362         protected void onPostExecute(PackageInstaller.Session session) {
363             if (session != null) {
364                 Intent broadcastIntent = new Intent(BROADCAST_ACTION);
365                 broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
366                 broadcastIntent.setPackage(getPackageName());
367                 broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);
368 
369                 PendingIntent pendingIntent = PendingIntent.getBroadcast(
370                         InstallInstalling.this,
371                         mInstallId,
372                         broadcastIntent,
373                         PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
374 
375                 session.commit(pendingIntent.getIntentSender());
376                 mCancelButton.setEnabled(false);
377                 setFinishOnTouchOutside(false);
378             } else {
379                 getPackageManager().getPackageInstaller().abandonSession(mSessionId);
380 
381                 if (!isCancelled()) {
382                     launchFailure(PackageInstaller.STATUS_FAILURE,
383                             PackageManager.INSTALL_FAILED_INVALID_APK, null);
384                 }
385             }
386         }
387     }
388 }
389