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