1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.storagemanager.deletionhelper; 18 19 import android.content.Context; 20 import android.os.Parcel; 21 import android.os.Parcelable; 22 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; 23 import androidx.preference.Preference; 24 import androidx.preference.PreferenceGroup; 25 import androidx.preference.PreferenceViewHolder; 26 import android.util.AttributeSet; 27 28 import android.view.View; 29 import android.view.ViewGroup; 30 import android.widget.Checkable; 31 32 import android.widget.ImageView; 33 import android.widget.ProgressBar; 34 import android.widget.TextView; 35 import com.android.internal.annotations.VisibleForTesting; 36 import com.android.storagemanager.R; 37 import com.android.storagemanager.deletionhelper.DeletionType.LoadingStatus; 38 39 /** 40 * CollapsibleCheckboxPreferenceGroup is a preference group that can be expanded or collapsed and 41 * also has a checkbox. 42 */ 43 public class CollapsibleCheckboxPreferenceGroup extends PreferenceGroup implements 44 View.OnClickListener { 45 private boolean mCollapsed; 46 private boolean mChecked; 47 private TextView mTextView; 48 private ProgressBar mProgressBar; 49 private View mWidget; 50 private boolean mLoaded; 51 CollapsibleCheckboxPreferenceGroup(Context context)52 public CollapsibleCheckboxPreferenceGroup(Context context) { 53 this(context, null); 54 } 55 CollapsibleCheckboxPreferenceGroup(Context context, AttributeSet attrs)56 public CollapsibleCheckboxPreferenceGroup(Context context, AttributeSet attrs) { 57 super(context, attrs); 58 setLayoutResource(R.layout.deletion_preference); 59 setWidgetLayoutResource(androidx.preference.R.layout.preference_widget_checkbox); 60 } 61 62 @Override onBindViewHolder(PreferenceViewHolder holder)63 public void onBindViewHolder(PreferenceViewHolder holder) { 64 super.onBindViewHolder(holder); 65 View checkbox = holder.findViewById(com.android.internal.R.id.checkbox); 66 mTextView = (TextView) holder.findViewById(android.R.id.summary); 67 // Ensures that the color of the text is consistent with the checkbox having a tick or not 68 mTextView.setActivated(mChecked); 69 if (checkbox != null && checkbox instanceof Checkable) { 70 ((Checkable) checkbox).setChecked(mChecked); 71 72 // Expand the touch target by making the parent the touch target. 73 View parent = (View) checkbox.getParent(); 74 parent.setClickable(true); 75 parent.setFocusable(true); 76 parent.setOnClickListener(this); 77 } 78 mProgressBar = (ProgressBar) holder.findViewById(R.id.progress_bar); 79 mProgressBar.setVisibility(mLoaded ? View.GONE : View.VISIBLE); 80 mWidget = holder.findViewById(android.R.id.widget_frame); 81 mWidget.setVisibility(mLoaded ? View.VISIBLE : View.GONE); 82 83 // CollapsibleCheckboxPreferenceGroup considers expansion to be its "longer-term 84 // (activation) state." 85 final ImageView imageView = (ImageView) holder.findViewById(android.R.id.icon); 86 imageView.setActivated(!mCollapsed); 87 } 88 89 @Override addPreference(Preference p)90 public boolean addPreference(Preference p) { 91 super.addPreference(p); 92 p.setVisible(!isCollapsed()); 93 return true; 94 } 95 96 // The preference click handler. 97 @Override onClick()98 protected void onClick() { 99 super.onClick(); 100 setCollapse(!isCollapsed()); 101 } 102 103 // The checkbox view click handler. 104 @Override onClick(View v)105 public void onClick(View v) { 106 super.onClick(); 107 setChecked(!isChecked()); 108 109 // We need to find the CheckBox in the parent view that we are using as a touch target. 110 // If we don't update it before onClick finishes, the accessibility gives invalid 111 // responses. 112 ViewGroup parent = (ViewGroup) v; 113 View child = parent.findViewById(com.android.internal.R.id.checkbox); 114 Checkable checkable = (Checkable) child; 115 checkable.setChecked(mChecked); 116 // Causes text color change when activated to differentiate selected elements from 117 // unselected elements. 118 mTextView.setActivated(mChecked); 119 } 120 121 /** 122 * Return if the view is collapsed. 123 */ isCollapsed()124 public boolean isCollapsed() { 125 return mCollapsed; 126 } 127 128 /** 129 * Returns the checked state of the preference. 130 */ isChecked()131 public boolean isChecked() { 132 return mChecked; 133 } 134 135 /** 136 * Sets the checked state and notifies listeners of the state change. 137 */ setChecked(boolean checked)138 public void setChecked(boolean checked) { 139 if (mChecked != checked) { 140 mChecked = checked; 141 142 callChangeListener(checked); 143 notifyDependencyChange(shouldDisableDependents()); 144 notifyChanged(); 145 } 146 } 147 148 @Override onInitializeAccessibilityNodeInfo(AccessibilityNodeInfoCompat info)149 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfoCompat info) { 150 super.onInitializeAccessibilityNodeInfo(info); 151 info.setCheckable(true); 152 info.setChecked(isChecked()); 153 } 154 155 @Override onSaveInstanceState()156 protected Parcelable onSaveInstanceState() { 157 final Parcelable superState = super.onSaveInstanceState(); 158 159 final SavedState myState = new SavedState(superState); 160 myState.checked = isChecked(); 161 myState.collapsed = isCollapsed(); 162 return myState; 163 } 164 165 @Override onRestoreInstanceState(Parcelable state)166 protected void onRestoreInstanceState(Parcelable state) { 167 // Only restore the state if it is valid and our saved state. 168 if (state == null || !SavedState.class.equals(state.getClass())) { 169 super.onRestoreInstanceState(state); 170 return; 171 } 172 173 SavedState myState = (SavedState) state; 174 super.onRestoreInstanceState(myState.getSuperState()); 175 setChecked(myState.checked); 176 setCollapse(myState.collapsed); 177 } 178 setCollapse(boolean isCollapsed)179 private void setCollapse(boolean isCollapsed) { 180 if (mCollapsed == isCollapsed) { 181 return; 182 } 183 184 mCollapsed = isCollapsed; 185 setAllPreferencesVisibility(!isCollapsed); 186 notifyChanged(); 187 } 188 setAllPreferencesVisibility(boolean visible)189 private void setAllPreferencesVisibility(boolean visible) { 190 for (int i = 0; i < getPreferenceCount(); i++) { 191 Preference p = getPreference(i); 192 p.setVisible(visible); 193 } 194 } 195 196 private static class SavedState extends BaseSavedState { 197 boolean checked; 198 boolean collapsed; 199 SavedState(Parcel source)200 public SavedState(Parcel source) { 201 super(source); 202 checked = source.readInt() != 0; 203 collapsed = source.readInt() != 0; 204 } 205 SavedState(Parcelable superState)206 public SavedState(Parcelable superState) { 207 super(superState); 208 } 209 210 @Override writeToParcel(Parcel dest, int flags)211 public void writeToParcel(Parcel dest, int flags) { 212 super.writeToParcel(dest, flags); 213 dest.writeInt(checked ? 1 : 0); 214 dest.writeInt(collapsed ? 1 : 0); 215 } 216 217 public static final Parcelable.Creator<SavedState> CREATOR = 218 new Parcelable.Creator<SavedState>() { 219 public SavedState createFromParcel(Parcel in) { 220 return new SavedState(in); 221 } 222 223 public SavedState[] newArray(int size) { 224 return new SavedState[size]; 225 } 226 }; 227 } 228 229 @VisibleForTesting switchSpinnerToCheckboxOrDisablePreference(long freeableBytes, int loadingStatus)230 void switchSpinnerToCheckboxOrDisablePreference(long freeableBytes, int loadingStatus) { 231 mLoaded = loadingStatus != LoadingStatus.LOADING; 232 setEnabled(loadingStatus != LoadingStatus.EMPTY); 233 if (!isEnabled()) { 234 setChecked(false); 235 } 236 if (mProgressBar != null) { 237 mProgressBar.setVisibility(View.GONE); 238 } 239 if (mWidget != null) { 240 mWidget.setVisibility(View.VISIBLE); 241 } 242 } 243 } 244