1 /* 2 * Copyright (C) 2018 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.systemui.statusbar.car; 18 19 import android.content.Context; 20 import android.content.Intent; 21 import android.content.res.TypedArray; 22 import android.os.UserHandle; 23 import android.util.AttributeSet; 24 import android.view.Display; 25 import android.view.View; 26 import android.widget.ImageView; 27 import android.widget.LinearLayout; 28 29 import com.android.keyguard.AlphaOptimizedImageButton; 30 import com.android.systemui.CarSystemUIFactory; 31 import com.android.systemui.R; 32 import com.android.systemui.SystemUIFactory; 33 34 /** 35 * CarFacetButton is a ui component designed to be used as a shortcut for an app of a defined 36 * category. It can also render a indicator impling that there are more options of apps to launch 37 * using this component. This is done with a "More icon" currently an arrow as defined in the layout 38 * file. The class is to serve as an example. 39 * Usage example: A button that allows a user to select a music app and indicate that there are 40 * other music apps installed. 41 */ 42 public class CarFacetButton extends LinearLayout { 43 private static final String FACET_FILTER_DELIMITER = ";"; 44 /** 45 * Extra information to be sent to a helper to make the decision of what app to launch when 46 * clicked. 47 */ 48 private static final String EXTRA_FACET_CATEGORIES = "categories"; 49 private static final String EXTRA_FACET_PACKAGES = "packages"; 50 private static final String EXTRA_FACET_ID = "filter_id"; 51 private static final String EXTRA_FACET_LAUNCH_PICKER = "launch_picker"; 52 private static final String TAG = "CarFacetButton"; 53 54 private Context mContext; 55 private AlphaOptimizedImageButton mIcon; 56 private AlphaOptimizedImageButton mMoreIcon; 57 private boolean mSelected = false; 58 private String[] mComponentNames; 59 /** App categories that are to be used with this widget */ 60 private String[] mFacetCategories; 61 /** App packages that are allowed to be used with this widget */ 62 private String[] mFacetPackages; 63 private int mIconResourceId; 64 /** 65 * If defined in the xml this will be the icon that's rendered when the button is marked as 66 * selected 67 */ 68 private int mSelectedIconResourceId; 69 private boolean mUseMoreIcon = true; 70 private float mSelectedAlpha = 1f; 71 private float mUnselectedAlpha = 1f; 72 CarFacetButton(Context context, AttributeSet attrs)73 public CarFacetButton(Context context, AttributeSet attrs) { 74 super(context, attrs); 75 mContext = context; 76 View.inflate(context, R.layout.car_facet_button, this); 77 // extract custom attributes 78 TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CarFacetButton); 79 setupIntents(typedArray); 80 setupIcons(typedArray); 81 CarSystemUIFactory factory = SystemUIFactory.getInstance(); 82 CarFacetButtonController carFacetButtonController = factory.getCarDependencyComponent() 83 .getCarFacetButtonController(); 84 carFacetButtonController.addFacetButton(this); 85 } 86 87 /** 88 * Reads the custom attributes to setup click handlers for this component. 89 */ setupIntents(TypedArray typedArray)90 protected void setupIntents(TypedArray typedArray) { 91 String intentString = typedArray.getString(R.styleable.CarFacetButton_intent); 92 String longPressIntentString = typedArray.getString(R.styleable.CarFacetButton_longIntent); 93 String categoryString = typedArray.getString(R.styleable.CarFacetButton_categories); 94 String packageString = typedArray.getString(R.styleable.CarFacetButton_packages); 95 String componentNameString = 96 typedArray.getString(R.styleable.CarFacetButton_componentNames); 97 try { 98 final Intent intent = Intent.parseUri(intentString, Intent.URI_INTENT_SCHEME); 99 intent.putExtra(EXTRA_FACET_ID, Integer.toString(getId())); 100 101 if (packageString != null) { 102 mFacetPackages = packageString.split(FACET_FILTER_DELIMITER); 103 intent.putExtra(EXTRA_FACET_PACKAGES, mFacetPackages); 104 } 105 if (categoryString != null) { 106 mFacetCategories = categoryString.split(FACET_FILTER_DELIMITER); 107 intent.putExtra(EXTRA_FACET_CATEGORIES, mFacetCategories); 108 } 109 if (componentNameString != null) { 110 mComponentNames = componentNameString.split(FACET_FILTER_DELIMITER); 111 } 112 113 setOnClickListener(v -> { 114 intent.putExtra(EXTRA_FACET_LAUNCH_PICKER, mSelected); 115 mContext.startActivityAsUser(intent, UserHandle.CURRENT); 116 }); 117 118 if (longPressIntentString != null) { 119 final Intent longPressIntent = Intent.parseUri(longPressIntentString, 120 Intent.URI_INTENT_SCHEME); 121 setOnLongClickListener(v -> { 122 mContext.startActivityAsUser(longPressIntent, UserHandle.CURRENT); 123 return true; 124 }); 125 } 126 } catch (Exception e) { 127 throw new RuntimeException("Failed to attach intent", e); 128 } 129 } 130 setupIcons(TypedArray styledAttributes)131 private void setupIcons(TypedArray styledAttributes) { 132 mSelectedAlpha = styledAttributes.getFloat( 133 R.styleable.CarFacetButton_selectedAlpha, mSelectedAlpha); 134 mUnselectedAlpha = styledAttributes.getFloat( 135 R.styleable.CarFacetButton_unselectedAlpha, mUnselectedAlpha); 136 mIcon = findViewById(R.id.car_nav_button_icon); 137 mIcon.setScaleType(ImageView.ScaleType.CENTER); 138 mIcon.setClickable(false); 139 mIcon.setAlpha(mUnselectedAlpha); 140 mIconResourceId = styledAttributes.getResourceId(R.styleable.CarFacetButton_icon, 0); 141 mIcon.setImageResource(mIconResourceId); 142 mSelectedIconResourceId = styledAttributes.getResourceId( 143 R.styleable.CarFacetButton_selectedIcon, mIconResourceId); 144 145 mMoreIcon = findViewById(R.id.car_nav_button_more_icon); 146 mMoreIcon.setClickable(false); 147 mMoreIcon.setAlpha(mSelectedAlpha); 148 mMoreIcon.setVisibility(GONE); 149 mUseMoreIcon = styledAttributes.getBoolean(R.styleable.CarFacetButton_useMoreIcon, true); 150 } 151 152 /** 153 * @return The app categories the component represents 154 */ getCategories()155 public String[] getCategories() { 156 if (mFacetCategories == null) { 157 return new String[0]; 158 } 159 return mFacetCategories; 160 } 161 162 /** 163 * @return The valid packages that should be considered. 164 */ getFacetPackages()165 public String[] getFacetPackages() { 166 if (mFacetPackages == null) { 167 return new String[0]; 168 } 169 return mFacetPackages; 170 } 171 172 /** 173 * @return The list of component names. 174 */ getComponentName()175 public String[] getComponentName() { 176 if (mComponentNames == null) { 177 return new String[0]; 178 } 179 return mComponentNames; 180 } 181 182 /** 183 * Updates the alpha of the icons to "selected" and shows the "More icon" 184 * 185 * @param selected true if the view must be selected, false otherwise 186 */ setSelected(boolean selected)187 public void setSelected(boolean selected) { 188 super.setSelected(selected); 189 setSelected(selected, selected); 190 } 191 192 /** 193 * Updates the visual state to let the user know if it's been selected. 194 * 195 * @param selected true if should update the alpha of the icon to selected, false otherwise 196 * @param showMoreIcon true if the "more icon" should be shown, false otherwise. Note this 197 * is ignored if the attribute useMoreIcon is set to false 198 */ setSelected(boolean selected, boolean showMoreIcon)199 public void setSelected(boolean selected, boolean showMoreIcon) { 200 mSelected = selected; 201 mIcon.setAlpha(mSelected ? mSelectedAlpha : mUnselectedAlpha); 202 mIcon.setImageResource(mSelected ? mSelectedIconResourceId : mIconResourceId); 203 if (mUseMoreIcon) { 204 mMoreIcon.setVisibility(showMoreIcon ? VISIBLE : GONE); 205 } 206 } 207 208 /** 209 * @return The id of the display the button is on or Display.INVALID_DISPLAY if it's not yet on 210 * a display. 211 */ getDisplayId()212 public int getDisplayId() { 213 Display display = getDisplay(); 214 if (display == null) { 215 return Display.INVALID_DISPLAY; 216 } 217 return display.getDisplayId(); 218 } 219 } 220