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 android.app.Activity;
20import android.app.ActivityThread;
21import android.app.AlertDialog;
22import android.app.Dialog;
23import android.app.DialogFragment;
24import android.app.Fragment;
25import android.app.FragmentTransaction;
26import android.app.PendingIntent;
27import android.content.Intent;
28import android.content.pm.ApplicationInfo;
29import android.content.pm.IPackageDeleteObserver2;
30import android.content.pm.PackageInstaller;
31import android.content.pm.PackageManager;
32import android.content.pm.VersionedPackage;
33import android.os.Bundle;
34import android.os.IBinder;
35import android.os.RemoteException;
36import android.os.UserHandle;
37import android.support.annotation.Nullable;
38import android.widget.Toast;
39
40/**
41 * Start an uninstallation, show a dialog while uninstalling and return result to the caller.
42 */
43public class UninstallUninstalling extends Activity implements
44        EventResultPersister.EventResultObserver {
45    private static final String UNINSTALL_ID = "com.android.packageinstaller.UNINSTALL_ID";
46    private static final String BROADCAST_ACTION =
47            "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT";
48
49    static final String EXTRA_APP_LABEL = "com.android.packageinstaller.extra.APP_LABEL";
50
51    private int mUninstallId;
52    private ApplicationInfo mAppInfo;
53    private IBinder mCallback;
54    private boolean mReturnResult;
55    private String mLabel;
56
57    @Override
58    protected void onCreate(@Nullable Bundle savedInstanceState) {
59        super.onCreate(savedInstanceState);
60
61        setFinishOnTouchOutside(false);
62
63        mAppInfo = getIntent().getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
64        mCallback = getIntent().getIBinderExtra(PackageInstaller.EXTRA_CALLBACK);
65        mReturnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);
66        mLabel = getIntent().getStringExtra(EXTRA_APP_LABEL);
67
68        try {
69            if (savedInstanceState == null) {
70                boolean allUsers = getIntent().getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS,
71                        false);
72                UserHandle user = getIntent().getParcelableExtra(Intent.EXTRA_USER);
73
74                // Show dialog, which is the whole UI
75                FragmentTransaction transaction = getFragmentManager().beginTransaction();
76                Fragment prev = getFragmentManager().findFragmentByTag("dialog");
77                if (prev != null) {
78                    transaction.remove(prev);
79                }
80                DialogFragment dialog = new UninstallUninstallingFragment();
81                dialog.setCancelable(false);
82                dialog.show(transaction, "dialog");
83
84                mUninstallId = UninstallEventReceiver.addObserver(this,
85                        EventResultPersister.GENERATE_NEW_ID, this);
86
87                Intent broadcastIntent = new Intent(BROADCAST_ACTION);
88                broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
89                broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mUninstallId);
90                broadcastIntent.setPackage(getPackageName());
91
92                PendingIntent pendingIntent = PendingIntent.getBroadcast(this, mUninstallId,
93                        broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT);
94
95                try {
96                    ActivityThread.getPackageManager().getPackageInstaller().uninstall(
97                            new VersionedPackage(mAppInfo.packageName,
98                                    PackageManager.VERSION_CODE_HIGHEST),
99                            getPackageName(), allUsers ? PackageManager.DELETE_ALL_USERS : 0,
100                            pendingIntent.getIntentSender(), user.getIdentifier());
101                } catch (RemoteException e) {
102                    e.rethrowFromSystemServer();
103                }
104            } else {
105                mUninstallId = savedInstanceState.getInt(UNINSTALL_ID);
106                UninstallEventReceiver.addObserver(this, mUninstallId, this);
107            }
108        } catch (EventResultPersister.OutOfIdsException e) {
109            onResult(PackageInstaller.STATUS_FAILURE, PackageManager.DELETE_FAILED_INTERNAL_ERROR,
110                    null);
111        }
112    }
113
114    @Override
115    protected void onSaveInstanceState(Bundle outState) {
116        super.onSaveInstanceState(outState);
117
118        outState.putInt(UNINSTALL_ID, mUninstallId);
119    }
120
121    @Override
122    public void onBackPressed() {
123        // do nothing
124    }
125
126    @Override
127    public void onResult(int status, int legacyStatus, @Nullable String message) {
128        if (mCallback != null) {
129            // The caller will be informed about the result via a callback
130            final IPackageDeleteObserver2 observer = IPackageDeleteObserver2.Stub
131                    .asInterface(mCallback);
132            try {
133                observer.onPackageDeleted(mAppInfo.packageName, legacyStatus, message);
134            } catch (RemoteException ignored) {
135            }
136        } else if (mReturnResult) {
137            // The caller will be informed about the result and might decide to display it
138            Intent result = new Intent();
139
140            result.putExtra(Intent.EXTRA_INSTALL_RESULT, legacyStatus);
141            setResult(status == PackageInstaller.STATUS_SUCCESS ? Activity.RESULT_OK
142                    : Activity.RESULT_FIRST_USER, result);
143        } else {
144            // This is the rare case that the caller did not ask for the result, but wanted to be
145            // notified via onActivityResult when the installation finishes
146            if (status != PackageInstaller.STATUS_SUCCESS) {
147                Toast.makeText(this, getString(R.string.uninstall_failed_app, mLabel),
148                        Toast.LENGTH_LONG).show();
149            }
150        }
151        finish();
152    }
153
154    @Override
155    protected void onDestroy() {
156        UninstallEventReceiver.removeObserver(this, mUninstallId);
157
158        super.onDestroy();
159    }
160
161    /**
162     * Dialog that shows that the app is uninstalling.
163     */
164    public static class UninstallUninstallingFragment extends DialogFragment {
165        @Override
166        public Dialog onCreateDialog(Bundle savedInstanceState) {
167            AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity());
168
169            dialogBuilder.setCancelable(false);
170            dialogBuilder.setMessage(getActivity().getString(R.string.uninstalling_app,
171                    ((UninstallUninstalling) getActivity()).mLabel));
172
173            Dialog dialog = dialogBuilder.create();
174            dialog.setCanceledOnTouchOutside(false);
175
176            return dialog;
177        }
178    }
179}
180