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