From 38889de6993e4ffadd22a97fa69749c7156db28d Mon Sep 17 00:00:00 2001 From: Daniel Nishi Date: Mon, 2 May 2016 16:39:17 -0700 Subject: [PATCH] Create an overlay to add a photo and video deletion helper service. This overlay defines a checkbox preference in the deletion helper for facilitating the deletion of photos and videos. The service which provides deletion clearing can be overridden and used to create a custom photo and video deletion helper. Bug: 28554187 Bug: 28560856 Change-Id: Id5d9671a307aaac0d1331f055b8f2ab7aa67977c --- res/values/strings.xml | 7 + res/xml/deletion_helper_list.xml | 3 + .../settings/PhotosDeletionPreference.java | 143 ++++++++++++++++++ .../DeletionHelperFragment.java | 92 +++++++++-- .../settings/deletionhelper/DeletionType.java | 55 +++++++ .../DeletionHelperFeatureProvider.java | 30 ++++ .../settings/overlay/FeatureFactory.java | 5 + .../settings/overlay/FeatureFactoryImpl.java | 5 + 8 files changed, 325 insertions(+), 15 deletions(-) create mode 100644 src/com/android/settings/PhotosDeletionPreference.java create mode 100644 src/com/android/settings/deletionhelper/DeletionType.java create mode 100644 src/com/android/settings/overlay/DeletionHelperFeatureProvider.java diff --git a/res/values/strings.xml b/res/values/strings.xml index a95055dadb3..77505013f61 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -7576,4 +7576,11 @@ Bluetooth + + + Photos & Videos (%1$d) + + + %1$s, older than %2$d days + diff --git a/res/xml/deletion_helper_list.xml b/res/xml/deletion_helper_list.xml index 7979b46c1c5..5affd601bf7 100644 --- a/res/xml/deletion_helper_list.xml +++ b/res/xml/deletion_helper_list.xml @@ -17,6 +17,9 @@ + + diff --git a/src/com/android/settings/PhotosDeletionPreference.java b/src/com/android/settings/PhotosDeletionPreference.java new file mode 100644 index 00000000000..63327918598 --- /dev/null +++ b/src/com/android/settings/PhotosDeletionPreference.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2016 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; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.support.v7.preference.Preference; +import android.support.v7.preference.Preference.OnPreferenceChangeListener; +import android.support.v7.preference.CheckBoxPreference; +import android.support.v7.preference.PreferenceViewHolder; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.text.format.Formatter; +import android.widget.TextView; +import com.android.settings.deletionhelper.DeletionType; + +/** + * Preference to handle the deletion of photos and videos in the Deletion Helper. + */ +public class PhotosDeletionPreference extends CheckBoxPreference implements + DeletionType.FreeableChangedListener, OnPreferenceChangeListener { + // TODO(b/28560570): Remove this dummy value. + private static final int FAKE_DAYS_TO_KEEP = 30; + private DeletionType.FreeableChangedListener mListener; + private boolean mChecked; + private long mFreeableBytes; + private int mFreeableItems; + private DeletionType mDeletionService; + + public PhotosDeletionPreference(Context context, AttributeSet attrs) { + super(context, attrs); + setIcon(getIcon(context)); + updatePreferenceText(); + setOnPreferenceChangeListener(this); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + final TextView titleView = (TextView) holder.findViewById(android.R.id.title); + if (titleView != null) { + titleView.setTextColor(getTintColor(getContext())); + } + } + + /** + * Get the tint color for the preference's icon and text. + * @param context UI context to get the theme. + * @return The tint color. + */ + public int getTintColor(Context context) { + TypedValue value = new TypedValue(); + context.getTheme().resolveAttribute(android.R.attr.colorAccent, value, true); + return context.getColor(value.resourceId); + } + + /** + * Updates the title and summary of the preference with fresh information. + */ + public void updatePreferenceText() { + Context context = getContext(); + setTitle(context.getString(R.string.deletion_helper_photos_title, + mFreeableItems)); + setSummary(context.getString(R.string.deletion_helper_photos_summary, + Formatter.formatFileSize(context, mFreeableBytes), FAKE_DAYS_TO_KEEP)); + } + + /** + * Returns the number of bytes which can be cleared by the deletion service. + * @return The number of bytes. + */ + public long getFreeableBytes() { + return mChecked ? mFreeableBytes : 0; + } + + /** + * Register a listener to be called back on when the freeable bytes have changed. + * @param listener The callback listener. + */ + public void registerFreeableChangedListener(DeletionType.FreeableChangedListener listener) { + mListener = listener; + } + + /** + * Registers a deletion service to update the preference's information. + * @param deletionService A photo/video deletion service. + */ + public void registerDeletionService(DeletionType deletionService) { + mDeletionService = deletionService; + if (mDeletionService != null) { + mDeletionService.registerFreeableChangedListener(this); + } + } + + @Override + public void onFreeableChanged(int numItems, long freeableBytes) { + mFreeableItems = numItems; + mFreeableBytes = freeableBytes; + updatePreferenceText(); + maybeUpdateListener(); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + mChecked = (boolean) newValue; + maybeUpdateListener(); + return true; + } + + private Drawable getIcon(Context context) { + final Drawable iconDrawable; + try { + Resources resources = context.getResources(); + final int resId = resources.getIdentifier("ic_photos_black_24", "drawable", + context.getPackageName()); + iconDrawable = context.getDrawable(resId); + } catch (Resources.NotFoundException e) { + return null; + } + return iconDrawable; + } + + private void maybeUpdateListener() { + if (mListener != null) { + mListener.onFreeableChanged(mFreeableItems, getFreeableBytes()); + } + } +} diff --git a/src/com/android/settings/deletionhelper/DeletionHelperFragment.java b/src/com/android/settings/deletionhelper/DeletionHelperFragment.java index ce467e39a9b..cc6ccef5274 100644 --- a/src/com/android/settings/deletionhelper/DeletionHelperFragment.java +++ b/src/com/android/settings/deletionhelper/DeletionHelperFragment.java @@ -1,5 +1,22 @@ +/* + * Copyright (C) 2016 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.deletionhelper; +import android.app.Application; import android.os.Bundle; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceGroup; @@ -7,31 +24,28 @@ import android.text.format.Formatter; import android.util.ArraySet; import android.util.Log; import android.view.View; -import android.view.ViewGroup; import android.widget.Button; -import android.widget.CompoundButton; -import android.widget.LinearLayout; -import android.widget.RelativeLayout; +import com.android.settings.PhotosDeletionPreference; import com.android.settings.SettingsPreferenceFragment; import com.android.settings.R; import com.android.internal.logging.MetricsProto.MetricsEvent; import com.android.settings.applications.AppStateBaseBridge; -import com.android.settings.deletionhelper.AppStateUsageStatsBridge; -import com.android.settings.deletionhelper.AppDeletionPreference; +import com.android.settings.overlay.DeletionHelperFeatureProvider; +import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.applications.ApplicationsState.AppEntry; import com.android.settingslib.applications.ApplicationsState.Callbacks; import com.android.settingslib.applications.ApplicationsState.Session; import java.util.ArrayList; -import java.util.Comparator; import java.util.HashSet; /** * Settings screen for the deletion helper, which manually removes data which is not recently used. */ public class DeletionHelperFragment extends SettingsPreferenceFragment implements - ApplicationsState.Callbacks, AppStateBaseBridge.Callback, Preference.OnPreferenceChangeListener { + ApplicationsState.Callbacks, AppStateBaseBridge.Callback, + Preference.OnPreferenceChangeListener, DeletionType.FreeableChangedListener { private static final String TAG = "DeletionHelperFragment"; private static final String EXTRA_HAS_BRIDGE = "hasBridge"; @@ -39,9 +53,11 @@ public class DeletionHelperFragment extends SettingsPreferenceFragment implement private static final String EXTRA_CHECKED_SET = "checkedSet"; private static final String KEY_APPS_GROUP = "apps_group"; + private static final String KEY_PHOTOS_VIDEOS_PREFERENCE = "delete_photos"; private Button mCancel, mFree; private PreferenceGroup mApps; + private PhotosDeletionPreference mPhotoPreference; private ApplicationsState mState; private Session mSession; @@ -49,17 +65,26 @@ public class DeletionHelperFragment extends SettingsPreferenceFragment implement private AppStateUsageStatsBridge mDataUsageBridge; private ArrayList mAppEntries; private boolean mHasReceivedAppEntries, mHasReceivedBridgeCallback, mFinishedLoading; + private DeletionHelperFeatureProvider mProvider; + private DeletionType mPhotoVideoDeletion; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mState = ApplicationsState.getInstance(getActivity().getApplication()); + Application app = getActivity().getApplication(); + mState = ApplicationsState.getInstance(app); mSession = mState.newSession(this); mUncheckedApplications = new HashSet<>(); mDataUsageBridge = new AppStateUsageStatsBridge(getActivity(), mState, this); addPreferencesFromResource(R.xml.deletion_helper_list); mApps = (PreferenceGroup) findPreference(KEY_APPS_GROUP); + mPhotoPreference = (PhotosDeletionPreference) findPreference(KEY_PHOTOS_VIDEOS_PREFERENCE); + mProvider = + FeatureFactory.getFactory(app).getDeletionHelperFeatureProvider(); + if (mProvider != null) { + mPhotoVideoDeletion = mProvider.createPhotoVideoDeletionType(); + } if (savedInstanceState != null) { mHasReceivedAppEntries = @@ -86,6 +111,13 @@ public class DeletionHelperFragment extends SettingsPreferenceFragment implement mFree.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { + // This should be fine as long as there is only one extra deletion feature. + // In the future, this should be done in an async queue in order to not interfere + // with the simultaneous PackageDeletionTask. + if (mPhotoPreference != null && mPhotoPreference.isChecked()) { + mPhotoVideoDeletion.clearFreeableData(); + } + ArraySet apps = new ArraySet<>(); for (AppEntry entry : mAppEntries) { if (!mUncheckedApplications.contains(entry.label)) { @@ -113,10 +145,21 @@ public class DeletionHelperFragment extends SettingsPreferenceFragment implement }); } + private void initializeDeletionPreferences() { + if (mProvider == null) { + getPreferenceScreen().removePreference(mPhotoPreference); + mPhotoPreference = null; + } else { + mPhotoPreference.registerFreeableChangedListener(this); + mPhotoPreference.registerDeletionService(mPhotoVideoDeletion); + } + } + @Override public void onViewCreated(View v, Bundle savedInstanceState) { super.onViewCreated(v, savedInstanceState); initializeButtons(v); + initializeDeletionPreferences(); setLoading(true, false); } @@ -125,6 +168,10 @@ public class DeletionHelperFragment extends SettingsPreferenceFragment implement super.onResume(); mSession.resume(); mDataUsageBridge.resume(); + + if (mPhotoVideoDeletion != null) { + mPhotoVideoDeletion.onResume(); + } } @@ -142,6 +189,10 @@ public class DeletionHelperFragment extends SettingsPreferenceFragment implement super.onPause(); mDataUsageBridge.pause(); mSession.pause(); + + if (mPhotoVideoDeletion != null) { + mPhotoVideoDeletion.onPause(); + } } private void rebuild() { @@ -250,16 +301,27 @@ public class DeletionHelperFragment extends SettingsPreferenceFragment implement return true; } + @Override + public void onFreeableChanged(int numItems, long freeableBytes) { + updateFreeButtonText(); + } + private long getTotalFreeableSpace() { long freeableSpace = 0; - for (int i = 0; i < mAppEntries.size(); i++) { - final AppEntry entry = mAppEntries.get(i); - long entrySize = mAppEntries.get(i).size; - // If the entrySize is negative, it is either an unknown size or an error occurred. - if (!mUncheckedApplications.contains(entry.label) && entrySize > 0) { - freeableSpace += entrySize; + if (mAppEntries != null) { + + for (int i = 0; i < mAppEntries.size(); i++) { + final AppEntry entry = mAppEntries.get(i); + long entrySize = mAppEntries.get(i).size; + // If the entrySize is negative, it is either an unknown size or an error occurred. + if (!mUncheckedApplications.contains(entry.label) && entrySize > 0) { + freeableSpace += entrySize; + } } } + if (mPhotoPreference != null) { + freeableSpace += mPhotoPreference.getFreeableBytes(); + } return freeableSpace; } } \ No newline at end of file diff --git a/src/com/android/settings/deletionhelper/DeletionType.java b/src/com/android/settings/deletionhelper/DeletionType.java new file mode 100644 index 00000000000..ee1e0f7a80f --- /dev/null +++ b/src/com/android/settings/deletionhelper/DeletionType.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2016 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.deletionhelper; + +import android.content.Context; +import android.support.v7.preference.PreferenceGroup; +import android.support.v7.preference.Preference; + +/** + * Helper for the Deletion Helper which can query, clear out, and visualize deletable data. + * This could represent a helper for deleting photos, downloads, movies, etc. + */ +public interface DeletionType { + /** + * Registers a callback to call when the amount of freeable space is updated. + * @param listener A callback. + */ + void registerFreeableChangedListener(FreeableChangedListener listener); + + /** + * Resumes an operation, intended to be called when the deletion fragment resumes. + */ + void onResume(); + + /** + * Pauses the feature's operations, intended to be called when the deletion fragment is paused. + */ + void onPause(); + + /** + * Asynchronously free up the freeable information for the feature. + */ + void clearFreeableData(); + + /** + * Callback interface to listen for when a deletion feature's amount of freeable space updates. + */ + interface FreeableChangedListener { + void onFreeableChanged(int numItems, long bytesFreeable); + } +} diff --git a/src/com/android/settings/overlay/DeletionHelperFeatureProvider.java b/src/com/android/settings/overlay/DeletionHelperFeatureProvider.java new file mode 100644 index 00000000000..39b1edf5580 --- /dev/null +++ b/src/com/android/settings/overlay/DeletionHelperFeatureProvider.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2016 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.overlay; + +import android.support.v7.preference.PreferenceGroup; +import com.android.settings.deletionhelper.DeletionType; + +/** + * Feature provider for the manual deletion helper Settings page. + */ +public interface DeletionHelperFeatureProvider { + /** + * Creates a {@link DeletionType} for clearing out stored photos and videos on the device. + */ + DeletionType createPhotoVideoDeletionType(); +} diff --git a/src/com/android/settings/overlay/FeatureFactory.java b/src/com/android/settings/overlay/FeatureFactory.java index 1bffc2b2ab0..3b307e51de1 100644 --- a/src/com/android/settings/overlay/FeatureFactory.java +++ b/src/com/android/settings/overlay/FeatureFactory.java @@ -61,6 +61,11 @@ public abstract class FeatureFactory { public abstract SupportFeatureProvider getSupportFeatureProvider(Context context); + /** + * Return a provider which adds additional deletion services to the Deletion Helper. + */ + public abstract DeletionHelperFeatureProvider getDeletionHelperFeatureProvider(); + public static final class FactoryNotFoundException extends RuntimeException { public FactoryNotFoundException(Throwable throwable) { super("Unable to create factory. Did you misconfigure Proguard?", throwable); diff --git a/src/com/android/settings/overlay/FeatureFactoryImpl.java b/src/com/android/settings/overlay/FeatureFactoryImpl.java index ce561f3d17d..425320ac5bf 100644 --- a/src/com/android/settings/overlay/FeatureFactoryImpl.java +++ b/src/com/android/settings/overlay/FeatureFactoryImpl.java @@ -28,4 +28,9 @@ public final class FeatureFactoryImpl extends FeatureFactory { return null; } + @Override + public DeletionHelperFeatureProvider getDeletionHelperFeatureProvider() { + return null; + } + }