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 package com.android.car.ui.utils;
17 
18 import android.app.Activity;
19 import android.content.Context;
20 import android.content.ContextWrapper;
21 import android.content.res.Resources;
22 import android.content.res.TypedArray;
23 import android.graphics.drawable.Drawable;
24 import android.util.TypedValue;
25 import android.view.View;
26 import android.view.ViewGroup;
27 
28 import androidx.annotation.DimenRes;
29 import androidx.annotation.IdRes;
30 import androidx.annotation.NonNull;
31 import androidx.annotation.Nullable;
32 import androidx.annotation.StyleRes;
33 import androidx.annotation.UiThread;
34 import androidx.core.view.ViewCompat;
35 
36 /**
37  * Collection of utility methods
38  */
39 public final class CarUiUtils {
40     /** This is a utility class */
CarUiUtils()41     private CarUiUtils() {
42     }
43 
44     /**
45      * Reads a float value from a dimens resource. This is necessary as {@link Resources#getFloat}
46      * is not currently public.
47      *
48      * @param res {@link Resources} to read values from
49      * @param resId Id of the dimens resource to read
50      */
getFloat(Resources res, @DimenRes int resId)51     public static float getFloat(Resources res, @DimenRes int resId) {
52         TypedValue outValue = new TypedValue();
53         res.getValue(resId, outValue, true);
54         return outValue.getFloat();
55     }
56 
57     /** Returns the identifier of the resolved resource assigned to the given attribute. */
getAttrResourceId(Context context, int attr)58     public static int getAttrResourceId(Context context, int attr) {
59         return getAttrResourceId(context, /*styleResId=*/ 0, attr);
60     }
61 
62     /**
63      * Returns the identifier of the resolved resource assigned to the given attribute defined in
64      * the given style.
65      */
getAttrResourceId(Context context, @StyleRes int styleResId, int attr)66     public static int getAttrResourceId(Context context, @StyleRes int styleResId, int attr) {
67         TypedArray ta = context.obtainStyledAttributes(styleResId, new int[]{attr});
68         int resId = ta.getResourceId(0, 0);
69         ta.recycle();
70         return resId;
71     }
72 
73     /**
74      * Gets the {@link Activity} for a certain {@link Context}.
75      *
76      * <p>It is possible the Context is not associated with an Activity, in which case
77      * this method will return null.
78      */
79     @Nullable
getActivity(Context context)80     public static Activity getActivity(Context context) {
81         while (context instanceof ContextWrapper) {
82             if (context instanceof Activity) {
83                 return (Activity) context;
84             }
85             context = ((ContextWrapper) context).getBaseContext();
86         }
87         return null;
88     }
89 
90     /**
91      * Updates the preference view enabled state. If the view is disabled we just disable the child
92      * of preference like TextView, ImageView. The preference itself is always enabled to get the
93      * click events. Ripple effect in background is also removed by default. If the ripple is
94      * needed see
95      * {@link IDisabledPreferenceCallback#setShouldShowRippleOnDisabledPreference(boolean)}
96      */
setPreferenceViewEnabled(boolean viewEnabled, View itemView, Drawable background, boolean shouldShowRippleOnDisabledPreference)97     public static Drawable setPreferenceViewEnabled(boolean viewEnabled, View itemView,
98             Drawable background, boolean shouldShowRippleOnDisabledPreference) {
99         if (viewEnabled) {
100             if (background != null) {
101                 ViewCompat.setBackground(itemView, background);
102             }
103             setChildViewsEnabled(itemView, true, false);
104         } else {
105             itemView.setEnabled(true);
106             if (background == null) {
107                 // store the original background.
108                 background = itemView.getBackground();
109             }
110             updateRippleStateOnDisabledPreference(false, shouldShowRippleOnDisabledPreference,
111                     background, itemView);
112             setChildViewsEnabled(itemView, false, true);
113         }
114         return background;
115     }
116 
117     /**
118      * Sets the enabled state on the views of the preference. If the view is being disabled we want
119      * only child views of preference to be disabled.
120      */
setChildViewsEnabled(View view, boolean enabled, boolean isRootView)121     private static void setChildViewsEnabled(View view, boolean enabled, boolean isRootView) {
122         if (!isRootView) {
123             view.setEnabled(enabled);
124         }
125         if (view instanceof ViewGroup) {
126             ViewGroup grp = (ViewGroup) view;
127             for (int index = 0; index < grp.getChildCount(); index++) {
128                 setChildViewsEnabled(grp.getChildAt(index), enabled, false);
129             }
130         }
131     }
132 
133     /**
134      * Updates the ripple state on the given preference.
135      *
136      * @param isEnabled whether the preference is enabled or not
137      * @param shouldShowRippleOnDisabledPreference should ripple be displayed when the preference is
138      * clicked
139      * @param background drawable that represents the ripple
140      * @param preference preference on which drawable will be applied
141      */
updateRippleStateOnDisabledPreference(boolean isEnabled, boolean shouldShowRippleOnDisabledPreference, Drawable background, View preference)142     public static void updateRippleStateOnDisabledPreference(boolean isEnabled,
143             boolean shouldShowRippleOnDisabledPreference, Drawable background, View preference) {
144         if (isEnabled || preference == null) {
145             return;
146         }
147         if (shouldShowRippleOnDisabledPreference && background != null) {
148             ViewCompat.setBackground(preference, background);
149         } else {
150             ViewCompat.setBackground(preference, null);
151         }
152     }
153 
154     /**
155      * It behaves similar to @see View#findViewById, except it resolves the ID reference first.
156      *
157      * @param id the ID to search for
158      * @return a view with given ID if found, or {@code null} otherwise
159      * @see View#requireViewById(int)
160      */
161     @Nullable
162     @UiThread
findViewByRefId(@onNull View root, @IdRes int id)163     public static <T extends View> T findViewByRefId(@NonNull View root, @IdRes int id) {
164         if (id == View.NO_ID) {
165             return null;
166         }
167 
168         TypedValue value = new TypedValue();
169         root.getResources().getValue(id, value, true);
170         return root.findViewById(value.resourceId);
171     }
172 
173     /**
174      * It behaves similar to @see View#requireViewById, except it resolves the ID reference first.
175      *
176      * @param id the ID to search for
177      * @return a view with given ID
178      * @see View#findViewById(int)
179      */
180     @NonNull
181     @UiThread
requireViewByRefId(@onNull View root, @IdRes int id)182     public static <T extends View> T requireViewByRefId(@NonNull View root, @IdRes int id) {
183         T view = findViewByRefId(root, id);
184         if (view == null) {
185             throw new IllegalArgumentException("ID "
186                     + root.getResources().getResourceName(id)
187                     + " does not reference a View inside this View");
188         }
189         return view;
190     }
191 }
192