• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Bitmap;
24 import android.graphics.Canvas;
25 import android.graphics.drawable.BitmapDrawable;
26 import android.graphics.drawable.Drawable;
27 import android.os.Build;
28 import android.text.TextUtils;
29 import android.util.Log;
30 import android.util.SparseArray;
31 import android.util.TypedValue;
32 import android.view.View;
33 import android.view.ViewGroup;
34 
35 import androidx.annotation.DimenRes;
36 import androidx.annotation.IdRes;
37 import androidx.annotation.NonNull;
38 import androidx.annotation.Nullable;
39 import androidx.annotation.StyleRes;
40 import androidx.annotation.UiThread;
41 
42 import com.android.car.ui.R;
43 import com.android.car.ui.uxr.DrawableStateView;
44 
45 import java.lang.reflect.Method;
46 import java.util.ArrayList;
47 import java.util.List;
48 import java.util.function.Function;
49 
50 /**
51  * Collection of utility methods
52  */
53 @SuppressWarnings("AndroidJdkLibsChecker")
54 public final class CarUiUtils {
55 
56     private static final String TAG = "CarUiUtils";
57     private static final String READ_ONLY_SYSTEM_PROPERTY_PREFIX = "ro.";
58     /** A map to cache read-only system properties. */
59     private static final SparseArray<String> READ_ONLY_SYSTEM_PROPERTY_MAP = new SparseArray<>();
60 
61     private static int[] sRestrictedState;
62 
63     /** This is a utility class */
CarUiUtils()64     private CarUiUtils() {
65     }
66 
67     /**
68      * Reads a float value from a dimens resource. This is necessary as {@link Resources#getFloat}
69      * is not currently public.
70      *
71      * @param res   {@link Resources} to read values from
72      * @param resId Id of the dimens resource to read
73      */
getFloat(Resources res, @DimenRes int resId)74     public static float getFloat(Resources res, @DimenRes int resId) {
75         TypedValue outValue = new TypedValue();
76         res.getValue(resId, outValue, true);
77         return outValue.getFloat();
78     }
79 
80     /** Returns the identifier of the resolved resource assigned to the given attribute. */
getAttrResourceId(Context context, int attr)81     public static int getAttrResourceId(Context context, int attr) {
82         return getAttrResourceId(context, /*styleResId=*/ 0, attr);
83     }
84 
85     /**
86      * Returns the identifier of the resolved resource assigned to the given attribute defined in
87      * the given style.
88      */
getAttrResourceId(Context context, @StyleRes int styleResId, int attr)89     public static int getAttrResourceId(Context context, @StyleRes int styleResId, int attr) {
90         TypedArray ta = context.obtainStyledAttributes(styleResId, new int[]{attr});
91         int resId = ta.getResourceId(0, 0);
92         ta.recycle();
93         return resId;
94     }
95 
96     /**
97      * Gets the {@link Activity} for a certain {@link Context}.
98      *
99      * <p>It is possible the Context is not associated with an Activity, in which case
100      * this method will return null.
101      */
102     @Nullable
getActivity(@ullable Context context)103     public static Activity getActivity(@Nullable Context context) {
104         while (context instanceof ContextWrapper) {
105             if (context instanceof Activity) {
106                 return (Activity) context;
107             }
108             context = ((ContextWrapper) context).getBaseContext();
109         }
110         return null;
111     }
112 
113     /**
114      * It behaves similarly to {@link View#findViewById(int)}, except that on Q and below,
115      * it will first resolve the id to whatever it references.
116      *
117      * This is to support layout RROs before the new RRO features in R.
118      *
119      * @param id the ID to search for
120      * @return a view with given ID if found, or {@code null} otherwise
121      * @see View#requireViewById(int)
122      */
123     @Nullable
124     @UiThread
findViewByRefId(@onNull View root, @IdRes int id)125     public static <T extends View> T findViewByRefId(@NonNull View root, @IdRes int id) {
126         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
127             return root.findViewById(id);
128         }
129 
130         if (id == View.NO_ID) {
131             return null;
132         }
133 
134         TypedValue value = new TypedValue();
135         root.getResources().getValue(id, value, true);
136         return root.findViewById(value.resourceId);
137     }
138 
139     /**
140      * It behaves similarly to {@link View#requireViewById(int)}, except that on Q and below,
141      * it will first resolve the id to whatever it references.
142      *
143      * This is to support layout RROs before the new RRO features in R.
144      *
145      * @param id the ID to search for
146      * @return a view with given ID
147      * @see View#findViewById(int)
148      */
149     @NonNull
150     @UiThread
requireViewByRefId(@onNull View root, @IdRes int id)151     public static <T extends View> T requireViewByRefId(@NonNull View root, @IdRes int id) {
152         T view = findViewByRefId(root, id);
153         if (view == null) {
154             throw new IllegalArgumentException("ID "
155                     + root.getResources().getResourceName(id)
156                     + " does not reference a View inside this View");
157         }
158         return view;
159     }
160 
161     /**
162      * Returns the system property of type boolean. This method converts the boolean value in string
163      * returned by {@link #getSystemProperty(Resources, int)}
164      */
getBooleanSystemProperty( @onNull Resources resources, int propertyResId, boolean defaultValue)165     public static boolean getBooleanSystemProperty(
166             @NonNull Resources resources, int propertyResId, boolean defaultValue) {
167         String value = getSystemProperty(resources, propertyResId);
168 
169         if (!TextUtils.isEmpty(value)) {
170             return Boolean.parseBoolean(value);
171         }
172         return defaultValue;
173     }
174 
175     /**
176      * Use reflection to interact with the hidden API <code>android.os.SystemProperties</code>.
177      *
178      * <p>This method caches read-only properties. CAVEAT: Please do not set read-only properties
179      * by 'adb setprop' after app started. Read-only properties CAN BE SET ONCE if it is unset.
180      * Thus, read-only properties MAY BE CHANGED from unset to set during application's lifetime if
181      * you use 'adb setprop' command to set read-only properties after app started. For the sake of
182      * performance, this method also caches the unset state. Otherwise, cache may not effective if
183      * the system property is unset (which is most-likely).
184      *
185      * @param resources     resources object to fetch string
186      * @param propertyResId the property resource id.
187      * @return The value of the property if defined, else null. Does not return empty strings.
188      */
189     @Nullable
getSystemProperty(@onNull Resources resources, int propertyResId)190     public static String getSystemProperty(@NonNull Resources resources, int propertyResId) {
191         String propertyName = resources.getString(propertyResId);
192         boolean isReadOnly = propertyName.startsWith(READ_ONLY_SYSTEM_PROPERTY_PREFIX);
193         if (!isReadOnly) {
194             return readSystemProperty(propertyName);
195         }
196         synchronized (READ_ONLY_SYSTEM_PROPERTY_MAP) {
197             // readOnlySystemPropertyMap may contain null values.
198             if (READ_ONLY_SYSTEM_PROPERTY_MAP.indexOfKey(propertyResId) >= 0) {
199                 return READ_ONLY_SYSTEM_PROPERTY_MAP.get(propertyResId);
200             }
201             String value = readSystemProperty(propertyName);
202             READ_ONLY_SYSTEM_PROPERTY_MAP.put(propertyResId, value);
203             return value;
204         }
205     }
206 
207     @Nullable
readSystemProperty(String propertyName)208     private static String readSystemProperty(String propertyName) {
209         Class<?> systemPropertiesClass;
210         try {
211             systemPropertiesClass = Class.forName("android.os.SystemProperties");
212         } catch (ClassNotFoundException e) {
213             Log.w(TAG, "Cannot find android.os.SystemProperties: ", e);
214             return null;
215         }
216 
217         Method getMethod;
218         try {
219             getMethod = systemPropertiesClass.getMethod("get", String.class);
220         } catch (NoSuchMethodException e) {
221             Log.w(TAG, "Cannot find SystemProperties.get(): ", e);
222             return null;
223         }
224 
225         try {
226             Object[] params = new Object[]{propertyName};
227             String value = (String) getMethod.invoke(systemPropertiesClass, params);
228             return TextUtils.isEmpty(value) ? null : value;
229         } catch (Exception e) {
230             Log.w(TAG, "Failed to invoke SystemProperties.get(): ", e);
231             return null;
232         }
233     }
234 
235     /**
236      * Converts a drawable to bitmap. This value should not be null.
237      */
drawableToBitmap(@onNull Drawable drawable)238     public static Bitmap drawableToBitmap(@NonNull Drawable drawable) {
239         Bitmap bitmap;
240 
241         if (drawable instanceof BitmapDrawable) {
242             BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
243             if (bitmapDrawable.getBitmap() != null) {
244                 return bitmapDrawable.getBitmap();
245             }
246         }
247 
248         if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
249             bitmap = Bitmap.createBitmap(1, 1,
250                     Bitmap.Config.ARGB_8888); // Single color bitmap will be created of 1x1 pixel
251         } else {
252             bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
253                     drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
254         }
255 
256         Canvas canvas = new Canvas(bitmap);
257         drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
258         drawable.draw(canvas);
259         return bitmap;
260     }
261 
262     /**
263      * Exact copy from Androidx.TypedArrayUtils class
264      * @return The resource ID value in the {@code context} specified by {@code attr}. If it does
265      * not exist, {@code fallbackAttr}.
266      */
getAttr(@onNull Context context, int attr, int fallbackAttr)267     public static int getAttr(@NonNull Context context, int attr, int fallbackAttr) {
268         TypedValue value = new TypedValue();
269         context.getTheme().resolveAttribute(attr, value, true);
270         if (value.resourceId != 0) {
271             return attr;
272         }
273         return fallbackAttr;
274     }
275 
276     /**
277      * Converts a {@link CharSequence} to a {@link String}.
278      *
279      * This is the same as calling {@link CharSequence#toString()}, except it will handle
280      * null CharSequences, returning a null string.
281      */
charSequenceToString(@ullable CharSequence charSequence)282     public static String charSequenceToString(@Nullable CharSequence charSequence) {
283         return charSequence == null ? null : charSequence.toString();
284     }
285 
286     /**
287      * Given a list of T and a function to convert from T to U, return a list of U.
288      *
289      * This will create a new list.
290      */
convertList(List<T> list, Function<T, U> f)291     public static <T, U> List<U> convertList(List<T> list, Function<T, U> f) {
292         if (list == null) {
293             return null;
294         }
295 
296         List<U> result = new ArrayList<>();
297         for (T item : list) {
298             result.add(f.apply(item));
299         }
300         return result;
301     }
302 
303     /**
304      * Traverses the view hierarchy, and whenever it sees a {@link DrawableStateView}, adds
305      * state_ux_restricted to it.
306      *
307      * Note that this will remove any other drawable states added by other calls to
308      * {@link DrawableStateView#setExtraDrawableState(int[], int[])}
309      */
makeAllViewsUxRestricted(@ullable View view, boolean restricted)310     public static void makeAllViewsUxRestricted(@Nullable View view, boolean restricted) {
311         if (view instanceof DrawableStateView) {
312             if (sRestrictedState == null) {
313                 int androidStateUxRestricted = view.getResources()
314                         .getIdentifier("state_ux_restricted", "attr", "android");
315 
316                 if (androidStateUxRestricted == 0) {
317                     sRestrictedState = new int[] { R.attr.state_ux_restricted };
318                 } else {
319                     sRestrictedState = new int[] {
320                             R.attr.state_ux_restricted,
321                             androidStateUxRestricted
322                     };
323                 }
324             }
325 
326             ((DrawableStateView) view).setExtraDrawableState(
327                     restricted ? sRestrictedState : null, null);
328         }
329 
330         if (view instanceof ViewGroup) {
331             ViewGroup vg = (ViewGroup) view;
332             for (int i = 0; i < vg.getChildCount(); i++) {
333                 makeAllViewsUxRestricted(vg.getChildAt(i), restricted);
334             }
335         }
336     }
337 }
338