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(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