diff --git a/res/drawable/ic_keyboard_arrow_down_black_32.xml b/res/drawable/ic_keyboard_arrow_down_black_32.xml new file mode 100644 index 00000000000..12ce9c2e4b3 --- /dev/null +++ b/res/drawable/ic_keyboard_arrow_down_black_32.xml @@ -0,0 +1,25 @@ + + + + + \ No newline at end of file diff --git a/res/drawable/ic_keyboard_arrow_up_black_32.xml b/res/drawable/ic_keyboard_arrow_up_black_32.xml new file mode 100644 index 00000000000..d41980061c4 --- /dev/null +++ b/res/drawable/ic_keyboard_arrow_up_black_32.xml @@ -0,0 +1,25 @@ + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 6c9e64541cb..10ad16b2834 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -7620,4 +7620,10 @@ Turn on + + Apps (%1$d) + + + %1$s + diff --git a/res/xml/deletion_helper_list.xml b/res/xml/deletion_helper_list.xml index 78d3b144153..ac5a85159cc 100644 --- a/res/xml/deletion_helper_list.xml +++ b/res/xml/deletion_helper_list.xml @@ -23,8 +23,8 @@ - + diff --git a/src/com/android/settings/CollapsibleCheckboxPreferenceGroup.java b/src/com/android/settings/CollapsibleCheckboxPreferenceGroup.java new file mode 100644 index 00000000000..a0f9d988ade --- /dev/null +++ b/src/com/android/settings/CollapsibleCheckboxPreferenceGroup.java @@ -0,0 +1,147 @@ +/* + * 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.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceGroup; +import android.support.v7.preference.PreferenceViewHolder; +import android.util.AttributeSet; + +import android.util.Log; +import android.util.TypedValue; +import android.view.View; +import android.widget.Checkable; +import android.widget.TextView; + +import java.util.LinkedHashMap; + +/** + * CollapsibleCheckboxPreferenceGroup is a preference group that can be expanded or collapsed and + * also has a checkbox. + */ +public class CollapsibleCheckboxPreferenceGroup extends PreferenceGroup implements + View.OnClickListener { + private boolean mCollapsed; + private boolean mChecked; + + public CollapsibleCheckboxPreferenceGroup(Context context) { + this(context, null); + } + + public CollapsibleCheckboxPreferenceGroup(Context context, AttributeSet attrs) { + super(context, attrs); + setWidgetLayoutResource(com.android.settings.R.layout.preference_widget_checkbox); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + View checkbox = holder.findViewById(com.android.internal.R.id.checkbox); + if (checkbox != null && checkbox instanceof Checkable) { + ((Checkable) checkbox).setChecked(mChecked); + checkbox.setClickable(true); + checkbox.setFocusable(true); + checkbox.setOnClickListener(this); + } + + final TextView titleView = (TextView) holder.findViewById(android.R.id.title); + if (titleView != null) { + Context context = getContext(); + TypedValue value = new TypedValue(); + context.getTheme().resolveAttribute(android.R.attr.colorAccent, value, true); + titleView.setTextColor(context.getColor(value.resourceId)); + } + } + + @Override + public boolean addPreference(Preference p) { + super.addPreference(p); + p.setVisible(!isCollapsed()); + return true; + } + + // The preference click handler. + @Override + protected void onClick() { + super.onClick(); + setCollapse(!isCollapsed()); + } + + // The checkbox view click handler. + @Override + public void onClick(View v) { + setChecked(!isChecked()); + } + + /** + * Return if the view is collapsed. + */ + public boolean isCollapsed() { + return mCollapsed; + } + + /** + * Returns the checked state of the preference. + */ + public boolean isChecked() { + return mChecked; + } + + /** + * Sets the checked state and notifies listeners of the state change. + */ + public void setChecked(boolean checked) { + if (mChecked != checked) { + mChecked = checked; + + callChangeListener(checked); + notifyDependencyChange(shouldDisableDependents()); + notifyChanged(); + } + } + + private void setCollapse(boolean isCollapsed) { + if (mCollapsed == isCollapsed) { + return; + } + + mCollapsed = isCollapsed; + if (isCollapsed) { + hideDropdownPreferences(); + } else { + showDropdownPreferences(); + } + } + + private void showDropdownPreferences() { + setAllPreferencesVisibility(true); + setIcon(R.drawable.ic_keyboard_arrow_down_black_32); + } + + private void hideDropdownPreferences() { + setAllPreferencesVisibility(false); + setIcon(R.drawable.ic_keyboard_arrow_up_black_32); + } + + private void setAllPreferencesVisibility(boolean visible) { + for (int i = 0; i < getPreferenceCount(); i++) { + Preference p = getPreference(i); + p.setVisible(visible); + } + } +} diff --git a/src/com/android/settings/deletionhelper/DeletionHelperFragment.java b/src/com/android/settings/deletionhelper/DeletionHelperFragment.java index 8c08ce77a67..357db90f7af 100644 --- a/src/com/android/settings/deletionhelper/DeletionHelperFragment.java +++ b/src/com/android/settings/deletionhelper/DeletionHelperFragment.java @@ -16,17 +16,18 @@ package com.android.settings.deletionhelper; +import android.app.Activity; import android.app.Application; import android.content.Intent; import android.os.Bundle; import android.support.v7.preference.Preference; -import android.support.v7.preference.PreferenceGroup; import android.text.format.Formatter; import android.util.ArraySet; import android.util.Log; import android.view.View; import android.widget.Button; import com.android.settings.deletionhelper.DownloadsDeletionPreference; +import com.android.settings.CollapsibleCheckboxPreferenceGroup; import com.android.settings.PhotosDeletionPreference; import com.android.settings.SettingsPreferenceFragment; import com.android.settings.R; @@ -65,7 +66,7 @@ public class DeletionHelperFragment extends SettingsPreferenceFragment implement private static final int DOWNLOADS_LOADER_ID = 1; private Button mCancel, mFree; - private PreferenceGroup mApps; + private CollapsibleCheckboxPreferenceGroup mApps; private PhotosDeletionPreference mPhotoPreference; private DownloadsDeletionPreference mDownloadsPreference; @@ -82,6 +83,7 @@ public class DeletionHelperFragment extends SettingsPreferenceFragment implement @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setAnimationAllowed(true); Application app = getActivity().getApplication(); mState = ApplicationsState.getInstance(app); mSession = mState.newSession(this); @@ -89,7 +91,7 @@ public class DeletionHelperFragment extends SettingsPreferenceFragment implement mDataUsageBridge = new AppStateUsageStatsBridge(getActivity(), mState, this); addPreferencesFromResource(R.xml.deletion_helper_list); - mApps = (PreferenceGroup) findPreference(KEY_APPS_GROUP); + mApps = (CollapsibleCheckboxPreferenceGroup) findPreference(KEY_APPS_GROUP); mPhotoPreference = (PhotosDeletionPreference) findPreference(KEY_PHOTOS_VIDEOS_PREFERENCE); mDownloadsPreference = (DownloadsDeletionPreference) findPreference(KEY_DOWNLOADS_PREFERENCE); @@ -131,6 +133,7 @@ public class DeletionHelperFragment extends SettingsPreferenceFragment implement mDownloadsPreference.registerFreeableChangedListener(this); mDownloadsPreference.registerDeletionService(mDownloadsDeletion); + mApps.setOnPreferenceChangeListener(this); } @Override @@ -191,24 +194,7 @@ public class DeletionHelperFragment extends SettingsPreferenceFragment implement ApplicationsState.SIZE_COMPARATOR); if (apps == null) return; mAppEntries = apps; - cacheRemoveAllPrefs(mApps); - int entryCount = apps.size(); - for (int i = 0; i < entryCount; i++) { - AppEntry entry = apps.get(i); - final String packageName = entry.label; - AppDeletionPreference preference = - (AppDeletionPreference) getCachedPreference(entry.label); - if (preference == null) { - preference = new AppDeletionPreference(getActivity(), entry, - mState); - preference.setKey(packageName); - preference.setChecked(mCheckedApplications.contains(packageName)); - preference.setOnPreferenceChangeListener(this); - mApps.addPreference(preference); - } - preference.setOrder(i); - } - removeCachedPrefs(mApps); + refreshAppGroup(apps); // All applications should be filled in if we've received the sizes. // setLoading being called multiple times causes flickering, so we only do it once. @@ -277,11 +263,20 @@ public class DeletionHelperFragment extends SettingsPreferenceFragment implement @Override public boolean onPreferenceChange(Preference preference, Object newValue) { boolean checked = (boolean) newValue; + if (preference.getKey().equals(mApps.getKey())) { + return toggleAllApps(checked); + } + String packageName = ((AppDeletionPreference) preference).getPackageName(); if (checked) { mCheckedApplications.add(packageName); } else { mCheckedApplications.remove(packageName); + + // We remove the preference change listener to avoid toggling every app on and off. + mApps.setOnPreferenceChangeListener(null); + mApps.setChecked(false); + mApps.setOnPreferenceChangeListener(this); } updateFreeButtonText(); return true; @@ -346,17 +341,7 @@ public class DeletionHelperFragment extends SettingsPreferenceFragment implement private long getTotalFreeableSpace() { long freeableSpace = 0; - 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 (mCheckedApplications.contains(entry.label) && entrySize > 0) { - freeableSpace += entrySize; - } - } - } + freeableSpace += getTotalAppsFreeableSpace(false); if (mPhotoPreference != null) { freeableSpace += mPhotoPreference.getFreeableBytes(); } @@ -365,4 +350,67 @@ public class DeletionHelperFragment extends SettingsPreferenceFragment implement } return freeableSpace; } + + private void refreshAppGroup(ArrayList apps) { + int entryCount = apps.size(); + cacheRemoveAllPrefs(mApps); + for (int i = 0; i < entryCount; i++) { + AppEntry entry = apps.get(i); + final String packageName = entry.label; + AppDeletionPreference preference = + (AppDeletionPreference) getCachedPreference(entry.label); + if (preference == null) { + preference = new AppDeletionPreference(getActivity(), entry, mState); + preference.setKey(packageName); + preference.setOnPreferenceChangeListener(this); + mApps.addPreference(preference); + } + preference.setChecked(mCheckedApplications.contains(packageName)); + preference.setOrder(i); + } + removeCachedPrefs(mApps); + updateAppsGroupText(); + } + + private long getTotalAppsFreeableSpace(boolean countUnchecked) { + long freeableSpace = 0; + 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 ((countUnchecked || + mCheckedApplications.contains(entry.label)) && entrySize > 0) { + freeableSpace += entrySize; + } + } + } + + return freeableSpace; + } + + private void updateAppsGroupText() { + if (mAppEntries != null) { + Activity app = getActivity(); + mApps.setTitle(app.getString(R.string.deletion_helper_apps_group_title, + mAppEntries.size())); + mApps.setSummary(app.getString(R.string.deletion_helper_apps_group_summary, + Formatter.formatFileSize(app, + getTotalAppsFreeableSpace(true)))); + } + } + + private boolean toggleAllApps(boolean checked) { + for (AppEntry entry : mAppEntries) { + final String packageName = entry.label; + if (checked) { + mCheckedApplications.add(packageName); + } else { + mCheckedApplications.remove(packageName); + } + } + refreshAppGroup(mAppEntries); + updateFreeButtonText(); + return true; + } } \ No newline at end of file