<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"
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;
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 {
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
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;
private Button mClearCacheButton;
private DropDownPreference mMoveDropDown;
- private boolean mMoveInProgress = false;
private boolean mCanClearData = true;
private boolean mHaveSizes = false;
private ClearCacheObserver mClearCacheObserver;
private ClearUserDataObserver mClearDataObserver;
- private PackageMoveObserver mPackageMoveObserver;
// Resource strings
private CharSequence mInvalidSizeStr;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mCanBeOnSdCardChecker = new CanBeOnSdCardChecker();
addPreferencesFromResource(R.xml.app_storage_settings);
setupViews();
}
@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;
}
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() {
}
private void initMoveDropDown() {
- if (Environment.isExternalStorageEmulated()) {
- updateMoveEnabled(false);
- return;
- }
- 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);
+ 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);
}
- updateMoveEnabled(!moveDisable);
mMoveDropDown.setSelectable(!mAppControlRestricted);
}
}
}
- 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
}
}
- 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) {
}
})
.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;
}
// Refresh size info
mState.requestSize(mPackageName, mUserId);
break;
- case MSG_PACKAGE_MOVE:
- processMoveMsg(msg);
- break;
}
}
};
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);
- }
- }
-
}
}
};
- 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() {
}
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;
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;
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
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Comparator;
import java.util.List;
/**
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) {
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) {
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+ }
+ }
+}