1/*
2**
3** Copyright 2007, The Android Open Source Project
4**
5** Licensed under the Apache License, Version 2.0 (the "License");
6** you may not use this file except in compliance with the License.
7** You may obtain a copy of the License at
8**
9**     http://www.apache.org/licenses/LICENSE-2.0
10**
11** Unless required by applicable law or agreed to in writing, software
12** distributed under the License is distributed on an "AS IS" BASIS,
13** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14** See the License for the specific language governing permissions and
15** limitations under the License.
16*/
17package com.android.packageinstaller;
18
19import android.app.Activity;
20import android.app.admin.IDevicePolicyManager;
21import android.content.Context;
22import android.content.Intent;
23import android.content.pm.ApplicationInfo;
24import android.content.pm.IPackageDeleteObserver;
25import android.content.pm.IPackageDeleteObserver2;
26import android.content.pm.IPackageManager;
27import android.content.pm.PackageInstaller;
28import android.content.pm.PackageManager;
29import android.content.pm.UserInfo;
30import android.content.res.Configuration;
31import android.graphics.Color;
32import android.graphics.drawable.ColorDrawable;
33import android.os.Bundle;
34import android.os.Handler;
35import android.os.IBinder;
36import android.os.Message;
37import android.os.RemoteException;
38import android.os.ServiceManager;
39import android.os.UserHandle;
40import android.os.UserManager;
41import android.provider.Settings;
42import android.util.Log;
43import android.util.TypedValue;
44import android.view.KeyEvent;
45import android.view.View;
46import android.view.View.OnClickListener;
47import android.widget.Button;
48import android.widget.ProgressBar;
49import android.widget.TextView;
50import android.widget.Toast;
51
52import java.util.List;
53
54/**
55 * This activity corresponds to a download progress screen that is displayed
56 * when an application is uninstalled. The result of the application uninstall
57 * is indicated in the result code that gets set to 0 or 1. The application gets launched
58 * by an intent with the intent's class name explicitly set to UninstallAppProgress and expects
59 * the application object of the application to uninstall.
60 */
61public class UninstallAppProgress extends Activity implements OnClickListener {
62    private final String TAG="UninstallAppProgress";
63
64    private ApplicationInfo mAppInfo;
65    private boolean mAllUsers;
66    private UserHandle mUser;
67    private IBinder mCallback;
68
69    private Button mOkButton;
70    private Button mDeviceManagerButton;
71    private Button mUsersButton;
72    private volatile int mResultCode = -1;
73
74    /**
75     * If initView was called. We delay this call to not have to call it at all if the uninstall is
76     * quick
77     */
78    private boolean mIsViewInitialized;
79
80    /** Amount of time to wait until we show the UI */
81    private static final int QUICK_INSTALL_DELAY_MILLIS = 500;
82
83    private static final int UNINSTALL_COMPLETE = 1;
84    private static final int UNINSTALL_IS_SLOW = 2;
85
86    private boolean isProfileOfOrSame(UserManager userManager, int userId, int profileId) {
87        if (userId == profileId) {
88            return true;
89        }
90        UserInfo parentUser = userManager.getProfileParent(profileId);
91        return parentUser != null && parentUser.id == userId;
92    }
93
94    private Handler mHandler = new Handler() {
95        public void handleMessage(Message msg) {
96            if (isFinishing() || isDestroyed()) {
97                return;
98            }
99
100            switch (msg.what) {
101                case UNINSTALL_IS_SLOW:
102                    initView();
103                    break;
104                case UNINSTALL_COMPLETE:
105                    mHandler.removeMessages(UNINSTALL_IS_SLOW);
106
107                    if (msg.arg1 != PackageManager.DELETE_SUCCEEDED) {
108                        initView();
109                    }
110
111                    mResultCode = msg.arg1;
112                    final String packageName = (String) msg.obj;
113
114                    if (mCallback != null) {
115                        final IPackageDeleteObserver2 observer = IPackageDeleteObserver2.Stub
116                                .asInterface(mCallback);
117                        try {
118                            observer.onPackageDeleted(mAppInfo.packageName, mResultCode,
119                                    packageName);
120                        } catch (RemoteException ignored) {
121                        }
122                        finish();
123                        return;
124                    }
125
126                    if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
127                        Intent result = new Intent();
128                        result.putExtra(Intent.EXTRA_INSTALL_RESULT, mResultCode);
129                        setResult(mResultCode == PackageManager.DELETE_SUCCEEDED
130                                ? Activity.RESULT_OK : Activity.RESULT_FIRST_USER,
131                                        result);
132                        finish();
133                        return;
134                    }
135
136                    // Update the status text
137                    final String statusText;
138                    switch (msg.arg1) {
139                        case PackageManager.DELETE_SUCCEEDED:
140                            statusText = getString(R.string.uninstall_done);
141                            // Show a Toast and finish the activity
142                            Context ctx = getBaseContext();
143                            Toast.makeText(ctx, statusText, Toast.LENGTH_LONG).show();
144                            setResultAndFinish(mResultCode);
145                            return;
146                        case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER: {
147                            UserManager userManager =
148                                    (UserManager) getSystemService(Context.USER_SERVICE);
149                            IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface(
150                                    ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
151                            // Find out if the package is an active admin for some non-current user.
152                            int myUserId = UserHandle.myUserId();
153                            UserInfo otherBlockingUser = null;
154                            for (UserInfo user : userManager.getUsers()) {
155                                // We only catch the case when the user in question is neither the
156                                // current user nor its profile.
157                                if (isProfileOfOrSame(userManager, myUserId, user.id)) continue;
158
159                                try {
160                                    if (dpm.packageHasActiveAdmins(packageName, user.id)) {
161                                        otherBlockingUser = user;
162                                        break;
163                                    }
164                                } catch (RemoteException e) {
165                                    Log.e(TAG, "Failed to talk to package manager", e);
166                                }
167                            }
168                            if (otherBlockingUser == null) {
169                                Log.d(TAG, "Uninstall failed because " + packageName
170                                        + " is a device admin");
171                                mDeviceManagerButton.setVisibility(View.VISIBLE);
172                                statusText = getString(
173                                        R.string.uninstall_failed_device_policy_manager);
174                            } else {
175                                Log.d(TAG, "Uninstall failed because " + packageName
176                                        + " is a device admin of user " + otherBlockingUser);
177                                mDeviceManagerButton.setVisibility(View.GONE);
178                                statusText = String.format(
179                                        getString(R.string.uninstall_failed_device_policy_manager_of_user),
180                                        otherBlockingUser.name);
181                            }
182                            break;
183                        }
184                        case PackageManager.DELETE_FAILED_OWNER_BLOCKED: {
185                            UserManager userManager =
186                                    (UserManager) getSystemService(Context.USER_SERVICE);
187                            IPackageManager packageManager = IPackageManager.Stub.asInterface(
188                                    ServiceManager.getService("package"));
189                            List<UserInfo> users = userManager.getUsers();
190                            int blockingUserId = UserHandle.USER_NULL;
191                            for (int i = 0; i < users.size(); ++i) {
192                                final UserInfo user = users.get(i);
193                                try {
194                                    if (packageManager.getBlockUninstallForUser(packageName,
195                                            user.id)) {
196                                        blockingUserId = user.id;
197                                        break;
198                                    }
199                                } catch (RemoteException e) {
200                                    // Shouldn't happen.
201                                    Log.e(TAG, "Failed to talk to package manager", e);
202                                }
203                            }
204                            int myUserId = UserHandle.myUserId();
205                            if (isProfileOfOrSame(userManager, myUserId, blockingUserId)) {
206                                mDeviceManagerButton.setVisibility(View.VISIBLE);
207                            } else {
208                                mDeviceManagerButton.setVisibility(View.GONE);
209                                mUsersButton.setVisibility(View.VISIBLE);
210                            }
211                            // TODO: b/25442806
212                            if (blockingUserId == UserHandle.USER_SYSTEM) {
213                                statusText = getString(R.string.uninstall_blocked_device_owner);
214                            } else if (blockingUserId == UserHandle.USER_NULL) {
215                                Log.d(TAG, "Uninstall failed for " + packageName + " with code "
216                                        + msg.arg1 + " no blocking user");
217                                statusText = getString(R.string.uninstall_failed);
218                            } else {
219                                statusText = mAllUsers
220                                        ? getString(R.string.uninstall_all_blocked_profile_owner) :
221                                        getString(R.string.uninstall_blocked_profile_owner);
222                            }
223                            break;
224                        }
225                        default:
226                            Log.d(TAG, "Uninstall failed for " + packageName + " with code "
227                                    + msg.arg1);
228                            statusText = getString(R.string.uninstall_failed);
229                            break;
230                    }
231                    findViewById(R.id.progress_view).setVisibility(View.GONE);
232                    findViewById(R.id.status_view).setVisibility(View.VISIBLE);
233                    ((TextView)findViewById(R.id.status_text)).setText(statusText);
234                    findViewById(R.id.ok_panel).setVisibility(View.VISIBLE);
235                    break;
236                default:
237                    break;
238            }
239        }
240    };
241
242    @Override
243    public void onCreate(Bundle icicle) {
244        super.onCreate(icicle);
245
246        Intent intent = getIntent();
247        mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
248        mCallback = intent.getIBinderExtra(PackageInstaller.EXTRA_CALLBACK);
249
250        // This currently does not support going through a onDestroy->onCreate cycle. Hence if that
251        // happened, just fail the operation for mysterious reasons.
252        if (icicle != null) {
253            mResultCode = PackageManager.DELETE_FAILED_INTERNAL_ERROR;
254
255            if (mCallback != null) {
256                final IPackageDeleteObserver2 observer = IPackageDeleteObserver2.Stub
257                        .asInterface(mCallback);
258                try {
259                    observer.onPackageDeleted(mAppInfo.packageName, mResultCode, null);
260                } catch (RemoteException ignored) {
261                }
262                finish();
263            } else {
264                setResultAndFinish(mResultCode);
265            }
266
267            return;
268        }
269
270        mAllUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false);
271        if (mAllUsers && !UserManager.get(this).isAdminUser()) {
272            throw new SecurityException("Only admin user can request uninstall for all users");
273        }
274        mUser = intent.getParcelableExtra(Intent.EXTRA_USER);
275        if (mUser == null) {
276            mUser = android.os.Process.myUserHandle();
277        } else {
278            UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
279            List<UserHandle> profiles = userManager.getUserProfiles();
280            if (!profiles.contains(mUser)) {
281                throw new SecurityException("User " + android.os.Process.myUserHandle() + " can't "
282                        + "request uninstall for user " + mUser);
283            }
284        }
285
286        PackageDeleteObserver observer = new PackageDeleteObserver();
287
288        // Make window transparent until initView is called. In many cases we can avoid showing the
289        // UI at all as the app is uninstalled very quickly. If we show the UI and instantly remove
290        // it, it just looks like a flicker.
291        getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
292        getWindow().setStatusBarColor(Color.TRANSPARENT);
293        getWindow().setNavigationBarColor(Color.TRANSPARENT);
294
295        getPackageManager().deletePackageAsUser(mAppInfo.packageName, observer,
296                mAllUsers ? PackageManager.DELETE_ALL_USERS : 0, mUser.getIdentifier());
297
298        mHandler.sendMessageDelayed(mHandler.obtainMessage(UNINSTALL_IS_SLOW),
299                QUICK_INSTALL_DELAY_MILLIS);
300    }
301
302    class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
303        public void packageDeleted(String packageName, int returnCode) {
304            Message msg = mHandler.obtainMessage(UNINSTALL_COMPLETE);
305            msg.arg1 = returnCode;
306            msg.obj = packageName;
307            mHandler.sendMessage(msg);
308        }
309    }
310
311    void setResultAndFinish(int retCode) {
312        setResult(retCode);
313        finish();
314    }
315
316    public void initView() {
317        if (mIsViewInitialized) {
318            return;
319        }
320        mIsViewInitialized = true;
321
322        // We set the window background to translucent in constructor, revert this
323        TypedValue attribute = new TypedValue();
324        getTheme().resolveAttribute(android.R.attr.windowBackground, attribute, true);
325        if (attribute.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
326                attribute.type <= TypedValue.TYPE_LAST_COLOR_INT) {
327            getWindow().setBackgroundDrawable(new ColorDrawable(attribute.data));
328        } else {
329            getWindow().setBackgroundDrawable(getResources().getDrawable(attribute.resourceId,
330                    getTheme()));
331        }
332
333        getTheme().resolveAttribute(android.R.attr.navigationBarColor, attribute, true);
334        getWindow().setNavigationBarColor(attribute.data);
335
336        getTheme().resolveAttribute(android.R.attr.statusBarColor, attribute, true);
337        getWindow().setStatusBarColor(attribute.data);
338
339        boolean isUpdate = ((mAppInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
340        setTitle(isUpdate ? R.string.uninstall_update_title : R.string.uninstall_application_title);
341
342        setContentView(R.layout.uninstall_progress);
343        // Initialize views
344        View snippetView = findViewById(R.id.app_snippet);
345        PackageUtil.initSnippetForInstalledApp(this, mAppInfo, snippetView);
346        mDeviceManagerButton = (Button) findViewById(R.id.device_manager_button);
347        mUsersButton = (Button) findViewById(R.id.users_button);
348        mDeviceManagerButton.setVisibility(View.GONE);
349        mDeviceManagerButton.setOnClickListener(new OnClickListener() {
350            @Override
351            public void onClick(View v) {
352                Intent intent = new Intent();
353                intent.setClassName("com.android.settings",
354                        "com.android.settings.Settings$DeviceAdminSettingsActivity");
355                intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK);
356                startActivity(intent);
357                finish();
358            }
359        });
360        mUsersButton.setVisibility(View.GONE);
361        mUsersButton.setOnClickListener(new OnClickListener() {
362            @Override
363            public void onClick(View v) {
364                Intent intent = new Intent(Settings.ACTION_USER_SETTINGS);
365                intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK);
366                startActivity(intent);
367                finish();
368            }
369        });
370        // Hide button till progress is being displayed
371        mOkButton = (Button) findViewById(R.id.ok_button);
372        mOkButton.setOnClickListener(this);
373    }
374
375    public void onClick(View v) {
376        if(v == mOkButton) {
377            Log.i(TAG, "Finished uninstalling pkg: " + mAppInfo.packageName);
378            setResultAndFinish(mResultCode);
379        }
380    }
381
382    @Override
383    public boolean dispatchKeyEvent(KeyEvent ev) {
384        if (ev.getKeyCode() == KeyEvent.KEYCODE_BACK) {
385            if (mResultCode == -1) {
386                // Ignore back key when installation is in progress
387                return true;
388            } else {
389                // If installation is done, just set the result code
390                setResult(mResultCode);
391            }
392        }
393        return super.dispatchKeyEvent(ev);
394    }
395}
396