1 /* 2 * Copyright (C) 2020 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.car.systembar; 18 19 import android.app.ActivityManager; 20 import android.app.ActivityOptions; 21 import android.app.ActivityTaskManager; 22 import android.app.role.RoleManager; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.res.TypedArray; 26 import android.graphics.drawable.Drawable; 27 import android.os.Build; 28 import android.os.UserHandle; 29 import android.util.AttributeSet; 30 import android.util.Log; 31 import android.view.Display; 32 import android.view.View; 33 import android.widget.ImageView; 34 import android.widget.LinearLayout; 35 36 import com.android.internal.annotations.VisibleForTesting; 37 import com.android.systemui.R; 38 import com.android.systemui.statusbar.AlphaOptimizedImageView; 39 40 import java.net.URISyntaxException; 41 import java.util.List; 42 43 /** 44 * CarSystemBarButton is an image button that allows for a bit more configuration at the 45 * xml file level. This allows for more control via overlays instead of having to update 46 * code. 47 */ 48 public class CarSystemBarButton extends LinearLayout { 49 50 private static final String TAG = "CarSystemBarButton"; 51 private static final String BUTTON_FILTER_DELIMITER = ";"; 52 private static final String EXTRA_BUTTON_CATEGORIES = "categories"; 53 private static final String EXTRA_BUTTON_PACKAGES = "packages"; 54 private static final float DEFAULT_SELECTED_ALPHA = 1f; 55 private static final float DEFAULT_UNSELECTED_ALPHA = 0.75f; 56 57 private final Context mContext; 58 private final ActivityTaskManager mActivityTaskManager; 59 private final ActivityManager mActivityManager; 60 private AlphaOptimizedImageView mIcon; 61 private AlphaOptimizedImageView mMoreIcon; 62 private ImageView mUnseenIcon; 63 private String mIntent; 64 private String mLongIntent; 65 private boolean mBroadcastIntent; 66 private boolean mClearBackStack; 67 private boolean mHasUnseen = false; 68 private boolean mSelected = false; 69 private float mSelectedAlpha; 70 private float mUnselectedAlpha; 71 private int mSelectedIconResourceId; 72 private int mIconResourceId; 73 private Drawable mAppIcon; 74 private boolean mIsDefaultAppIconForRoleEnabled; 75 private String[] mComponentNames; 76 /** App categories that are to be used with this widget */ 77 private String[] mButtonCategories; 78 /** App packages that are allowed to be used with this widget */ 79 private String[] mButtonPackages; 80 /** Whether to display more icon beneath the primary icon when the button is selected */ 81 private boolean mShowMoreWhenSelected = false; 82 /** Whether to highlight the button if the active application is associated with it */ 83 private boolean mHighlightWhenSelected = false; 84 CarSystemBarButton(Context context, AttributeSet attrs)85 public CarSystemBarButton(Context context, AttributeSet attrs) { 86 super(context, attrs); 87 mContext = context; 88 mActivityTaskManager = ActivityTaskManager.getInstance(); 89 mActivityManager = mContext.getSystemService(ActivityManager.class); 90 View.inflate(mContext, R.layout.car_system_bar_button, /* root= */ this); 91 // CarSystemBarButton attrs 92 TypedArray typedArray = context.obtainStyledAttributes(attrs, 93 R.styleable.CarSystemBarButton); 94 95 setUpIntents(typedArray); 96 setUpIcons(typedArray); 97 typedArray.recycle(); 98 } 99 100 /** 101 * @param selected true if should indicate if this is a selected state, false otherwise 102 */ setSelected(boolean selected)103 public void setSelected(boolean selected) { 104 super.setSelected(selected); 105 mSelected = selected; 106 107 if (mHighlightWhenSelected) { 108 mIcon.setAlpha(mSelected ? mSelectedAlpha : mUnselectedAlpha); 109 } 110 111 if (mShowMoreWhenSelected && mMoreIcon != null) { 112 mMoreIcon.setVisibility(selected ? VISIBLE : GONE); 113 } 114 updateImage(); 115 } 116 117 /** 118 * @param hasUnseen true if should indicate if this is a Unseen state, false otherwise. 119 */ setUnseen(boolean hasUnseen)120 public void setUnseen(boolean hasUnseen) { 121 mHasUnseen = hasUnseen; 122 updateImage(); 123 } 124 125 /** 126 * Sets the current icon of the default application associated with this button. 127 */ setAppIcon(Drawable appIcon)128 public void setAppIcon(Drawable appIcon) { 129 mAppIcon = appIcon; 130 updateImage(); 131 } 132 133 /** Gets the icon of the app currently associated to the role of this button. */ 134 @VisibleForTesting getAppIcon()135 protected Drawable getAppIcon() { 136 return mAppIcon; 137 } 138 139 /** Gets whether the icon is in an unseen state. */ getUnseen()140 public boolean getUnseen() { 141 return mHasUnseen; 142 } 143 144 /** 145 * @return The app categories the component represents 146 */ getCategories()147 public String[] getCategories() { 148 if (mButtonCategories == null) { 149 return new String[0]; 150 } 151 return mButtonCategories; 152 } 153 154 /** 155 * @return The valid packages that should be considered. 156 */ getPackages()157 public String[] getPackages() { 158 if (mButtonPackages == null) { 159 return new String[0]; 160 } 161 return mButtonPackages; 162 } 163 164 /** 165 * @return The list of component names. 166 */ getComponentName()167 public String[] getComponentName() { 168 if (mComponentNames == null) { 169 return new String[0]; 170 } 171 return mComponentNames; 172 } 173 174 /** 175 * Subclasses should override this method to return the {@link RoleManager} role associated 176 * with this button. 177 */ getRoleName()178 protected String getRoleName() { 179 return null; 180 } 181 182 /** 183 * @return true if this button should show the icon of the default application for the 184 * role returned by {@link #getRoleName()}. 185 */ isDefaultAppIconForRoleEnabled()186 protected boolean isDefaultAppIconForRoleEnabled() { 187 return mIsDefaultAppIconForRoleEnabled; 188 } 189 190 /** 191 * @return The id of the display the button is on or Display.INVALID_DISPLAY if it's not yet on 192 * a display. 193 */ getDisplayId()194 protected int getDisplayId() { 195 Display display = getDisplay(); 196 if (display == null) { 197 return Display.INVALID_DISPLAY; 198 } 199 return display.getDisplayId(); 200 } 201 hasSelectionState()202 protected boolean hasSelectionState() { 203 return mHighlightWhenSelected || mShowMoreWhenSelected; 204 } 205 206 @VisibleForTesting getSelectedAlpha()207 protected float getSelectedAlpha() { 208 return mSelectedAlpha; 209 } 210 211 @VisibleForTesting getUnselectedAlpha()212 protected float getUnselectedAlpha() { 213 return mUnselectedAlpha; 214 } 215 216 @VisibleForTesting getIconAlpha()217 protected float getIconAlpha() { return mIcon.getAlpha(); } 218 219 /** 220 * Sets up intents for click, long touch, and broadcast. 221 */ setUpIntents(TypedArray typedArray)222 protected void setUpIntents(TypedArray typedArray) { 223 mIntent = typedArray.getString(R.styleable.CarSystemBarButton_intent); 224 mLongIntent = typedArray.getString(R.styleable.CarSystemBarButton_longIntent); 225 mBroadcastIntent = typedArray.getBoolean(R.styleable.CarSystemBarButton_broadcast, false); 226 227 mClearBackStack = typedArray.getBoolean(R.styleable.CarSystemBarButton_clearBackStack, 228 false); 229 230 String categoryString = typedArray.getString(R.styleable.CarSystemBarButton_categories); 231 String packageString = typedArray.getString(R.styleable.CarSystemBarButton_packages); 232 String componentNameString = 233 typedArray.getString(R.styleable.CarSystemBarButton_componentNames); 234 235 try { 236 if (mIntent != null) { 237 final Intent intent = Intent.parseUri(mIntent, Intent.URI_INTENT_SCHEME); 238 setOnClickListener(getButtonClickListener(intent)); 239 if (packageString != null) { 240 mButtonPackages = packageString.split(BUTTON_FILTER_DELIMITER); 241 intent.putExtra(EXTRA_BUTTON_PACKAGES, mButtonPackages); 242 } 243 if (categoryString != null) { 244 mButtonCategories = categoryString.split(BUTTON_FILTER_DELIMITER); 245 intent.putExtra(EXTRA_BUTTON_CATEGORIES, mButtonCategories); 246 } 247 if (componentNameString != null) { 248 mComponentNames = componentNameString.split(BUTTON_FILTER_DELIMITER); 249 } 250 } 251 } catch (URISyntaxException e) { 252 throw new RuntimeException("Failed to attach intent", e); 253 } 254 255 try { 256 if (mLongIntent != null && (Build.IS_ENG || Build.IS_USERDEBUG)) { 257 final Intent intent = Intent.parseUri(mLongIntent, Intent.URI_INTENT_SCHEME); 258 setOnLongClickListener(getButtonLongClickListener(intent)); 259 } 260 } catch (URISyntaxException e) { 261 throw new RuntimeException("Failed to attach long press intent", e); 262 } 263 } 264 265 /** Defines the behavior of a button click. */ getButtonClickListener(Intent toSend)266 protected OnClickListener getButtonClickListener(Intent toSend) { 267 return v -> { 268 mContext.sendBroadcastAsUser(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), 269 UserHandle.CURRENT); 270 try { 271 if (mBroadcastIntent) { 272 mContext.sendBroadcastAsUser(toSend, UserHandle.CURRENT); 273 return; 274 } 275 ActivityOptions options = ActivityOptions.makeBasic(); 276 options.setLaunchDisplayId(mContext.getDisplayId()); 277 mContext.startActivityAsUser(toSend, options.toBundle(), 278 UserHandle.CURRENT); 279 280 if (mClearBackStack) { 281 List<ActivityManager.RunningTaskInfo> runningTasks = 282 mActivityTaskManager.getTasks(1); 283 if (!runningTasks.isEmpty()) { 284 mActivityManager.moveTaskToFront(runningTasks.get(0).taskId, 285 ActivityManager.MOVE_TASK_WITH_HOME); 286 } else { 287 Log.e(TAG, "No backstack to clear"); 288 } 289 } 290 } catch (Exception e) { 291 Log.e(TAG, "Failed to launch intent", e); 292 } 293 }; 294 } 295 296 /** Defines the behavior of a long click. */ getButtonLongClickListener(Intent toSend)297 protected OnLongClickListener getButtonLongClickListener(Intent toSend) { 298 return v -> { 299 mContext.sendBroadcastAsUser(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), 300 UserHandle.CURRENT); 301 try { 302 ActivityOptions options = ActivityOptions.makeBasic(); 303 options.setLaunchDisplayId(mContext.getDisplayId()); 304 mContext.startActivityAsUser(toSend, options.toBundle(), 305 UserHandle.CURRENT); 306 } catch (Exception e) { 307 Log.e(TAG, "Failed to launch intent", e); 308 } 309 // consume event either way 310 return true; 311 }; 312 } 313 314 /** 315 * Initializes view-related aspects of the button. 316 */ 317 private void setUpIcons(TypedArray typedArray) { 318 mSelectedAlpha = typedArray.getFloat( 319 R.styleable.CarSystemBarButton_selectedAlpha, DEFAULT_SELECTED_ALPHA); 320 mUnselectedAlpha = typedArray.getFloat( 321 R.styleable.CarSystemBarButton_unselectedAlpha, DEFAULT_UNSELECTED_ALPHA); 322 mHighlightWhenSelected = typedArray.getBoolean( 323 R.styleable.CarSystemBarButton_highlightWhenSelected, 324 mHighlightWhenSelected); 325 mShowMoreWhenSelected = typedArray.getBoolean( 326 R.styleable.CarSystemBarButton_showMoreWhenSelected, 327 mShowMoreWhenSelected); 328 329 mIconResourceId = typedArray.getResourceId( 330 R.styleable.CarSystemBarButton_icon, 0); 331 mSelectedIconResourceId = typedArray.getResourceId( 332 R.styleable.CarSystemBarButton_selectedIcon, mIconResourceId); 333 mIsDefaultAppIconForRoleEnabled = typedArray.getBoolean( 334 R.styleable.CarSystemBarButton_useDefaultAppIconForRole, false); 335 mIcon = findViewById(R.id.car_nav_button_icon_image); 336 // Always apply un-selected alpha regardless of if the button toggles alpha based on 337 // selection state. 338 mIcon.setAlpha(mHighlightWhenSelected ? mUnselectedAlpha : mSelectedAlpha); 339 mMoreIcon = findViewById(R.id.car_nav_button_more_icon); 340 mUnseenIcon = findViewById(R.id.car_nav_button_unseen_icon); 341 updateImage(); 342 } 343 344 private void updateImage() { 345 if (mIsDefaultAppIconForRoleEnabled && mAppIcon != null) { 346 mIcon.setImageDrawable(mAppIcon); 347 } else { 348 mIcon.setImageResource(mSelected ? mSelectedIconResourceId : mIconResourceId); 349 } 350 mUnseenIcon.setVisibility(mHasUnseen ? VISIBLE : GONE); 351 } 352 } 353