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