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