1 /*
2  * Copyright (C) 2019 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.car.apps.common;
18 
19 import android.car.drivingstate.CarUxRestrictions;
20 import android.content.Context;
21 import android.content.res.TypedArray;
22 import android.os.Handler;
23 import android.util.AttributeSet;
24 import android.view.View;
25 import android.widget.TextView;
26 import android.widget.Toast;
27 
28 import androidx.annotation.Nullable;
29 
30 import com.android.car.ui.utils.CarUxRestrictionsUtil;
31 
32 /**
33  * UX Restrictions compliant Button.
34  * This class will automatically listen to Car UXRestrictions, and respond to click event
35  * accordingly. You can set one or multiple restrictions in the layout file, e.g.,
36  * app:carUxRestrictions="UX_RESTRICTIONS_NO_SETUP|UX_RESTRICTIONS_NO_KEYBOARD"
37  * If not set, it'll use UX_RESTRICTIONS_FULLY_RESTRICTED as fallback.
38  * If no restriction is enforced, this Button will work as a normal Button; otherwise, its
39  * OnClickListener will be disabled if any, and a blocking message will be displayed.
40  *
41  * This class extends from TextView instead of Button because only TextView supports gradient
42  * truncate for now.
43  */
44 public class UxrButton extends TextView {
45     private static final int[] STATE_UX_RESTRICTED = {R.attr.state_ux_restricted};
46 
47     private CarUxRestrictionsUtil mCarUxRestrictionsUtil;
48     private CarUxRestrictions mActiveCarUxRestrictions;
49     private View.OnClickListener mOnClickListenerDelegate;
50 
51     @CarUxRestrictions.CarUxRestrictionsInfo
52     private int mRestrictions;
53 
54     private final Handler mHandler = new Handler();
55 
56     private final CarUxRestrictionsUtil.OnUxRestrictionsChangedListener mListener =
57             this::updateActiveCarUxRestrictions;
58 
59     private final View.OnClickListener mOnClickListenerWrapper = (View v) -> {
60         if (mOnClickListenerDelegate == null) {
61             return;
62         }
63         if (isRestricted()) {
64             showBlockingMessage();
65         } else {
66             mOnClickListenerDelegate.onClick(v);
67         }
68     };
69 
UxrButton(Context context)70     public UxrButton(Context context) {
71         super(context);
72         init(context, null);
73     }
74 
UxrButton(Context context, AttributeSet attrs)75     public UxrButton(Context context, AttributeSet attrs) {
76         super(context, attrs);
77         init(context, attrs);
78     }
79 
UxrButton(Context context, AttributeSet attrs, int defStyleAttr)80     public UxrButton(Context context, AttributeSet attrs, int defStyleAttr) {
81         super(context, attrs, defStyleAttr);
82         init(context, attrs);
83     }
84 
UxrButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)85     public UxrButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
86         super(context, attrs, defStyleAttr, defStyleRes);
87         init(context, attrs);
88     }
89 
init(Context context, AttributeSet attrs)90     private void init(Context context, AttributeSet attrs) {
91         mCarUxRestrictionsUtil = CarUxRestrictionsUtil.getInstance(context);
92         super.setOnClickListener(mOnClickListenerWrapper);
93         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.UxrButton);
94         try {
95             mRestrictions = a.getInteger(R.styleable.UxrButton_carUxRestrictions,
96                     CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED);
97         } finally {
98             a.recycle();
99         }
100     }
101 
102     @Override
onCreateDrawableState(int extraSpace)103     public int[] onCreateDrawableState(int extraSpace) {
104         final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
105         if (isRestricted()) {
106             mergeDrawableStates(drawableState, STATE_UX_RESTRICTED);
107         }
108         return drawableState;
109     }
110 
111     @Override
setOnClickListener(@ullable View.OnClickListener listener)112     public void setOnClickListener(@Nullable View.OnClickListener listener) {
113         mOnClickListenerDelegate = listener;
114     }
115 
116     @Override
onAttachedToWindow()117     protected void onAttachedToWindow() {
118         super.onAttachedToWindow();
119         mCarUxRestrictionsUtil.register(mListener);
120     }
121 
122     @Override
onDetachedFromWindow()123     protected void onDetachedFromWindow() {
124         super.onDetachedFromWindow();
125         mCarUxRestrictionsUtil.unregister(mListener);
126     }
127 
128     /**
129      * Set the UX restriction mode for this button
130      */
setUxRestrictions(int uxRestrictions)131     public void setUxRestrictions(int uxRestrictions) {
132         mRestrictions = uxRestrictions;
133         mHandler.post(() -> refreshDrawableState());
134     }
135 
isRestricted()136     private boolean isRestricted() {
137         return CarUxRestrictionsUtil.isRestricted(mRestrictions, mActiveCarUxRestrictions);
138     }
139 
updateActiveCarUxRestrictions(CarUxRestrictions carUxRestrictions)140     private void updateActiveCarUxRestrictions(CarUxRestrictions carUxRestrictions) {
141         mActiveCarUxRestrictions = carUxRestrictions;
142         mHandler.post(() -> refreshDrawableState());
143     }
144 
145     /**
146      * Shows a message to inform the user that the current feature is not available when driving.
147      */
showBlockingMessage()148     protected void showBlockingMessage() {
149         Toast.makeText(getContext(), R.string.restricted_while_driving,
150                 Toast.LENGTH_SHORT).show();
151     }
152 }
153