Merge "Move packages between candidate volumes."

This commit is contained in:
Jeff Sharkey
2015-04-16 05:50:13 +00:00
committed by Android (Google) Code Review
7 changed files with 246 additions and 182 deletions

View File

@@ -36,7 +36,7 @@
<ListView android:id="@android:id/list"
style="@style/PreferenceFragmentListSinglePane"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:paddingStart="@dimen/settings_side_margin"
android:paddingEnd="@dimen/settings_side_margin"
android:paddingTop="@dimen/dashboard_padding_top"

View File

@@ -23,13 +23,12 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageMoveObserver;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.text.format.Formatter;
import android.util.Log;
import android.view.View;
@@ -38,11 +37,16 @@ import android.widget.Button;
import android.widget.TextView;
import com.android.internal.logging.MetricsLogger;
import com.android.settings.DropDownPreference;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.applications.ApplicationsState.AppEntry;
import com.android.settings.applications.ApplicationsState.Callbacks;
import com.android.settings.DropDownPreference;
import com.android.settings.deviceinfo.StorageWizardMoveConfirm;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
public class AppStorageSettings extends AppInfoWithHeader
implements OnClickListener, Callbacks, DropDownPreference.Callback {
@@ -53,7 +57,6 @@ public class AppStorageSettings extends AppInfoWithHeader
private static final int OP_FAILED = 2;
private static final int MSG_CLEAR_USER_DATA = 1;
private static final int MSG_CLEAR_CACHE = 3;
private static final int MSG_PACKAGE_MOVE = 4;
// invalid size value used initially and also when size retrieval through PackageManager
// fails for whatever reason
@@ -64,13 +67,11 @@ public class AppStorageSettings extends AppInfoWithHeader
private static final int DLG_CLEAR_DATA = DLG_BASE + 1;
private static final int DLG_CANNOT_CLEAR_DATA = DLG_BASE + 2;
private static final int DLG_MOVE_FAILED = DLG_BASE + 3;
private static final String KEY_MOVE_PREFERENCE = "app_location_setting";
private static final String KEY_STORAGE_SETTINGS = "storage_settings";
private static final String KEY_CACHE_SETTINGS = "cache_settings";
private CanBeOnSdCardChecker mCanBeOnSdCardChecker;
private TextView mTotalSize;
private TextView mAppSize;
private TextView mDataSize;
@@ -83,7 +84,6 @@ public class AppStorageSettings extends AppInfoWithHeader
private Button mClearCacheButton;
private DropDownPreference mMoveDropDown;
private boolean mMoveInProgress = false;
private boolean mCanClearData = true;
private boolean mHaveSizes = false;
@@ -97,7 +97,6 @@ public class AppStorageSettings extends AppInfoWithHeader
private ClearCacheObserver mClearCacheObserver;
private ClearUserDataObserver mClearDataObserver;
private PackageMoveObserver mPackageMoveObserver;
// Resource strings
private CharSequence mInvalidSizeStr;
@@ -107,7 +106,6 @@ public class AppStorageSettings extends AppInfoWithHeader
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mCanBeOnSdCardChecker = new CanBeOnSdCardChecker();
addPreferencesFromResource(R.xml.app_storage_settings);
setupViews();
}
@@ -166,18 +164,19 @@ public class AppStorageSettings extends AppInfoWithHeader
@Override
public boolean onItemSelected(int pos, Object value) {
boolean selectedExternal = (Boolean) value;
boolean isExternal = (mAppEntry.info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
if (selectedExternal ^ isExternal) {
if (mPackageMoveObserver == null) {
mPackageMoveObserver = new PackageMoveObserver();
}
int moveFlags = selectedExternal ? PackageManager.MOVE_EXTERNAL_MEDIA
: PackageManager.MOVE_INTERNAL;
mMoveInProgress = true;
refreshButtons();
mPm.movePackage(mAppEntry.info.packageName, mPackageMoveObserver, moveFlags);
final Context context = getActivity();
// If not current volume, kick off move wizard
final VolumeInfo targetVol = (VolumeInfo) value;
final VolumeInfo currentVol = context.getPackageManager().getApplicationCurrentVolume(
mAppEntry.info);
if (!Objects.equals(targetVol, currentVol)) {
final Intent intent = new Intent(context, StorageWizardMoveConfirm.class);
intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, targetVol.getId());
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mAppEntry.info.packageName);
startActivity(intent);
}
return true;
}
@@ -260,27 +259,17 @@ public class AppStorageSettings extends AppInfoWithHeader
retrieveAppEntry();
refreshButtons();
refreshSizeInfo();
boolean isExternal = (mAppEntry.info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
mMoveDropDown.setSelectedItem(isExternal ? 1 : 0);
final VolumeInfo currentVol = getActivity().getPackageManager()
.getApplicationCurrentVolume(mAppEntry.info);
mMoveDropDown.setSelectedValue(currentVol);
return true;
}
private void refreshButtons() {
if (!mMoveInProgress) {
initMoveDropDown();
initDataButtons();
} else {
mMoveDropDown.setSummary(R.string.moving);
mMoveDropDown.setSelectable(false);
}
}
private void updateMoveEnabled(boolean enabled) {
mMoveDropDown.clearItems();
mMoveDropDown.addItem(R.string.storage_type_internal, false);
if (enabled) {
mMoveDropDown.addItem(R.string.storage_type_external, true);
}
initMoveDropDown();
initDataButtons();
}
private void initDataButtons() {
@@ -310,20 +299,18 @@ public class AppStorageSettings extends AppInfoWithHeader
}
private void initMoveDropDown() {
if (Environment.isExternalStorageEmulated()) {
updateMoveEnabled(false);
return;
final Context context = getActivity();
final StorageManager storage = context.getSystemService(StorageManager.class);
final List<VolumeInfo> candidates = context.getPackageManager()
.getApplicationCandidateVolumes(mAppEntry.info);
Collections.sort(candidates, VolumeInfo.getDescriptionComparator());
mMoveDropDown.clearItems();
for (VolumeInfo vol : candidates) {
final String volDescrip = storage.getBestVolumeDescription(vol);
mMoveDropDown.addItem(volDescrip, vol);
}
boolean dataOnly = (mPackageInfo == null) && (mAppEntry != null);
boolean moveDisable = true;
if ((mAppEntry.info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
// Always let apps move to internal storage from sdcard.
moveDisable = false;
} else {
mCanBeOnSdCardChecker.init();
moveDisable = !mCanBeOnSdCardChecker.check(mAppEntry.info);
}
updateMoveEnabled(!moveDisable);
mMoveDropDown.setSelectable(!mAppControlRestricted);
}
@@ -351,21 +338,6 @@ public class AppStorageSettings extends AppInfoWithHeader
}
}
private void processMoveMsg(Message msg) {
int result = msg.arg1;
String packageName = mAppEntry.info.packageName;
// Refresh the button attributes.
mMoveInProgress = false;
if (result == PackageManager.MOVE_SUCCEEDED) {
Log.i(TAG, "Moved resources for " + packageName);
// Refresh size information again.
mState.requestSize(mPackageName, mUserId);
} else {
showDialogInner(DLG_MOVE_FAILED, result);
}
refreshUi();
}
/*
* Private method to handle clear message notification from observer when
* the async operation from PackageManager is complete
@@ -382,24 +354,6 @@ public class AppStorageSettings extends AppInfoWithHeader
}
}
private CharSequence getMoveErrMsg(int errCode) {
switch (errCode) {
case PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE:
return getActivity().getString(R.string.insufficient_storage);
case PackageManager.MOVE_FAILED_DOESNT_EXIST:
return getActivity().getString(R.string.does_not_exist);
case PackageManager.MOVE_FAILED_FORWARD_LOCKED:
return getActivity().getString(R.string.app_forward_locked);
case PackageManager.MOVE_FAILED_INVALID_LOCATION:
return getActivity().getString(R.string.invalid_location);
case PackageManager.MOVE_FAILED_SYSTEM_PACKAGE:
return getActivity().getString(R.string.system_package);
case PackageManager.MOVE_FAILED_INTERNAL_ERROR:
return "";
}
return "";
}
@Override
protected AlertDialog createDialog(int id, int errorCode) {
switch (id) {
@@ -427,14 +381,6 @@ public class AppStorageSettings extends AppInfoWithHeader
}
})
.create();
case DLG_MOVE_FAILED:
CharSequence msg = getActivity().getString(R.string.move_app_failed_dlg_text,
getMoveErrMsg(errorCode));
return new AlertDialog.Builder(getActivity())
.setTitle(getActivity().getText(R.string.move_app_failed_dlg_title))
.setMessage(msg)
.setNeutralButton(R.string.dlg_ok, null)
.create();
}
return null;
}
@@ -459,9 +405,6 @@ public class AppStorageSettings extends AppInfoWithHeader
// Refresh size info
mState.requestSize(mPackageName, mUserId);
break;
case MSG_PACKAGE_MOVE:
processMoveMsg(msg);
break;
}
}
};
@@ -508,13 +451,4 @@ public class AppStorageSettings extends AppInfoWithHeader
mHandler.sendMessage(msg);
}
}
class PackageMoveObserver extends IPackageMoveObserver.Stub {
public void packageMoved(String packageName, int returnCode) throws RemoteException {
final Message msg = mHandler.obtainMessage(MSG_PACKAGE_MOVE);
msg.arg1 = returnCode;
mHandler.sendMessage(msg);
}
}
}

View File

@@ -295,20 +295,6 @@ public class ApplicationsState {
}
};
public static final AppFilter FILTER_ON_SD_CARD = new AppFilter() {
final CanBeOnSdCardChecker mCanBeOnSdCardChecker
= new CanBeOnSdCardChecker();
public void init() {
mCanBeOnSdCardChecker.init();
}
@Override
public boolean filterApp(AppEntry entry) {
return mCanBeOnSdCardChecker.check(entry.info);
}
};
public static final AppFilter FILTER_DISABLED = new AppFilter() {
public void init() {
}

View File

@@ -20,15 +20,11 @@ import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.preference.PreferenceFrameLayout;
@@ -53,7 +49,6 @@ import android.widget.Filterable;
import android.widget.ListView;
import android.widget.Spinner;
import com.android.internal.content.PackageHelper;
import com.android.internal.logging.MetricsLogger;
import com.android.settings.HelpUtils;
import com.android.settings.InstrumentedFragment;
@@ -74,47 +69,6 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
final class CanBeOnSdCardChecker {
final IPackageManager mPm;
int mInstallLocation;
CanBeOnSdCardChecker() {
mPm = IPackageManager.Stub.asInterface(
ServiceManager.getService("package"));
}
void init() {
try {
mInstallLocation = mPm.getInstallLocation();
} catch (RemoteException e) {
Log.e("CanBeOnSdCardChecker", "Is Package Manager running?");
return;
}
}
boolean check(ApplicationInfo info) {
boolean canBe = false;
if ((info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
canBe = true;
} else {
if ((info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
if (info.installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL ||
info.installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
canBe = true;
} else if (info.installLocation
== PackageInfo.INSTALL_LOCATION_UNSPECIFIED) {
if (mInstallLocation == PackageHelper.APP_INSTALL_EXTERNAL) {
// For apps with no preference and the default value set
// to install on sdcard.
canBe = true;
}
}
}
}
return canBe;
}
}
/**
* Activity to pick an application that will be used to display installation information and
* options to uninstall/delete user data for system applications. This activity

View File

@@ -41,7 +41,6 @@ import com.android.settings.search.SearchIndexableRaw;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
@@ -91,21 +90,6 @@ public class StorageSettings extends SettingsPreferenceFragment implements Index
setHasOptionsMenu(true);
}
private static final Comparator<VolumeInfo> sVolumeComparator = new Comparator<VolumeInfo>() {
@Override
public int compare(VolumeInfo lhs, VolumeInfo rhs) {
if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(lhs.getId())) {
return -1;
} else if (lhs.getDescription() == null) {
return 1;
} else if (rhs.getDescription() == null) {
return -1;
} else {
return lhs.getDescription().compareTo(rhs.getDescription());
}
}
};
private final StorageEventListener mStorageListener = new StorageEventListener() {
@Override
public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
@@ -133,7 +117,7 @@ public class StorageSettings extends SettingsPreferenceFragment implements Index
mExternalCategory.removeAll();
final List<VolumeInfo> volumes = mStorageManager.getVolumes();
Collections.sort(volumes, sVolumeComparator);
Collections.sort(volumes, VolumeInfo.getDescriptionComparator());
for (VolumeInfo vol : volumes) {
if (vol.getType() == VolumeInfo.TYPE_PRIVATE) {

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2015 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.settings.deviceinfo;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.os.storage.VolumeInfo;
import com.android.internal.util.Preconditions;
import com.android.settings.R;
public class StorageWizardMoveConfirm extends StorageWizardBase {
private String mPackageName;
private ApplicationInfo mApp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.storage_wizard_generic);
try {
mPackageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
mApp = getPackageManager().getApplicationInfo(mPackageName, 0);
} catch (NameNotFoundException e) {
throw new RuntimeException(e);
}
Preconditions.checkNotNull(mVolume);
Preconditions.checkNotNull(mApp);
// Sanity check that target volume is candidate
Preconditions.checkState(
getPackageManager().getApplicationCandidateVolumes(mApp).contains(mVolume));
final String appName = getPackageManager().getApplicationLabel(mApp).toString();
final String volumeName = mStorage.getBestVolumeDescription(mVolume);
setHeaderText(R.string.storage_wizard_move_confirm_title, appName);
setBodyText(R.string.storage_wizard_move_confirm_body, appName, volumeName);
getNextButton().setText(R.string.move_app);
}
@Override
public void onNavigateNext() {
final Intent intent = new Intent(this, StorageWizardMoveProgress.class);
intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, mVolume.getId());
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mPackageName);
startActivity(intent);
finishAffinity();
}
}

View File

@@ -0,0 +1,138 @@
/*
* Copyright (C) 2015 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.settings.deviceinfo;
import static com.android.settings.deviceinfo.StorageSettings.TAG;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageMoveObserver;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.storage.VolumeInfo;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.android.internal.util.Preconditions;
import com.android.settings.R;
import java.util.concurrent.CountDownLatch;
public class StorageWizardMoveProgress extends StorageWizardBase {
private String mPackageName;
private ApplicationInfo mApp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.storage_wizard_progress);
try {
mPackageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
mApp = getPackageManager().getApplicationInfo(mPackageName, 0);
} catch (NameNotFoundException e) {
throw new RuntimeException(e);
}
Preconditions.checkNotNull(mVolume);
Preconditions.checkNotNull(mApp);
final String appName = getPackageManager().getApplicationLabel(mApp).toString();
final String volumeName = mStorage.getBestVolumeDescription(mVolume);
setHeaderText(R.string.storage_wizard_move_progress_title, appName);
setBodyText(R.string.storage_wizard_move_progress_body, volumeName, appName);
setCurrentProgress(20);
getNextButton().setVisibility(View.GONE);
new MoveTask().execute();
}
private CharSequence moveStatusToMessage(int returnCode) {
switch (returnCode) {
case PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE:
return getString(R.string.insufficient_storage);
case PackageManager.MOVE_FAILED_DOESNT_EXIST:
return getString(R.string.does_not_exist);
case PackageManager.MOVE_FAILED_FORWARD_LOCKED:
return getString(R.string.app_forward_locked);
case PackageManager.MOVE_FAILED_INVALID_LOCATION:
return getString(R.string.invalid_location);
case PackageManager.MOVE_FAILED_SYSTEM_PACKAGE:
return getString(R.string.system_package);
case PackageManager.MOVE_FAILED_INTERNAL_ERROR:
default:
return getString(R.string.insufficient_storage);
}
}
private class LocalPackageMoveObserver extends IPackageMoveObserver.Stub {
public int returnCode;
public CountDownLatch finished = new CountDownLatch(1);
@Override
public void packageMoved(String packageName, int returnCode) throws RemoteException {
this.returnCode = returnCode;
this.finished.countDown();
}
}
public class MoveTask extends AsyncTask<Void, Void, Integer> {
@Override
protected Integer doInBackground(Void... params) {
try {
final LocalPackageMoveObserver observer = new LocalPackageMoveObserver();
if (mApp.isExternalAsec()) {
getPackageManager().movePackage(mPackageName, observer,
PackageManager.MOVE_INTERNAL);
} else if (mVolume.getType() == VolumeInfo.TYPE_PUBLIC) {
getPackageManager().movePackage(mPackageName, observer,
PackageManager.MOVE_EXTERNAL_MEDIA);
} else {
getPackageManager().movePackageAndData(mPackageName, mVolume.fsUuid, observer);
}
observer.finished.await();
return observer.returnCode;
} catch (Exception e) {
Log.e(TAG, "Failed to move", e);
return PackageManager.MOVE_FAILED_INTERNAL_ERROR;
}
}
@Override
protected void onPostExecute(Integer returnCode) {
final Context context = StorageWizardMoveProgress.this;
if (returnCode == PackageManager.MOVE_SUCCEEDED) {
finishAffinity();
} else {
Log.w(TAG, "Move failed with status " + returnCode);
Toast.makeText(context, moveStatusToMessage(returnCode), Toast.LENGTH_LONG).show();
finishAffinity();
}
}
}
}