1/*
2 * Copyright (C) 2015 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.tv.settings.device.apps;
18
19import android.app.Activity;
20import android.app.ActivityManager;
21import android.content.ActivityNotFoundException;
22import android.content.Context;
23import android.content.Intent;
24import android.content.pm.IPackageDataObserver;
25import android.content.pm.PackageManager;
26import android.graphics.drawable.Drawable;
27import android.os.Bundle;
28import android.os.Handler;
29import android.os.UserHandle;
30import android.support.annotation.NonNull;
31import android.support.v17.preference.LeanbackPreferenceFragment;
32import android.support.v7.preference.Preference;
33import android.support.v7.preference.PreferenceScreen;
34import android.text.TextUtils;
35import android.util.Log;
36import android.widget.Toast;
37
38import com.android.settingslib.applications.ApplicationsState;
39import com.android.tv.settings.R;
40
41import java.util.ArrayList;
42
43public class AppManagementFragment extends LeanbackPreferenceFragment {
44    private static final String TAG = "AppManagementFragment";
45
46    private static final String ARG_PACKAGE_NAME = "packageName";
47
48    private static final String KEY_VERSION = "version";
49    private static final String KEY_OPEN = "open";
50    private static final String KEY_FORCE_STOP = "forceStop";
51    private static final String KEY_UNINSTALL = "uninstall";
52    private static final String KEY_ENABLE_DISABLE = "enableDisable";
53    private static final String KEY_APP_STORAGE = "appStorage";
54    private static final String KEY_CLEAR_DATA = "clearData";
55    private static final String KEY_CLEAR_CACHE = "clearCache";
56    private static final String KEY_CLEAR_DEFAULTS = "clearDefaults";
57    private static final String KEY_NOTIFICATIONS = "notifications";
58    private static final String KEY_PERMISSIONS = "permissions";
59
60    // Result code identifiers
61    private static final int REQUEST_UNINSTALL = 1;
62    private static final int REQUEST_MANAGE_SPACE = 2;
63    private static final int REQUEST_UNINSTALL_UPDATES = 3;
64
65    private PackageManager mPackageManager;
66    private String mPackageName;
67    private ApplicationsState mApplicationsState;
68    private ApplicationsState.Session mSession;
69    private ApplicationsState.AppEntry mEntry;
70    private final ApplicationsState.Callbacks mCallbacks = new ApplicationsStateCallbacks();
71
72    private ForceStopPreference mForceStopPreference;
73    private UninstallPreference mUninstallPreference;
74    private EnableDisablePreference mEnableDisablePreference;
75    private AppStoragePreference mAppStoragePreference;
76    private ClearDataPreference mClearDataPreference;
77    private ClearCachePreference mClearCachePreference;
78    private ClearDefaultsPreference mClearDefaultsPreference;
79    private NotificationsPreference mNotificationsPreference;
80
81    private final Handler mHandler = new Handler();
82    private Runnable mBailoutRunnable = new Runnable() {
83        @Override
84        public void run() {
85            if (isResumed() && !getFragmentManager().popBackStackImmediate()) {
86                getActivity().onBackPressed();
87            }
88        }
89    };
90
91    public static void prepareArgs(@NonNull Bundle args, String packageName) {
92        args.putString(ARG_PACKAGE_NAME, packageName);
93    }
94
95    @Override
96    public void onCreate(Bundle savedInstanceState) {
97        mPackageName = getArguments().getString(ARG_PACKAGE_NAME);
98
99        final Activity activity = getActivity();
100        mPackageManager = activity.getPackageManager();
101        mApplicationsState = ApplicationsState.getInstance(activity.getApplication());
102        mSession = mApplicationsState.newSession(mCallbacks);
103        mEntry = mApplicationsState.getEntry(mPackageName, UserHandle.myUserId());
104
105        super.onCreate(savedInstanceState);
106    }
107
108    @Override
109    public void onResume() {
110        super.onResume();
111        mSession.resume();
112
113        if (mEntry == null) {
114            Log.w(TAG, "App not found, trying to bail out");
115            navigateBack();
116        }
117
118        if (mClearDefaultsPreference != null) {
119            mClearDefaultsPreference.refresh();
120        }
121        if (mEnableDisablePreference != null) {
122            mEnableDisablePreference.refresh();
123        }
124    }
125
126    @Override
127    public void onPause() {
128        super.onPause();
129        mSession.pause();
130        mHandler.removeCallbacks(mBailoutRunnable);
131    }
132
133    @Override
134    public void onActivityResult(int requestCode, int resultCode, Intent data) {
135        super.onActivityResult(requestCode, resultCode, data);
136        if (mEntry == null) {
137            return;
138        }
139        switch (requestCode) {
140            case REQUEST_UNINSTALL:
141                final int deleteResult = data != null
142                        ? data.getIntExtra(Intent.EXTRA_INSTALL_RESULT, 0) : 0;
143                if (deleteResult == PackageManager.DELETE_SUCCEEDED) {
144                    final int userId =  UserHandle.getUserId(mEntry.info.uid);
145                    mApplicationsState.removePackage(mPackageName, userId);
146                    navigateBack();
147                } else {
148                    Log.e(TAG, "Uninstall failed with result " + deleteResult);
149                }
150                break;
151            case REQUEST_MANAGE_SPACE:
152                mClearDataPreference.setClearingData(false);
153                if(resultCode == Activity.RESULT_OK) {
154                    final int userId = UserHandle.getUserId(mEntry.info.uid);
155                    mApplicationsState.requestSize(mPackageName, userId);
156                } else {
157                    Log.w(TAG, "Failed to clear data!");
158                }
159                break;
160            case REQUEST_UNINSTALL_UPDATES:
161                mUninstallPreference.refresh();
162                break;
163        }
164    }
165
166    private void navigateBack() {
167        // need to post this to avoid recursing in the fragment manager.
168        mHandler.removeCallbacks(mBailoutRunnable);
169        mHandler.post(mBailoutRunnable);
170    }
171
172    @Override
173    public boolean onPreferenceTreeClick(Preference preference) {
174        final Intent intent = preference.getIntent();
175        if (intent != null) {
176            try {
177                if (preference.equals(mUninstallPreference)) {
178                    startActivityForResult(intent, mUninstallPreference.canUninstall()
179                            ? REQUEST_UNINSTALL : REQUEST_UNINSTALL_UPDATES);
180                } else {
181                    startActivity(intent);
182                }
183            } catch (ActivityNotFoundException e) {
184                Log.e(TAG, "Could not find activity to launch", e);
185                Toast.makeText(getContext(), R.string.device_apps_app_management_not_available,
186                        Toast.LENGTH_SHORT).show();
187            }
188            return true;
189        } else {
190            return super.onPreferenceTreeClick(preference);
191        }
192    }
193
194    @Override
195    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
196        final Context themedContext = getPreferenceManager().getContext();
197        final PreferenceScreen screen =
198                getPreferenceManager().createPreferenceScreen(themedContext);
199        screen.setTitle(getAppName());
200        setPreferenceScreen(screen);
201
202        updatePrefs();
203    }
204
205    private void updatePrefs() {
206        if (mEntry == null) {
207            final PreferenceScreen screen = getPreferenceScreen();
208            screen.removeAll();
209            return;
210        }
211        final Context themedContext = getPreferenceManager().getContext();
212
213        // Version
214        Preference versionPreference = findPreference(KEY_VERSION);
215        if (versionPreference == null) {
216            versionPreference = new Preference(themedContext);
217            versionPreference.setKey(KEY_VERSION);
218            replacePreference(versionPreference);
219            versionPreference.setSelectable(false);
220        }
221        versionPreference.setTitle(getString(R.string.device_apps_app_management_version,
222                mEntry.getVersion(getActivity())));
223        versionPreference.setSummary(mPackageName);
224
225        // Open
226        Preference openPreference = findPreference(KEY_OPEN);
227        if (openPreference == null) {
228            openPreference = new Preference(themedContext);
229            openPreference.setKey(KEY_OPEN);
230            replacePreference(openPreference);
231        }
232        Intent appLaunchIntent =
233                mPackageManager.getLeanbackLaunchIntentForPackage(mEntry.info.packageName);
234        if (appLaunchIntent == null) {
235            appLaunchIntent = mPackageManager.getLaunchIntentForPackage(mEntry.info.packageName);
236        }
237        if (appLaunchIntent != null) {
238            openPreference.setIntent(appLaunchIntent);
239            openPreference.setTitle(R.string.device_apps_app_management_open);
240            openPreference.setVisible(true);
241        } else {
242            openPreference.setVisible(false);
243        }
244
245        // Force stop
246        if (mForceStopPreference == null) {
247            mForceStopPreference = new ForceStopPreference(themedContext, mEntry);
248            mForceStopPreference.setKey(KEY_FORCE_STOP);
249            replacePreference(mForceStopPreference);
250        } else {
251            mForceStopPreference.setEntry(mEntry);
252        }
253
254        // Uninstall
255        if (mUninstallPreference == null) {
256            mUninstallPreference = new UninstallPreference(themedContext, mEntry);
257            mUninstallPreference.setKey(KEY_UNINSTALL);
258            replacePreference(mUninstallPreference);
259        } else {
260            mUninstallPreference.setEntry(mEntry);
261        }
262
263        // Disable/Enable
264        if (mEnableDisablePreference == null) {
265            mEnableDisablePreference = new EnableDisablePreference(themedContext, mEntry);
266            mEnableDisablePreference.setKey(KEY_ENABLE_DISABLE);
267            replacePreference(mEnableDisablePreference);
268        } else {
269            mEnableDisablePreference.setEntry(mEntry);
270        }
271
272        // Storage used
273        if (mAppStoragePreference == null) {
274            mAppStoragePreference = new AppStoragePreference(themedContext, mEntry);
275            mAppStoragePreference.setKey(KEY_APP_STORAGE);
276            replacePreference(mAppStoragePreference);
277        } else {
278            mAppStoragePreference.setEntry(mEntry);
279        }
280
281        // Clear data
282        if (mClearDataPreference == null) {
283            mClearDataPreference = new ClearDataPreference(themedContext, mEntry);
284            mClearDataPreference.setKey(KEY_CLEAR_DATA);
285            replacePreference(mClearDataPreference);
286        } else {
287            mClearDataPreference.setEntry(mEntry);
288        }
289
290        // Clear cache
291        if (mClearCachePreference == null) {
292            mClearCachePreference = new ClearCachePreference(themedContext, mEntry);
293            mClearCachePreference.setKey(KEY_CLEAR_CACHE);
294            replacePreference(mClearCachePreference);
295        } else {
296            mClearCachePreference.setEntry(mEntry);
297        }
298
299        // Clear defaults
300        if (mClearDefaultsPreference == null) {
301            mClearDefaultsPreference = new ClearDefaultsPreference(themedContext, mEntry);
302            mClearDefaultsPreference.setKey(KEY_CLEAR_DEFAULTS);
303            replacePreference(mClearDefaultsPreference);
304        } else {
305            mClearDefaultsPreference.setEntry(mEntry);
306        }
307
308        // Notifications
309        if (mNotificationsPreference == null) {
310            mNotificationsPreference = new NotificationsPreference(themedContext, mEntry);
311            mNotificationsPreference.setKey(KEY_NOTIFICATIONS);
312            replacePreference(mNotificationsPreference);
313        } else {
314            mNotificationsPreference.setEntry(mEntry);
315        }
316
317        // Permissions
318        Preference permissionsPreference = findPreference(KEY_PERMISSIONS);
319        if (permissionsPreference == null) {
320            permissionsPreference = new Preference(themedContext);
321            permissionsPreference.setKey(KEY_PERMISSIONS);
322            permissionsPreference.setTitle(R.string.device_apps_app_management_permissions);
323            replacePreference(permissionsPreference);
324        }
325        permissionsPreference.setIntent(new Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS)
326                .putExtra(Intent.EXTRA_PACKAGE_NAME, mPackageName));
327    }
328
329    private void replacePreference(Preference preference) {
330        final String key = preference.getKey();
331        if (TextUtils.isEmpty(key)) {
332            throw new IllegalArgumentException("Can't replace a preference without a key");
333        }
334        final Preference old = findPreference(key);
335        if (old != null) {
336            getPreferenceScreen().removePreference(old);
337        }
338        getPreferenceScreen().addPreference(preference);
339    }
340
341    public String getAppName() {
342        if (mEntry == null) {
343            return null;
344        }
345        mEntry.ensureLabel(getActivity());
346        return mEntry.label;
347    }
348
349    public Drawable getAppIcon() {
350        if (mEntry == null) {
351            return null;
352        }
353        mApplicationsState.ensureIcon(mEntry);
354        return mEntry.icon;
355    }
356
357    public void clearData() {
358        mClearDataPreference.setClearingData(true);
359        String spaceManagementActivityName = mEntry.info.manageSpaceActivityName;
360        if (spaceManagementActivityName != null) {
361            if (!ActivityManager.isUserAMonkey()) {
362                Intent intent = new Intent(Intent.ACTION_DEFAULT);
363                intent.setClassName(mEntry.info.packageName, spaceManagementActivityName);
364                startActivityForResult(intent, REQUEST_MANAGE_SPACE);
365            }
366        } else {
367            ActivityManager am = (ActivityManager) getActivity().getSystemService(
368                    Context.ACTIVITY_SERVICE);
369            boolean success = am.clearApplicationUserData(
370                    mEntry.info.packageName, new IPackageDataObserver.Stub() {
371                        public void onRemoveCompleted(
372                                final String packageName, final boolean succeeded) {
373                            mHandler.post(new Runnable() {
374                                @Override
375                                public void run() {
376                                    mClearDataPreference.setClearingData(false);
377                                    if (succeeded) {
378                                        dataCleared(true);
379                                    } else {
380                                        dataCleared(false);
381                                    }
382                                }
383                            });
384                        }
385                    });
386            if (!success) {
387                mClearDataPreference.setClearingData(false);
388                dataCleared(false);
389            }
390        }
391        mClearDataPreference.refresh();
392    }
393
394    private void dataCleared(boolean succeeded) {
395        if (succeeded) {
396            final int userId =  UserHandle.getUserId(mEntry.info.uid);
397            mApplicationsState.requestSize(mPackageName, userId);
398        } else {
399            Log.w(TAG, "Failed to clear data!");
400            mClearDataPreference.refresh();
401        }
402    }
403
404    public void clearCache() {
405        mClearCachePreference.setClearingCache(true);
406        mPackageManager.deleteApplicationCacheFiles(mEntry.info.packageName,
407                new IPackageDataObserver.Stub() {
408                    public void onRemoveCompleted(final String packageName,
409                            final boolean succeeded) {
410                        mHandler.post(new Runnable() {
411                            @Override
412                            public void run() {
413                                mClearCachePreference.setClearingCache(false);
414                                cacheCleared(succeeded);
415                            }
416                        });
417                    }
418                });
419        mClearCachePreference.refresh();
420    }
421
422    private void cacheCleared(boolean succeeded) {
423        if (succeeded) {
424            final int userId =  UserHandle.getUserId(mEntry.info.uid);
425            mApplicationsState.requestSize(mPackageName, userId);
426        } else {
427            Log.w(TAG, "Failed to clear cache!");
428            mClearCachePreference.refresh();
429        }
430    }
431
432    private class ApplicationsStateCallbacks implements ApplicationsState.Callbacks {
433
434        @Override
435        public void onRunningStateChanged(boolean running) {
436            if (mForceStopPreference != null) {
437                mForceStopPreference.refresh();
438            }
439        }
440
441        @Override
442        public void onPackageListChanged() {
443            if (mEntry == null) {
444                return;
445            }
446            final int userId = UserHandle.getUserId(mEntry.info.uid);
447            mEntry = mApplicationsState.getEntry(mPackageName, userId);
448            if (mEntry == null) {
449                navigateBack();
450            }
451            updatePrefs();
452        }
453
454        @Override
455        public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {}
456
457        @Override
458        public void onPackageIconChanged() {}
459
460        @Override
461        public void onPackageSizeChanged(String packageName) {
462            if (mAppStoragePreference == null) {
463                // Nothing to do here.
464                return;
465            }
466            mAppStoragePreference.refresh();
467            mClearDataPreference.refresh();
468            mClearCachePreference.refresh();
469        }
470
471        @Override
472        public void onAllSizesComputed() {
473            if (mAppStoragePreference == null) {
474                // Nothing to do here.
475                return;
476            }
477            mAppStoragePreference.refresh();
478            mClearDataPreference.refresh();
479            mClearCachePreference.refresh();
480        }
481
482        @Override
483        public void onLauncherInfoChanged() {
484            updatePrefs();
485        }
486
487        @Override
488        public void onLoadEntriesCompleted() {
489            if (mAppStoragePreference == null) {
490                // Nothing to do here.
491                return;
492            }
493            mAppStoragePreference.refresh();
494            mClearDataPreference.refresh();
495            mClearCachePreference.refresh();
496        }
497    }
498}
499