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
17package com.android.packageinstaller;
18
19import static android.content.pm.PackageInstaller.SessionParams.UID_UNKNOWN;
20
21import android.app.Activity;
22import android.app.PendingIntent;
23import android.content.Intent;
24import android.content.pm.ApplicationInfo;
25import android.content.pm.PackageInstaller;
26import android.content.pm.PackageManager;
27import android.content.pm.PackageParser;
28import android.net.Uri;
29import android.os.AsyncTask;
30import android.os.Bundle;
31import android.support.annotation.Nullable;
32import android.util.Log;
33import android.widget.Button;
34import android.widget.ProgressBar;
35
36import com.android.internal.content.PackageHelper;
37
38import java.io.File;
39import java.io.FileInputStream;
40import java.io.IOException;
41import java.io.InputStream;
42import 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 */
50public 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
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     */
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     */
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
206    protected void onStart() {
207        super.onStart();
208
209        getPackageManager().getPackageInstaller().registerSessionCallback(mSessionCallback);
210    }
211
212    @Override
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
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
241    public void onBackPressed() {
242        if (mCancelButton.isEnabled()) {
243            super.onBackPressed();
244        }
245    }
246
247    @Override
248    protected void onStop() {
249        super.onStop();
250
251        getPackageManager().getPackageInstaller().unregisterSessionCallback(mSessionCallback);
252    }
253
254    @Override
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     */
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
293        public void onCreated(int sessionId) {
294            // empty
295        }
296
297        @Override
298        public void onBadgingChanged(int sessionId) {
299            // empty
300        }
301
302        @Override
303        public void onActiveChanged(int sessionId, boolean active) {
304            // empty
305        }
306
307        @Override
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
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
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
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