• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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