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.PackageParser;
29 import android.net.Uri;
30 import android.os.AsyncTask;
31 import android.os.Bundle;
32 import android.util.Log;
33 import android.view.View;
34 import android.widget.Button;
35 import android.widget.ProgressBar;
36 
37 import com.android.internal.app.AlertActivity;
38 import com.android.internal.content.PackageHelper;
39 
40 import java.io.File;
41 import java.io.FileInputStream;
42 import java.io.IOException;
43 import java.io.InputStream;
44 import java.io.OutputStream;
45 
46 /**
47  * Send package to the package manager and handle results from package manager. Once the
48  * installation succeeds, start {@link InstallSuccess} or {@link InstallFailed}.
49  * <p>This has two phases: First send the data to the package manager, then wait until the package
50  * manager processed the result.</p>
51  */
52 public class InstallInstalling extends AlertActivity {
53     private static final String LOG_TAG = InstallInstalling.class.getSimpleName();
54 
55     private static final String SESSION_ID = "com.android.packageinstaller.SESSION_ID";
56     private static final String INSTALL_ID = "com.android.packageinstaller.INSTALL_ID";
57 
58     private static final String BROADCAST_ACTION =
59             "com.android.packageinstaller.ACTION_INSTALL_COMMIT";
60 
61     /** Listens to changed to the session and updates progress bar */
62     private PackageInstaller.SessionCallback mSessionCallback;
63 
64     /** Task that sends the package to the package installer */
65     private InstallingAsyncTask mInstallingTask;
66 
67     /** Id of the session to install the package */
68     private int mSessionId;
69 
70     /** Id of the install event we wait for */
71     private int mInstallId;
72 
73     /** URI of package to install */
74     private Uri mPackageURI;
75 
76     /** The button that can cancel this dialog */
77     private Button mCancelButton;
78 
79     @Override
onCreate(@ullable Bundle savedInstanceState)80     protected void onCreate(@Nullable Bundle savedInstanceState) {
81         super.onCreate(savedInstanceState);
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.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                     PackageParser.PackageLite pkg = PackageParser.parsePackageLite(file, 0);
146                     params.setAppPackageName(pkg.packageName);
147                     params.setInstallLocation(pkg.installLocation);
148                     params.setSize(
149                             PackageHelper.calculateInstalledSize(pkg, false, params.abiOverride));
150                 } catch (PackageParser.PackageParserException e) {
151                     Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults.");
152                     Log.e(LOG_TAG,
153                             "Cannot calculate installed size " + file + ". Try only apk size.");
154                     params.setSize(file.length());
155                 } catch (IOException e) {
156                     Log.e(LOG_TAG,
157                             "Cannot calculate installed size " + file + ". Try only apk size.");
158                     params.setSize(file.length());
159                 }
160 
161                 try {
162                     mInstallId = InstallEventReceiver
163                             .addObserver(this, EventResultPersister.GENERATE_NEW_ID,
164                                     this::launchFinishBasedOnResult);
165                 } catch (EventResultPersister.OutOfIdsException e) {
166                     launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
167                 }
168 
169                 try {
170                     mSessionId = getPackageManager().getPackageInstaller().createSession(params);
171                 } catch (IOException e) {
172                     launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
173                 }
174             }
175 
176             mCancelButton = mAlert.getButton(DialogInterface.BUTTON_NEGATIVE);
177 
178             mSessionCallback = new InstallSessionCallback();
179         }
180     }
181 
182     /**
183      * Launch the "success" version of the final package installer dialog
184      */
launchSuccess()185     private void launchSuccess() {
186         Intent successIntent = new Intent(getIntent());
187         successIntent.setClass(this, InstallSuccess.class);
188         successIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
189 
190         startActivity(successIntent);
191         finish();
192     }
193 
194     /**
195      * Launch the "failure" version of the final package installer dialog
196      *
197      * @param legacyStatus  The status as used internally in the package manager.
198      * @param statusMessage The status description.
199      */
launchFailure(int legacyStatus, String statusMessage)200     private void launchFailure(int legacyStatus, String statusMessage) {
201         Intent failureIntent = new Intent(getIntent());
202         failureIntent.setClass(this, InstallFailed.class);
203         failureIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
204         failureIntent.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, legacyStatus);
205         failureIntent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, statusMessage);
206 
207         startActivity(failureIntent);
208         finish();
209     }
210 
211     @Override
onStart()212     protected void onStart() {
213         super.onStart();
214 
215         getPackageManager().getPackageInstaller().registerSessionCallback(mSessionCallback);
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
onStop()254     protected void onStop() {
255         super.onStop();
256 
257         getPackageManager().getPackageInstaller().unregisterSessionCallback(mSessionCallback);
258     }
259 
260     @Override
onDestroy()261     protected void onDestroy() {
262         if (mInstallingTask != null) {
263             mInstallingTask.cancel(true);
264             synchronized (mInstallingTask) {
265                 while (!mInstallingTask.isDone) {
266                     try {
267                         mInstallingTask.wait();
268                     } catch (InterruptedException e) {
269                         Log.i(LOG_TAG, "Interrupted while waiting for installing task to cancel",
270                                 e);
271                     }
272                 }
273             }
274         }
275 
276         InstallEventReceiver.removeObserver(this, mInstallId);
277 
278         super.onDestroy();
279     }
280 
281     /**
282      * Launch the appropriate finish activity (success or failed) for the installation result.
283      *
284      * @param statusCode    The installation result.
285      * @param legacyStatus  The installation as used internally in the package manager.
286      * @param statusMessage The detailed installation result.
287      */
launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage)288     private void launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage) {
289         if (statusCode == PackageInstaller.STATUS_SUCCESS) {
290             launchSuccess();
291         } else {
292             launchFailure(legacyStatus, statusMessage);
293         }
294     }
295 
296 
297     private class InstallSessionCallback extends PackageInstaller.SessionCallback {
298         @Override
onCreated(int sessionId)299         public void onCreated(int sessionId) {
300             // empty
301         }
302 
303         @Override
onBadgingChanged(int sessionId)304         public void onBadgingChanged(int sessionId) {
305             // empty
306         }
307 
308         @Override
onActiveChanged(int sessionId, boolean active)309         public void onActiveChanged(int sessionId, boolean active) {
310             // empty
311         }
312 
313         @Override
onProgressChanged(int sessionId, float progress)314         public void onProgressChanged(int sessionId, float progress) {
315             if (sessionId == mSessionId) {
316                 ProgressBar progressBar = requireViewById(R.id.progress);
317                 progressBar.setMax(Integer.MAX_VALUE);
318                 progressBar.setProgress((int) (Integer.MAX_VALUE * progress));
319             }
320         }
321 
322         @Override
onFinished(int sessionId, boolean success)323         public void onFinished(int sessionId, boolean success) {
324             // empty, finish is handled by InstallResultReceiver
325         }
326     }
327 
328     /**
329      * Send the package to the package installer and then register a event result observer that
330      * will call {@link #launchFinishBasedOnResult(int, int, String)}
331      */
332     private final class InstallingAsyncTask extends AsyncTask<Void, Void,
333             PackageInstaller.Session> {
334         volatile boolean isDone;
335 
336         @Override
doInBackground(Void... params)337         protected PackageInstaller.Session doInBackground(Void... params) {
338             PackageInstaller.Session session;
339             try {
340                 session = getPackageManager().getPackageInstaller().openSession(mSessionId);
341             } catch (IOException e) {
342                 return null;
343             }
344 
345             session.setStagingProgress(0);
346 
347             try {
348                 File file = new File(mPackageURI.getPath());
349 
350                 try (InputStream in = new FileInputStream(file)) {
351                     long sizeBytes = file.length();
352                     try (OutputStream out = session
353                             .openWrite("PackageInstaller", 0, sizeBytes)) {
354                         byte[] buffer = new byte[1024 * 1024];
355                         while (true) {
356                             int numRead = in.read(buffer);
357 
358                             if (numRead == -1) {
359                                 session.fsync(out);
360                                 break;
361                             }
362 
363                             if (isCancelled()) {
364                                 session.close();
365                                 break;
366                             }
367 
368                             out.write(buffer, 0, numRead);
369                             if (sizeBytes > 0) {
370                                 float fraction = ((float) numRead / (float) sizeBytes);
371                                 session.addProgress(fraction);
372                             }
373                         }
374                     }
375                 }
376 
377                 return session;
378             } catch (IOException | SecurityException e) {
379                 Log.e(LOG_TAG, "Could not write package", e);
380 
381                 session.close();
382 
383                 return null;
384             } finally {
385                 synchronized (this) {
386                     isDone = true;
387                     notifyAll();
388                 }
389             }
390         }
391 
392         @Override
onPostExecute(PackageInstaller.Session session)393         protected void onPostExecute(PackageInstaller.Session session) {
394             if (session != null) {
395                 Intent broadcastIntent = new Intent(BROADCAST_ACTION);
396                 broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
397                 broadcastIntent.setPackage(getPackageName());
398                 broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);
399 
400                 PendingIntent pendingIntent = PendingIntent.getBroadcast(
401                         InstallInstalling.this,
402                         mInstallId,
403                         broadcastIntent,
404                         PendingIntent.FLAG_UPDATE_CURRENT);
405 
406                 session.commit(pendingIntent.getIntentSender());
407                 mCancelButton.setEnabled(false);
408                 setFinishOnTouchOutside(false);
409             } else {
410                 getPackageManager().getPackageInstaller().abandonSession(mSessionId);
411 
412                 if (!isCancelled()) {
413                     launchFailure(PackageManager.INSTALL_FAILED_INVALID_APK, null);
414                 }
415             }
416         }
417     }
418 }
419