• 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.installFlags = PackageManager.INSTALL_FULL_APP;
115                 params.referrerUri = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER);
116                 params.originatingUri = getIntent()
117                         .getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
118                 params.originatingUid = getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
119                         UID_UNKNOWN);
120                 params.installerPackageName =
121                         getIntent().getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
122 
123                 File file = new File(mPackageURI.getPath());
124                 try {
125                     PackageParser.PackageLite pkg = PackageParser.parsePackageLite(file, 0);
126                     params.setAppPackageName(pkg.packageName);
127                     params.setInstallLocation(pkg.installLocation);
128                     params.setSize(
129                             PackageHelper.calculateInstalledSize(pkg, false, params.abiOverride));
130                 } catch (PackageParser.PackageParserException e) {
131                     Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults.");
132                     Log.e(LOG_TAG,
133                             "Cannot calculate installed size " + file + ". Try only apk size.");
134                     params.setSize(file.length());
135                 } catch (IOException e) {
136                     Log.e(LOG_TAG,
137                             "Cannot calculate installed size " + file + ". Try only apk size.");
138                     params.setSize(file.length());
139                 }
140 
141                 try {
142                     mInstallId = InstallEventReceiver
143                             .addObserver(this, EventResultPersister.GENERATE_NEW_ID,
144                                     this::launchFinishBasedOnResult);
145                 } catch (EventResultPersister.OutOfIdsException e) {
146                     launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
147                 }
148 
149                 try {
150                     mSessionId = getPackageManager().getPackageInstaller().createSession(params);
151                 } catch (IOException e) {
152                     launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
153                 }
154             }
155 
156             mCancelButton = (Button) findViewById(R.id.cancel_button);
157 
158             mCancelButton.setOnClickListener(view -> {
159                 if (mInstallingTask != null) {
160                     mInstallingTask.cancel(true);
161                 }
162 
163                 if (mSessionId > 0) {
164                     getPackageManager().getPackageInstaller().abandonSession(mSessionId);
165                     mSessionId = 0;
166                 }
167 
168                 setResult(RESULT_CANCELED);
169                 finish();
170             });
171 
172             mSessionCallback = new InstallSessionCallback();
173         }
174     }
175 
176     /**
177      * Launch the "success" version of the final package installer dialog
178      */
launchSuccess()179     private void launchSuccess() {
180         Intent successIntent = new Intent(getIntent());
181         successIntent.setClass(this, InstallSuccess.class);
182         successIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
183 
184         startActivity(successIntent);
185         finish();
186     }
187 
188     /**
189      * Launch the "failure" version of the final package installer dialog
190      *
191      * @param legacyStatus  The status as used internally in the package manager.
192      * @param statusMessage The status description.
193      */
launchFailure(int legacyStatus, String statusMessage)194     private void launchFailure(int legacyStatus, String statusMessage) {
195         Intent failureIntent = new Intent(getIntent());
196         failureIntent.setClass(this, InstallFailed.class);
197         failureIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
198         failureIntent.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, legacyStatus);
199         failureIntent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, statusMessage);
200 
201         startActivity(failureIntent);
202         finish();
203     }
204 
205     @Override
onStart()206     protected void onStart() {
207         super.onStart();
208 
209         getPackageManager().getPackageInstaller().registerSessionCallback(mSessionCallback);
210     }
211 
212     @Override
onResume()213     protected void onResume() {
214         super.onResume();
215 
216         // This is the first onResume in a single life of the activity
217         if (mInstallingTask == null) {
218             PackageInstaller installer = getPackageManager().getPackageInstaller();
219             PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId);
220 
221             if (sessionInfo != null && !sessionInfo.isActive()) {
222                 mInstallingTask = new InstallingAsyncTask();
223                 mInstallingTask.execute();
224             } else {
225                 // we will receive a broadcast when the install is finished
226                 mCancelButton.setEnabled(false);
227                 setFinishOnTouchOutside(false);
228             }
229         }
230     }
231 
232     @Override
onSaveInstanceState(Bundle outState)233     protected void onSaveInstanceState(Bundle outState) {
234         super.onSaveInstanceState(outState);
235 
236         outState.putInt(SESSION_ID, mSessionId);
237         outState.putInt(INSTALL_ID, mInstallId);
238     }
239 
240     @Override
onBackPressed()241     public void onBackPressed() {
242         if (mCancelButton.isEnabled()) {
243             super.onBackPressed();
244         }
245     }
246 
247     @Override
onStop()248     protected void onStop() {
249         super.onStop();
250 
251         getPackageManager().getPackageInstaller().unregisterSessionCallback(mSessionCallback);
252     }
253 
254     @Override
onDestroy()255     protected void onDestroy() {
256         if (mInstallingTask != null) {
257             mInstallingTask.cancel(true);
258             synchronized (mInstallingTask) {
259                 while (!mInstallingTask.isDone) {
260                     try {
261                         mInstallingTask.wait();
262                     } catch (InterruptedException e) {
263                         Log.i(LOG_TAG, "Interrupted while waiting for installing task to cancel",
264                                 e);
265                     }
266                 }
267             }
268         }
269 
270         InstallEventReceiver.removeObserver(this, mInstallId);
271 
272         super.onDestroy();
273     }
274 
275     /**
276      * Launch the appropriate finish activity (success or failed) for the installation result.
277      *
278      * @param statusCode    The installation result.
279      * @param legacyStatus  The installation as used internally in the package manager.
280      * @param statusMessage The detailed installation result.
281      */
launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage)282     private void launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage) {
283         if (statusCode == PackageInstaller.STATUS_SUCCESS) {
284             launchSuccess();
285         } else {
286             launchFailure(legacyStatus, statusMessage);
287         }
288     }
289 
290 
291     private class InstallSessionCallback extends PackageInstaller.SessionCallback {
292         @Override
onCreated(int sessionId)293         public void onCreated(int sessionId) {
294             // empty
295         }
296 
297         @Override
onBadgingChanged(int sessionId)298         public void onBadgingChanged(int sessionId) {
299             // empty
300         }
301 
302         @Override
onActiveChanged(int sessionId, boolean active)303         public void onActiveChanged(int sessionId, boolean active) {
304             // empty
305         }
306 
307         @Override
onProgressChanged(int sessionId, float progress)308         public void onProgressChanged(int sessionId, float progress) {
309             if (sessionId == mSessionId) {
310                 ProgressBar progressBar = (ProgressBar)findViewById(R.id.progress_bar);
311                 progressBar.setMax(Integer.MAX_VALUE);
312                 progressBar.setProgress((int) (Integer.MAX_VALUE * progress));
313             }
314         }
315 
316         @Override
onFinished(int sessionId, boolean success)317         public void onFinished(int sessionId, boolean success) {
318             // empty, finish is handled by InstallResultReceiver
319         }
320     }
321 
322     /**
323      * Send the package to the package installer and then register a event result observer that
324      * will call {@link #launchFinishBasedOnResult(int, int, String)}
325      */
326     private final class InstallingAsyncTask extends AsyncTask<Void, Void,
327             PackageInstaller.Session> {
328         volatile boolean isDone;
329 
330         @Override
doInBackground(Void... params)331         protected PackageInstaller.Session doInBackground(Void... params) {
332             PackageInstaller.Session session;
333             try {
334                 session = getPackageManager().getPackageInstaller().openSession(mSessionId);
335             } catch (IOException e) {
336                 return null;
337             }
338 
339             session.setStagingProgress(0);
340 
341             try {
342                 File file = new File(mPackageURI.getPath());
343 
344                 try (InputStream in = new FileInputStream(file)) {
345                     long sizeBytes = file.length();
346                     try (OutputStream out = session
347                             .openWrite("PackageInstaller", 0, sizeBytes)) {
348                         byte[] buffer = new byte[1024 * 1024];
349                         while (true) {
350                             int numRead = in.read(buffer);
351 
352                             if (numRead == -1) {
353                                 session.fsync(out);
354                                 break;
355                             }
356 
357                             if (isCancelled()) {
358                                 session.close();
359                                 break;
360                             }
361 
362                             out.write(buffer, 0, numRead);
363                             if (sizeBytes > 0) {
364                                 float fraction = ((float) numRead / (float) sizeBytes);
365                                 session.addProgress(fraction);
366                             }
367                         }
368                     }
369                 }
370 
371                 return session;
372             } catch (IOException | SecurityException e) {
373                 Log.e(LOG_TAG, "Could not write package", e);
374 
375                 session.close();
376 
377                 return null;
378             } finally {
379                 synchronized (this) {
380                     isDone = true;
381                     notifyAll();
382                 }
383             }
384         }
385 
386         @Override
onPostExecute(PackageInstaller.Session session)387         protected void onPostExecute(PackageInstaller.Session session) {
388             if (session != null) {
389                 Intent broadcastIntent = new Intent(BROADCAST_ACTION);
390                 broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
391                 broadcastIntent.setPackage(
392                         getPackageManager().getPermissionControllerPackageName());
393                 broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);
394 
395                 PendingIntent pendingIntent = PendingIntent.getBroadcast(
396                         InstallInstalling.this,
397                         mInstallId,
398                         broadcastIntent,
399                         PendingIntent.FLAG_UPDATE_CURRENT);
400 
401                 session.commit(pendingIntent.getIntentSender());
402                 mCancelButton.setEnabled(false);
403                 setFinishOnTouchOutside(false);
404             } else {
405                 getPackageManager().getPackageInstaller().abandonSession(mSessionId);
406 
407                 if (!isCancelled()) {
408                     launchFailure(PackageManager.INSTALL_FAILED_INVALID_APK, null);
409                 }
410             }
411         }
412     }
413 }
414