diff --git a/AndroidManifest.xml b/AndroidManifest.xml index f992e45c8e1..ba152150c72 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2448,7 +2448,7 @@ diff --git a/res/drawable/ic_trash_can.xml b/res/drawable/ic_trash_can.xml index ed4a4cbbd09..7829e1b9f8a 100644 --- a/res/drawable/ic_trash_can.xml +++ b/res/drawable/ic_trash_can.xml @@ -18,7 +18,8 @@ android:width="24dp" android:height="24dp" android:viewportWidth="24" - android:viewportHeight="24"> + android:viewportHeight="24" + android:tint="?android:attr/colorControlNormal"> - + android:layout_height="wrap_content" + android:clipChildren="true"> - + android:layout_height="match_parent" + android:scrollbarStyle="outsideOverlay"> - + android:orientation="vertical" + android:padding="24dp"> - + - + - + + + \ No newline at end of file diff --git a/res/layout/accessibility_edit_shortcut_magnification.xml b/res/layout/accessibility_edit_shortcut_magnification.xml index 31966e3ce4c..725c4c77d79 100644 --- a/res/layout/accessibility_edit_shortcut_magnification.xml +++ b/res/layout/accessibility_edit_shortcut_magnification.xml @@ -15,64 +15,69 @@ limitations under the License --> - + android:layout_height="wrap_content" + android:clipChildren="true"> - - - - - + android:layout_height="match_parent" + android:scrollbarStyle="outsideOverlay"> + android:orientation="vertical" + android:padding="24dp"> - - - + android:layout_height="wrap_content" + android:layout_marginBottom="32dp" /> + + + + + + + + + + + + - - - - - - + + \ No newline at end of file diff --git a/res/values/styles.xml b/res/values/styles.xml index a5b187bf94e..7b8a75d947e 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -648,7 +648,7 @@ diff --git a/src/com/android/settings/applications/intentpicker/AppLaunchSettings.java b/src/com/android/settings/applications/intentpicker/AppLaunchSettings.java index 8683f56af79..439a6a31b40 100644 --- a/src/com/android/settings/applications/intentpicker/AppLaunchSettings.java +++ b/src/com/android/settings/applications/intentpicker/AppLaunchSettings.java @@ -312,6 +312,7 @@ public class AppLaunchSettings extends AppInfoBase implements /** Initialize add link preference */ private void initAddLinkPreference() { mAddLinkPreference = findPreference(ADD_LINK_PREF_KEY); + mAddLinkPreference.setVisible(isAddLinksShown()); mAddLinkPreference.setEnabled(isAddLinksNotEmpty()); mAddLinkPreference.setOnPreferenceClickListener(preference -> { final int stateNoneLinksNo = getLinksNumber(DOMAIN_STATE_NONE); @@ -327,6 +328,10 @@ public class AppLaunchSettings extends AppInfoBase implements return getLinksNumber(DOMAIN_STATE_NONE) > 0; } + private boolean isAddLinksShown() { + return (isAddLinksNotEmpty() || getLinksNumber(DOMAIN_STATE_SELECTED) > 0); + } + private void showProgressDialogFragment() { final Bundle args = new Bundle(); args.putString(APP_PACKAGE_KEY, mPackageName); diff --git a/src/com/android/settings/deviceinfo/StorageCategoryFragment.java b/src/com/android/settings/deviceinfo/StorageCategoryFragment.java index ce8f2199187..9ae835e7a3f 100644 --- a/src/com/android/settings/deviceinfo/StorageCategoryFragment.java +++ b/src/com/android/settings/deviceinfo/StorageCategoryFragment.java @@ -73,6 +73,7 @@ public class StorageCategoryFragment extends DashboardFragment LoaderManager.LoaderCallbacks>, Preference.OnPreferenceClickListener { private static final String TAG = "StorageCategoryFrag"; + private static final String SELECTED_STORAGE_ENTRY_KEY = "selected_storage_entry_key"; private static final String SUMMARY_PREF_KEY = "storage_summary"; private static final String FREE_UP_SPACE_PREF_KEY = "free_up_space"; private static final int STORAGE_JOB_ID = 0; @@ -127,6 +128,10 @@ public class StorageCategoryFragment extends DashboardFragment mStorageManager = getActivity().getSystemService(StorageManager.class); + if (icicle != null) { + mSelectedStorageEntry = icicle.getParcelable(SELECTED_STORAGE_ENTRY_KEY); + } + initializePreference(); } @@ -167,6 +172,12 @@ public class StorageCategoryFragment extends DashboardFragment } } + @Override + public void onSaveInstanceState(Bundle outState) { + outState.putParcelable(SELECTED_STORAGE_ENTRY_KEY, mSelectedStorageEntry); + super.onSaveInstanceState(outState); + } + private void onReceivedSizes() { boolean stopLoading = false; if (mStorageInfo != null) { diff --git a/src/com/android/settings/deviceinfo/StorageItemPreference.java b/src/com/android/settings/deviceinfo/StorageItemPreference.java index 8410e42c889..cdb9f9d6fe1 100644 --- a/src/com/android/settings/deviceinfo/StorageItemPreference.java +++ b/src/com/android/settings/deviceinfo/StorageItemPreference.java @@ -17,8 +17,6 @@ package com.android.settings.deviceinfo; import android.content.Context; -import android.text.TextUtils; -import android.text.format.Formatter; import android.util.AttributeSet; import android.widget.ProgressBar; @@ -26,6 +24,7 @@ import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; import com.android.settings.R; +import com.android.settings.deviceinfo.storage.StorageUtils; public class StorageItemPreference extends Preference { public int userHandle; @@ -49,7 +48,7 @@ public class StorageItemPreference extends Preference { public void setStorageSize(long size, long total) { mStorageSize = size; - setSummary(getStorageSummary(size)); + setSummary(StorageUtils.getStorageSizeLabel(getContext(), size)); if (total == 0) { mProgressPercent = 0; @@ -77,11 +76,4 @@ public class StorageItemPreference extends Preference { updateProgressBar(); super.onBindViewHolder(view); } - - private String getStorageSummary(long bytes) { - final Formatter.BytesResult result = Formatter.formatBytes(getContext().getResources(), - bytes, Formatter.FLAG_SHORTER); - return TextUtils.expandTemplate(getContext().getText(R.string.storage_size_large), - result.value, result.units).toString(); - } } diff --git a/src/com/android/settings/deviceinfo/storage/EmptyTrashFragment.java b/src/com/android/settings/deviceinfo/storage/EmptyTrashFragment.java index da7b9ba8773..3c486721a8c 100644 --- a/src/com/android/settings/deviceinfo/storage/EmptyTrashFragment.java +++ b/src/com/android/settings/deviceinfo/storage/EmptyTrashFragment.java @@ -18,25 +18,54 @@ package com.android.settings.deviceinfo.storage; import android.app.Dialog; import android.app.settings.SettingsEnums; +import android.content.Context; +import android.content.pm.PackageManager.NameNotFoundException; import android.os.Bundle; +import android.os.UserHandle; +import android.provider.MediaStore; +import android.util.Log; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.Fragment; import com.android.settings.R; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; +import com.android.settingslib.utils.ThreadUtils; /** * Dialog asks if users want to empty trash files. + * TODO(b/189388449): Shows "Deleting..." and disables Trash category while deleting trash files. */ public class EmptyTrashFragment extends InstrumentedDialogFragment { + private static final String TAG = "EmptyTrashFragment"; + private static final String TAG_EMPTY_TRASH = "empty_trash"; + private final Fragment mParentFragment; + private final int mUserId; + private final long mTrashSize; + private final OnEmptyTrashCompleteListener mOnEmptyTrashCompleteListener; + + /** The listener to receive empty trash complete callback event. */ + public interface OnEmptyTrashCompleteListener { + /** The empty trash complete callback. */ + void onEmptyTrashComplete(); + } + + public EmptyTrashFragment(Fragment parent, int userId, long trashSize, + OnEmptyTrashCompleteListener onEmptyTrashCompleteListener) { + super(); + + mParentFragment = parent; + setTargetFragment(mParentFragment, 0 /* requestCode */); + mUserId = userId; + mTrashSize = trashSize; + mOnEmptyTrashCompleteListener = onEmptyTrashCompleteListener; + } + /** Shows the empty trash dialog. */ - public static void show(Fragment parent) { - final EmptyTrashFragment dialog = new EmptyTrashFragment(); - dialog.setTargetFragment(parent, 0 /* requestCode */); - dialog.show(parent.getFragmentManager(), TAG_EMPTY_TRASH); + public void show() { + show(mParentFragment.getFragmentManager(), TAG_EMPTY_TRASH); } @Override @@ -48,10 +77,38 @@ public class EmptyTrashFragment extends InstrumentedDialogFragment { public Dialog onCreateDialog(Bundle savedInstanceState) { final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); return builder.setTitle(R.string.storage_trash_dialog_title) - .setMessage(R.string.storage_trash_dialog_ask_message) - .setPositiveButton(R.string.storage_trash_dialog_confirm, (dialog, which) -> { - // TODO(170918505): Implement the logic in worker thread. - }).setNegativeButton(android.R.string.cancel, null) + .setMessage(getActivity().getString(R.string.storage_trash_dialog_ask_message, + StorageUtils.getStorageSizeLabel(getActivity(), mTrashSize))) + .setPositiveButton(R.string.storage_trash_dialog_confirm, + (dialog, which) -> emptyTrashAsync()) + .setNegativeButton(android.R.string.cancel, null) .create(); } + + private void emptyTrashAsync() { + final Context context = getActivity(); + final Context perUserContext; + try { + perUserContext = context.createPackageContextAsUser( + context.getApplicationContext().getPackageName(), + 0 /* flags= */, + UserHandle.of(mUserId)); + } catch (NameNotFoundException e) { + Log.e(TAG, "Not able to get Context for user ID " + mUserId); + return; + } + + final Bundle trashQueryArgs = new Bundle(); + trashQueryArgs.putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_ONLY); + ThreadUtils.postOnBackgroundThread(() -> { + perUserContext.getContentResolver().delete( + MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL), + trashQueryArgs); + if (mOnEmptyTrashCompleteListener == null) { + return; + } + ThreadUtils.postOnMainThread( + () -> mOnEmptyTrashCompleteListener.onEmptyTrashComplete()); + }); + } } diff --git a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java index 67a5bb7f49d..d57d81ef7f8 100644 --- a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java +++ b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java @@ -32,6 +32,7 @@ import android.os.UserManager; import android.os.storage.VolumeInfo; import android.util.Log; import android.util.SparseArray; +import android.widget.Toast; import androidx.annotation.VisibleForTesting; import androidx.fragment.app.Fragment; @@ -64,7 +65,8 @@ import java.util.Map; * categorization breakdown. */ public class StorageItemPreferenceController extends AbstractPreferenceController implements - PreferenceControllerMixin { + PreferenceControllerMixin, + EmptyTrashFragment.OnEmptyTrashCompleteListener { private static final String TAG = "StorageItemPreference"; private static final String SYSTEM_FRAGMENT_TAG = "SystemInfo"; @@ -256,8 +258,7 @@ public class StorageItemPreferenceController extends AbstractPreferenceControlle mGamesPreference.setVisible(privateStoragePreferencesVisible); mDocumentsAndOtherPreference.setVisible(privateStoragePreferencesVisible); mSystemPreference.setVisible(privateStoragePreferencesVisible); - // TODO(b/170918505): Shows trash category after trash category feature complete. - mTrashPreference.setVisible(false); + mTrashPreference.setVisible(privateStoragePreferencesVisible); if (privateStoragePreferencesVisible) { final VolumeInfo sharedVolume = mSvp.findEmulatedForPrivate(mVolume); @@ -460,13 +461,29 @@ public class StorageItemPreferenceController extends AbstractPreferenceControlle private void launchTrashIntent() { final Intent intent = new Intent("android.settings.VIEW_TRASH"); - if (intent.resolveActivity(mPackageManager) == null) { - EmptyTrashFragment.show(mFragment); + if (mPackageManager.resolveActivityAsUser(intent, 0 /* flags */, mUserId) == null) { + final long trashSize = mTrashPreference.getStorageSize(); + if (trashSize > 0) { + new EmptyTrashFragment(mFragment, mUserId, trashSize, + this /* onEmptyTrashCompleteListener */).show(); + } else { + Toast.makeText(mContext, R.string.storage_trash_dialog_empty_message, + Toast.LENGTH_SHORT).show(); + } } else { mContext.startActivityAsUser(intent, new UserHandle(mUserId)); } } + @Override + public void onEmptyTrashComplete() { + if (mTrashPreference == null) { + return; + } + mTrashPreference.setStorageSize(0, mTotalSize); + updatePrivateStorageCategoryPreferencesOrder(); + } + private static long totalValues(StorageMeasurement.MeasurementDetails details, int userId, String... keys) { long total = 0; diff --git a/src/com/android/settings/deviceinfo/storage/StorageUtils.java b/src/com/android/settings/deviceinfo/storage/StorageUtils.java index 549eef61f7f..9b52fe803b1 100644 --- a/src/com/android/settings/deviceinfo/storage/StorageUtils.java +++ b/src/com/android/settings/deviceinfo/storage/StorageUtils.java @@ -26,6 +26,8 @@ import android.os.storage.DiskInfo; import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; import android.os.storage.VolumeRecord; +import android.text.TextUtils; +import android.text.format.Formatter; import android.util.Log; import android.widget.Toast; @@ -115,6 +117,14 @@ public class StorageUtils { .launch(); } + /** Returns size label of changing units. (e.g., 1kB, 2MB, 3GB) */ + public static String getStorageSizeLabel(Context context, long bytes) { + final Formatter.BytesResult result = Formatter.formatBytes(context.getResources(), + bytes, Formatter.FLAG_SHORTER); + return TextUtils.expandTemplate(context.getText(R.string.storage_size_large), + result.value, result.units).toString(); + } + /** An AsyncTask to unmount a specified volume. */ public static class UnmountTask extends AsyncTask { private final Context mContext; diff --git a/src/com/android/settings/location/LocationSettings.java b/src/com/android/settings/location/LocationSettings.java index fe3cf30c84d..8f9787b08a5 100644 --- a/src/com/android/settings/location/LocationSettings.java +++ b/src/com/android/settings/location/LocationSettings.java @@ -55,11 +55,14 @@ import java.util.List; * implementation. */ @SearchIndexable -public class LocationSettings extends DashboardFragment { +public class LocationSettings extends DashboardFragment implements + LocationEnabler.LocationModeChangeListener { private static final String TAG = "LocationSettings"; + private static final String RECENT_LOCATION_ACCESS_PREF_KEY = "recent_location_access"; private LocationSwitchBarController mSwitchBarController; + private LocationEnabler mLocationEnabler; @Override public int getMetricsCategory() { @@ -75,6 +78,7 @@ public class LocationSettings extends DashboardFragment { switchBar.show(); mSwitchBarController = new LocationSwitchBarController(activity, switchBar, getSettingsLifecycle()); + mLocationEnabler = new LocationEnabler(getContext(), this, getSettingsLifecycle()); } @Override @@ -98,6 +102,13 @@ public class LocationSettings extends DashboardFragment { return TAG; } + @Override + public void onLocationModeChanged(int mode, boolean restricted) { + if (mLocationEnabler.isEnabled(mode)) { + scrollToPreference(RECENT_LOCATION_ACCESS_PREF_KEY); + } + } + static void addPreferencesSorted(List prefs, PreferenceGroup container) { // If there's some items to display, sort the items and add them to the container. Collections.sort(prefs, diff --git a/tests/robotests/src/com/android/settings/location/LocationSettingsFooterPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/LocationSettingsFooterPreferenceControllerTest.java index 7929b718abe..333929dc10e 100644 --- a/tests/robotests/src/com/android/settings/location/LocationSettingsFooterPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/location/LocationSettingsFooterPreferenceControllerTest.java @@ -109,24 +109,30 @@ public class LocationSettingsFooterPreferenceControllerTest { assertThat(mController.isAvailable()).isTrue(); } + /** + * Display the footer even without the injected string. + */ @Test - public void isAvailable_noSystemApp_returnsFalse() { + public void isAvailable_noSystemApp_returnsTrue() { final List testResolveInfos = new ArrayList<>(); testResolveInfos.add( getTestResolveInfo(/*isSystemApp*/ false, /*hasRequiredMetadata*/ true)); when(mPackageManager.queryBroadcastReceivers(any(Intent.class), anyInt())) .thenReturn(testResolveInfos); - assertThat(mController.isAvailable()).isFalse(); + assertThat(mController.isAvailable()).isTrue(); } + /** + * Display the footer even without the injected string. + */ @Test - public void isAvailable_noRequiredMetadata_returnsFalse() { + public void isAvailable_noRequiredMetadata_returnsTrue() { final List testResolveInfos = new ArrayList<>(); testResolveInfos.add( getTestResolveInfo(/*isSystemApp*/ true, /*hasRequiredMetadata*/ false)); when(mPackageManager.queryBroadcastReceivers(any(Intent.class), anyInt())) .thenReturn(testResolveInfos); - assertThat(mController.isAvailable()).isFalse(); + assertThat(mController.isAvailable()).isTrue(); } @Test @@ -154,7 +160,8 @@ public class LocationSettingsFooterPreferenceControllerTest { mController.onLocationModeChanged(/* mode= */ 0, /* restricted= */ false); ArgumentCaptor title = ArgumentCaptor.forClass(CharSequence.class); verify(mFooterPreference, times(2)).setTitle(title.capture()); - assertThat(title.getValue().toString()).isEqualTo( + + assertThat(title.getValue().toString()).contains( Html.fromHtml(mContext.getString( R.string.location_settings_footer_location_off)).toString()); } @@ -171,7 +178,7 @@ public class LocationSettingsFooterPreferenceControllerTest { mController.onLocationModeChanged(/* mode= */ 1, /* restricted= */ false); ArgumentCaptor title = ArgumentCaptor.forClass(CharSequence.class); verify(mFooterPreference, times(2)).setTitle(title.capture()); - assertThat(title.getValue().toString()).isNotEqualTo( + assertThat(title.getValue().toString()).doesNotContain( Html.fromHtml(mContext.getString( R.string.location_settings_footer_location_off)).toString()); }