Add a collapsible checkbox preference for deletion helper.

This adds a custom preference group which has both a checkbox
and collapse/expand behavior. This is intended to be used in
the deletion helper for apps deletion and downloads folder
deletion.

This patch implements the apps deletion integration.

Bug: 28769691
Change-Id: I9fb28a1baa4067841742b5dbeaf2083728c16144
This commit is contained in:
Daniel Nishi
2016-05-12 13:07:46 -07:00
parent 0772471119
commit a779cd2b16
6 changed files with 286 additions and 35 deletions

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M7.41,7.84L12,12.42l4.59,-4.58L18,9.25l-6,6 -6,-6z"/>
</vector>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z"/>
</vector>

View File

@@ -7620,4 +7620,10 @@
<!-- Button to activate the storage manager on the storage manager upsell. [CHAR LIMIT=20]-->
<string name="deletion_helper_upsell_activate">Turn on</string>
<!-- Title for the apps category in the deletion helper, showing how many apps to delete. [CHAR LIMIT=40]-->
<string name="deletion_helper_apps_group_title">Apps (<xliff:g id="num_items">%1$d</xliff:g>)</string>
<!-- Summary for the apps category in the deletion helper, showing how many space to clear. [CHAR LIMIT=NONE]-->
<string name="deletion_helper_apps_group_summary"><xliff:g id="used" example="1.2GB">%1$s</xliff:g></string>
</resources>

View File

@@ -23,8 +23,8 @@
<com.android.settings.deletionhelper.DownloadsDeletionPreference
android:key="delete_downloads" />
<PreferenceCategory
android:key="apps_group"
android:title="@string/deletion_helper_apps_title" />
<com.android.settings.CollapsibleCheckboxPreferenceGroup
android:key="apps_group"
android:icon="@drawable/ic_keyboard_arrow_down_black_32"/>
</PreferenceScreen>

View File

@@ -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);
}
}
}

View File

@@ -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<AppEntry> 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;
}
}