1 /*
2  * Copyright (C) 2021 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.settings.widget;
18 
19 import android.content.Context;
20 import android.util.AttributeSet;
21 import android.view.View;
22 import android.view.ViewGroup;
23 import android.view.accessibility.AccessibilityEvent;
24 import android.view.accessibility.AccessibilityNodeInfo;
25 import android.widget.Checkable;
26 import android.widget.RelativeLayout;
27 
28 import androidx.annotation.NonNull;
29 import androidx.annotation.Nullable;
30 
31 /**
32  * A RelativeLayout which implements {@link Checkable}. With this implementation, it could be used
33  * in the list item layout for {@link android.widget.AbsListView} to change UI after item click.
34  * Its checked state would be propagated to the checkable child.
35  *
36  * <p>
37  * To support accessibility, the state description is from the checkable view and is
38  * changed with {@link #setChecked(boolean)}. We make the checkable child unclickable, unfocusable
39  * and non-important for accessibility, so that the announcement wouldn't include
40  * the checkable view.
41  * <
42  */
43 public class CheckableRelativeLayout extends RelativeLayout implements Checkable {
44 
45     private Checkable mCheckable;
46     private View mCheckableChild;
47     private boolean mChecked;
48 
CheckableRelativeLayout(Context context)49     public CheckableRelativeLayout(Context context) {
50         super(context);
51     }
52 
CheckableRelativeLayout(Context context, @Nullable AttributeSet attrs)53     public CheckableRelativeLayout(Context context, @Nullable AttributeSet attrs) {
54         super(context, attrs);
55     }
56 
57     @Override
onFinishInflate()58     protected void onFinishInflate() {
59         mCheckableChild = findFirstCheckableView(this);
60         if (mCheckableChild != null) {
61             mCheckableChild.setClickable(false);
62             mCheckableChild.setFocusable(false);
63             mCheckableChild.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
64             mCheckable = (Checkable) mCheckableChild;
65             mCheckable.setChecked(isChecked());
66             setStateDescriptionIfNeeded();
67         }
68         super.onFinishInflate();
69     }
70 
71     @Nullable
findFirstCheckableView(@onNull ViewGroup viewGroup)72     private static View findFirstCheckableView(@NonNull ViewGroup viewGroup) {
73         final int childCount = viewGroup.getChildCount();
74         for (int i = 0; i < childCount; i++) {
75             final View child = viewGroup.getChildAt(i);
76             if (child instanceof Checkable) {
77                 return child;
78             }
79             if (child instanceof ViewGroup) {
80                 findFirstCheckableView((ViewGroup) child);
81             }
82         }
83         return  null;
84     }
85 
86     @Override
setChecked(boolean checked)87     public void setChecked(boolean checked) {
88         if (mChecked != checked) {
89             mChecked = checked;
90             if (mCheckable != null) {
91                 mCheckable.setChecked(checked);
92             }
93         }
94         setStateDescriptionIfNeeded();
95         notifyViewAccessibilityStateChangedIfNeeded(
96                 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
97     }
98 
setStateDescriptionIfNeeded()99     private void setStateDescriptionIfNeeded() {
100         if (mCheckableChild == null) {
101             return;
102         }
103         setStateDescription(mCheckableChild.getStateDescription());
104     }
105 
106     @Override
isChecked()107     public boolean isChecked() {
108         return mChecked;
109     }
110 
111     @Override
toggle()112     public void toggle() {
113         setChecked(!mChecked);
114     }
115 
116     @Override
onInitializeAccessibilityEvent(AccessibilityEvent event)117     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
118         super.onInitializeAccessibilityEvent(event);
119         event.setChecked(mChecked);
120     }
121 
122     @Override
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)123     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
124         super.onInitializeAccessibilityNodeInfo(info);
125         info.setChecked(mChecked);
126     }
127 }
128