blob: 95f1bd9b85c379af9d4492bddfc0cdc497f425a0 [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tv.settings.device.apps;
import android.app.Fragment;
import android.app.FragmentManager;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.util.Log;
import android.widget.Toast;
import com.android.tv.settings.ActionBehavior;
import com.android.tv.settings.ActionKey;
import com.android.tv.settings.R;
import com.android.tv.settings.SettingsConstant;
import com.android.tv.settings.device.storage.MoveAppProgressFragment;
import com.android.tv.settings.device.storage.MoveAppStepFragment;
import com.android.tv.settings.dialog.old.Action;
import com.android.tv.settings.dialog.old.ActionAdapter;
import com.android.tv.settings.dialog.old.ActionFragment;
import com.android.tv.settings.dialog.old.ContentFragment;
import com.android.tv.settings.dialog.old.DialogActivity;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
/**
* Activity that manages an apps.
*/
public class AppManagementActivity extends DialogActivity implements ActionAdapter.Listener,
ApplicationsState.Callbacks, DataClearer.Listener, CacheClearer.Listener,
DefaultClearer.Listener, MoveAppStepFragment.Callback {
private static final String TAG = "AppManagementActivity";
private static final String DIALOG_BACKSTACK_TAG = "storageUsed";
private static final String SAVE_STATE_MOVE_ID = "AppManagementActivity.moveId";
// Result code identifiers
private static final int REQUEST_UNINSTALL = 1;
private static final int REQUEST_MANAGE_SPACE = 2;
private PackageManager mPackageManager;
private StorageManager mStorageManager;
private String mPackageName;
private ApplicationsState mApplicationsState;
private ApplicationsState.Session mSession;
private AppInfo mAppInfo;
private OpenManager mOpenManager;
private ForceStopManager mForceStopManager;
private UninstallManager mUninstallManager;
private NotificationSetter mNotificationSetter;
private DataClearer mDataClearer;
private DefaultClearer mDefaultClearer;
private CacheClearer mCacheClearer;
private ActionFragment mActionFragment;
private int mAppMoveId;
private final PackageManager.MoveCallback mMoveCallback = new PackageManager.MoveCallback() {
@Override
public void onStatusChanged(int moveId, int status, long estMillis) {
if (moveId != mAppMoveId || !PackageManager.isMoveStatusFinished(status)) {
return;
}
getFragmentManager().popBackStack(DIALOG_BACKSTACK_TAG,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
// TODO: this isn't quite enough to refresh the UI
mApplicationsState.invalidatePackage(mPackageName);
updateActions();
if (status != PackageManager.MOVE_SUCCEEDED) {
Log.d(TAG, "Move failure status: " + status);
Toast.makeText(AppManagementActivity.this,
MoveAppProgressFragment.moveStatusToMessage(AppManagementActivity.this,
status),
Toast.LENGTH_LONG).show();
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPackageManager = getPackageManager();
mStorageManager = getSystemService(StorageManager.class);
final Uri uri = getIntent().getData();
if (uri == null) {
Log.e(TAG, "No app to inspect (missing data uri in intent)");
finish();
return;
}
mPackageName = uri.getSchemeSpecificPart();
mApplicationsState = ApplicationsState.getInstance(getApplication());
mSession = mApplicationsState.newSession(this);
mSession.resume();
mAppInfo = new AppInfo(this, mApplicationsState.getEntry(mPackageName));
mOpenManager = new OpenManager(this, mAppInfo);
mForceStopManager = new ForceStopManager(this, mAppInfo);
mUninstallManager = new UninstallManager(this, mAppInfo);
mNotificationSetter = new NotificationSetter(mAppInfo);
mDataClearer = new DataClearer(this, mAppInfo);
mDefaultClearer = new DefaultClearer(this, mAppInfo);
mCacheClearer = new CacheClearer(this, mAppInfo);
mActionFragment = ActionFragment.newInstance(getActions());
mAppMoveId = savedInstanceState != null ?
savedInstanceState.getInt(SAVE_STATE_MOVE_ID) : -1;
setContentAndActionFragments(ContentFragment.newInstance(mAppInfo.getName(),
getString(R.string.device_apps),
getString(R.string.device_apps_app_management_version, mAppInfo.getVersion()),
Uri.parse(AppsBrowseInfo.getAppIconUri(this, mAppInfo)),
getColor(R.color.icon_background)), mActionFragment);
mPackageManager.registerMoveCallback(mMoveCallback, new Handler());
}
@Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putInt(SAVE_STATE_MOVE_ID, mAppMoveId);
}
@Override
protected void onDestroy() {
super.onDestroy();
mPackageManager.unregisterMoveCallback(mMoveCallback);
}
public static Intent getLaunchIntent(String packageName) {
Intent i = new Intent();
i.setComponent(new ComponentName(SettingsConstant.PACKAGE,
SettingsConstant.PACKAGE + ".device.apps.AppManagementActivity"));
i.setData(Uri.parse("package:" + packageName));
return i;
}
private void refreshUpdateActions() {
mApplicationsState = ApplicationsState.getInstance(getApplication());
mAppInfo = new AppInfo(this, mApplicationsState.getEntry(mPackageName));
mUninstallManager = new UninstallManager(this, mAppInfo);
updateActions();
}
static class DisableChanger extends AsyncTask<Void, Void, Void> {
final PackageManager mPm;
final WeakReference<AppManagementActivity> mActivity;
final ApplicationInfo mInfo;
final int mState;
DisableChanger(AppManagementActivity activity, ApplicationInfo info, int state) {
mPm = activity.getPackageManager();
mActivity = new WeakReference<>(activity);
mInfo = info;
mState = state;
}
@Override
protected Void doInBackground(Void... params) {
mPm.setApplicationEnabledSetting(mInfo.packageName, mState, 0);
return null;
}
@Override
protected void onPostExecute(Void result) {
AppManagementActivity activity = mActivity.get();
if (activity != null) {
activity.refreshUpdateActions();
}
}
}
@Override
public void onActionClicked(Action action) {
ActionKey<ActionType, ActionBehavior> actionKey = new ActionKey<>(
ActionType.class, ActionBehavior.class, action.getKey());
ActionType actionType = actionKey.getType();
switch (actionKey.getBehavior()) {
case INIT:
onInit(actionType, action);
break;
case OK:
onOk(actionType);
break;
case CANCEL:
onCancel(actionType);
break;
case ON:
onOn(actionType);
break;
case OFF:
onOff(actionType);
break;
default:
break;
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case REQUEST_UNINSTALL:
if (resultCode == RESULT_OK) {
mApplicationsState.removePackage(mPackageName);
goToAppSelectScreen();
}
break;
case REQUEST_MANAGE_SPACE:
mDataClearer.onActivityResult(resultCode);
break;
}
}
@Override
public void onRunningStateChanged(boolean running) {
}
@Override
public void onPackageListChanged() {
}
@Override
public void onRebuildComplete() {
}
@Override
public void onPackageIconChanged() {
}
@Override
public void onPackageSizeChanged(String packageName) {
}
@Override
public void onAllSizesComputed() {
updateActions();
}
@Override
public void dataCleared(boolean succeeded) {
if (succeeded) {
mApplicationsState.requestSize(mPackageName);
} else {
Log.w(TAG, "Failed to clear data!");
updateActions();
}
}
@Override
public void defaultCleared() {
updateActions();
}
@Override
public void cacheCleared(boolean succeeded) {
if (succeeded) {
mApplicationsState.requestSize(mPackageName);
} else {
Log.w(TAG, "Failed to clear cache!");
updateActions();
}
}
private void onInit(ActionType actionType, Action action) {
switch (actionType) {
case OPEN:
onOpen();
break;
case PERMISSIONS:
startManagePermissionsActivity();
break;
case NOTIFICATIONS:
setContentAndActionFragments(createContentFragment(actionType,
action), ActionFragment.newInstance(actionType.toSelectableActions(
getResources(),
(mNotificationSetter.areNotificationsOn()) ? ActionBehavior.ON
: ActionBehavior.OFF)));
break;
case STORAGE_USED:
startDialogFragment(MoveAppStepFragment.newInstance(mPackageName,
mAppInfo.getName()));
break;
default:
setContentAndActionFragments(createContentFragment(actionType, action),
ActionFragment.newInstance(actionType.toActions(getResources())));
break;
}
}
private void startManagePermissionsActivity() {
// start new activity to manage app permissions
Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS);
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mPackageName);
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
Log.w(TAG, "No app can handle android.intent.action.MANAGE_APP_PERMISSIONS");
}
}
private ContentFragment createContentFragment(ActionType actionType, Action action) {
String description = actionType.getDesc(getResources());
String description2 = actionType.getDesc2(getResources());
String descriptionToUse = null;
if (description != null) {
if (description2 != null) {
descriptionToUse = description + "\n" + description2;
} else {
descriptionToUse = description;
}
} else if (description2 != null) {
descriptionToUse = description2;
}
return ContentFragment.newInstance(action.getTitle(), mAppInfo.getName(), descriptionToUse,
Uri.parse(AppsBrowseInfo.getAppIconUri(this, mAppInfo)),
getColor(R.color.icon_background));
}
private void onOk(ActionType actionType) {
switch (actionType) {
case CLEAR_CACHE:
onClearCacheOk();
break;
case CLEAR_DATA:
onClearDataOk();
break;
case CLEAR_DEFAULTS:
onClearDefaultOk();
break;
case FORCE_STOP:
onForceStopOk();
break;
case UNINSTALL:
onUninstallOk();
break;
case DISABLE:
onDisableOk();
break;
case ENABLE:
onEnableOk();
break;
default:
break;
}
}
private void onCancel(ActionType actionType) {
goToActionSelectScreen();
}
private void onOn(ActionType actionType) {
switch (actionType) {
case NOTIFICATIONS:
onNotificationsOn();
break;
default:
break;
}
}
private void onOff(ActionType actionType) {
switch (actionType) {
case NOTIFICATIONS:
onNotificationsOff();
break;
default:
break;
}
}
private void onUninstallOk() {
mUninstallManager.uninstall(REQUEST_UNINSTALL);
}
private void onDisableOk() {
new DisableChanger(this, mAppInfo.getApplicationInfo(),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER).execute();
goToActionSelectScreen();
}
private void onEnableOk() {
new DisableChanger(this, mAppInfo.getApplicationInfo(),
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT).execute();
goToActionSelectScreen();
}
private void onNotificationsOn() {
if (!mNotificationSetter.enableNotifications()) {
Log.w(TAG, "Failed to enable notifications!");
}
goToActionSelectScreen();
}
private void onNotificationsOff() {
if (!mNotificationSetter.disableNotifications()) {
Log.w(TAG, "Failed to disable notifications!");
}
goToActionSelectScreen();
}
private void onOpen() {
mOpenManager.open(mApplicationsState);
// TODO: figure out what to do here
}
private void onForceStopOk() {
mForceStopManager.forceStop(mApplicationsState);
goToActionSelectScreen();
}
private void onClearDataOk() {
mDataClearer.clearData(this, REQUEST_MANAGE_SPACE);
goToActionSelectScreen();
}
private void onClearDefaultOk() {
mDefaultClearer.clearDefault(this);
goToActionSelectScreen();
}
private void onClearCacheOk() {
mCacheClearer.clearCache(this);
goToActionSelectScreen();
}
private void goToActionSelectScreen() {
updateActions();
getFragmentManager().popBackStack(null, 0);
}
private void goToAppSelectScreen() {
finish();
}
private ArrayList<Action> getActions() {
ArrayList<Action> actions = new ArrayList<>();
final Resources res = getResources();
if (mOpenManager.canOpen()) {
actions.add(ActionType.OPEN.toInitAction(res));
}
if (mForceStopManager.canForceStop()) {
actions.add(ActionType.FORCE_STOP.toInitAction(res));
}
if (mUninstallManager.canUninstall()) {
actions.add(ActionType.UNINSTALL.toInitAction(res));
} else {
// App is on system partition.
if (mUninstallManager.canDisable()) {
if (mUninstallManager.isEnabled()) {
actions.add(ActionType.DISABLE.toInitAction(res));
} else {
actions.add(ActionType.ENABLE.toInitAction(res));
}
}
}
actions.add(ActionType.STORAGE_USED.toInitAction(res, getStorageDescription()));
actions.add( ActionType.CLEAR_DATA.toInitAction(res, mDataClearer.getDataSize(this)));
actions.add(ActionType.CLEAR_CACHE.toInitAction(res, mCacheClearer.getCacheSize(this)));
actions.add(ActionType.CLEAR_DEFAULTS.toInitAction(res,
mDefaultClearer.getDescription(this)));
actions.add(ActionType.NOTIFICATIONS.toInitAction(res,
getString((mNotificationSetter.areNotificationsOn()) ? R.string.settings_on
: R.string.settings_off)));
actions.add(ActionType.PERMISSIONS.toInitAction(res));
return actions;
}
private String getStorageDescription() {
final ApplicationInfo applicationInfo = mAppInfo.getApplicationInfo();
final VolumeInfo volumeInfo = mPackageManager.getPackageCurrentVolume(applicationInfo);
final String volumeDesc = mStorageManager.getBestVolumeDescription(volumeInfo);
return getString(R.string.device_apps_app_management_storage_used_desc, mAppInfo.getSize(),
volumeDesc);
}
private void updateActions() {
((ActionAdapter) mActionFragment.getAdapter()).setActions(getActions());
}
@Override
public void onRequestMovePackageToVolume(String packageName, VolumeInfo destination) {
// Kick off the move
mAppMoveId = mPackageManager.movePackage(packageName, destination);
// Show the progress dialog
startDialogFragment(MoveAppProgressFragment.newInstance(mAppInfo.getName()));
}
private void startDialogFragment(Fragment fragment) {
// Get rid of any previous wizard screen(s)
getFragmentManager().popBackStack(DIALOG_BACKSTACK_TAG,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
// Replace it with the progress screen
getFragmentManager().beginTransaction()
.addToBackStack(DIALOG_BACKSTACK_TAG)
.hide(getDialogFragment())
.hide(getContentFragment())
.hide(getActionFragment())
.add(android.R.id.content, fragment)
.commit();
}
}