blob: 580cd2300b6d029b854f94587664656eb8299d1f [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.car.systembar;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static com.android.systemui.car.users.CarSystemUIUserUtil.getCurrentUserHandle;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.role.RoleManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.RemoteException;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.AlphaOptimizedImageView;
import java.net.URISyntaxException;
/**
* CarSystemBarButton is an image button that allows for a bit more configuration at the
* xml file level. This allows for more control via overlays instead of having to update
* code.
*/
public class CarSystemBarButton extends LinearLayout {
private static final String TAG = "CarSystemBarButton";
private static final String BUTTON_FILTER_DELIMITER = ";";
private static final String EXTRA_BUTTON_CATEGORIES = "categories";
private static final String EXTRA_BUTTON_PACKAGES = "packages";
private static final String EXTRA_DIALOG_CLOSE_REASON = "reason";
private static final String DIALOG_CLOSE_REASON_CAR_SYSTEMBAR_BUTTON = "carsystembarbutton";
private static final float DEFAULT_SELECTED_ALPHA = 1f;
private static final float DEFAULT_UNSELECTED_ALPHA = 0.75f;
private static final float DISABLED_ALPHA = 0.25f;
private final Context mContext;
private final ActivityManager mActivityManager;
@Nullable
private UserTracker mUserTracker;
private AlphaOptimizedImageView mIcon;
private AlphaOptimizedImageView mMoreIcon;
private ImageView mUnseenIcon;
private String mIntent;
private String mLongIntent;
private boolean mBroadcastIntent;
/** Whether to clear the backstack (i.e. put the home activity directly behind) when pressed */
private boolean mClearBackStack;
private boolean mHasUnseen = false;
private boolean mSelected = false;
private boolean mDisabled = false;
private float mSelectedAlpha;
private float mUnselectedAlpha;
private int mSelectedIconResourceId;
private int mIconResourceId;
private Drawable mAppIcon;
private boolean mIsDefaultAppIconForRoleEnabled;
private boolean mToggleSelectedState;
private String[] mComponentNames;
/** App categories that are to be used with this widget */
private String[] mButtonCategories;
/** App packages that are allowed to be used with this widget */
private String[] mButtonPackages;
/** Whether to display more icon beneath the primary icon when the button is selected */
private boolean mShowMoreWhenSelected = false;
/** Whether to highlight the button if the active application is associated with it */
private boolean mHighlightWhenSelected = false;
private Runnable mOnClickWhileDisabledRunnable;
public CarSystemBarButton(Context context, AttributeSet attrs) {
super(context, attrs);
// Do not move this init call. All logic should be carried out after this.
init();
mContext = context;
mActivityManager = mContext.getSystemService(ActivityManager.class);
View.inflate(mContext, R.layout.car_system_bar_button, /* root= */ this);
// CarSystemBarButton attrs
TypedArray typedArray = context.obtainStyledAttributes(attrs,
R.styleable.CarSystemBarButton);
setUpIntents(typedArray);
setUpIcons(typedArray);
typedArray.recycle();
}
/**
* Initializer for child classes.
*/
protected void init() {
}
/**
* @param selected true if should indicate if this is a selected state, false otherwise
*/
public void setSelected(boolean selected) {
if (mDisabled) {
// if the button is disabled, mSelected should not be modified and the button
// should be unselectable
return;
}
super.setSelected(selected);
mSelected = selected;
refreshIconAlpha(mIcon);
if (mShowMoreWhenSelected && mMoreIcon != null) {
mMoreIcon.setVisibility(selected ? VISIBLE : GONE);
}
updateImage(mIcon);
}
/** Gets whether the icon is in a selected state. */
public boolean getSelected() {
return mSelected;
}
/**
* @param hasUnseen true if should indicate if this is a Unseen state, false otherwise.
*/
public void setUnseen(boolean hasUnseen) {
mHasUnseen = hasUnseen;
updateImage(mIcon);
}
/**
* @param disabled true if icon should be isabled, false otherwise.
* @param runnable to run when button is clicked while disabled.
*/
public void setDisabled(boolean disabled, @Nullable Runnable runnable) {
mDisabled = disabled;
mOnClickWhileDisabledRunnable = runnable;
refreshIconAlpha(mIcon);
updateImage(mIcon);
}
/** Gets whether the icon is disabled */
public boolean getDisabled() {
return mDisabled;
}
/** Runs the Runnable when the button is clicked while disabled */
public void runOnClickWhileDisabled() {
if (mOnClickWhileDisabledRunnable == null) {
return;
}
mOnClickWhileDisabledRunnable.run();
}
/**
* Sets the current icon of the default application associated with this button.
*/
public void setAppIcon(Drawable appIcon) {
mAppIcon = appIcon;
updateImage(mIcon);
}
/** Gets the icon of the app currently associated to the role of this button. */
@VisibleForTesting
protected Drawable getAppIcon() {
return mAppIcon;
}
/** Gets whether the icon is in an unseen state. */
public boolean getUnseen() {
return mHasUnseen;
}
/**
* @return The app categories the component represents
*/
public String[] getCategories() {
if (mButtonCategories == null) {
return new String[0];
}
return mButtonCategories;
}
/**
* @return The valid packages that should be considered.
*/
public String[] getPackages() {
if (mButtonPackages == null) {
return new String[0];
}
return mButtonPackages;
}
/**
* @return The list of component names.
*/
public String[] getComponentName() {
if (mComponentNames == null) {
return new String[0];
}
return mComponentNames;
}
/**
* Subclasses should override this method to return the {@link RoleManager} role associated
* with this button.
*/
protected String getRoleName() {
return null;
}
/**
* @return true if this button should show the icon of the default application for the
* role returned by {@link #getRoleName()}.
*/
protected boolean isDefaultAppIconForRoleEnabled() {
return mIsDefaultAppIconForRoleEnabled;
}
/**
* @return The id of the display the button is on or Display.INVALID_DISPLAY if it's not yet on
* a display.
*/
protected int getDisplayId() {
Display display = getDisplay();
if (display == null) {
return Display.INVALID_DISPLAY;
}
return display.getDisplayId();
}
protected boolean hasSelectionState() {
return mHighlightWhenSelected || mShowMoreWhenSelected;
}
protected float getSelectedAlpha() {
return mSelectedAlpha;
}
@VisibleForTesting
protected float getUnselectedAlpha() {
return mUnselectedAlpha;
}
@VisibleForTesting
protected float getDisabledAlpha() {
return DISABLED_ALPHA;
}
@VisibleForTesting
protected float getIconAlpha() { return mIcon.getAlpha(); }
/**
* Sets up intents for click, long touch, and broadcast.
*/
protected void setUpIntents(TypedArray typedArray) {
mIntent = typedArray.getString(R.styleable.CarSystemBarButton_intent);
mLongIntent = typedArray.getString(R.styleable.CarSystemBarButton_longIntent);
mBroadcastIntent = typedArray.getBoolean(R.styleable.CarSystemBarButton_broadcast, false);
mClearBackStack = typedArray.getBoolean(R.styleable.CarSystemBarButton_clearBackStack,
false);
String categoryString = typedArray.getString(R.styleable.CarSystemBarButton_categories);
String packageString = typedArray.getString(R.styleable.CarSystemBarButton_packages);
String componentNameString =
typedArray.getString(R.styleable.CarSystemBarButton_componentNames);
try {
if (mIntent != null) {
final Intent intent = Intent.parseUri(mIntent, Intent.URI_INTENT_SCHEME);
setOnClickListener(getButtonClickListener(intent));
if (packageString != null) {
mButtonPackages = packageString.split(BUTTON_FILTER_DELIMITER);
intent.putExtra(EXTRA_BUTTON_PACKAGES, mButtonPackages);
}
if (categoryString != null) {
mButtonCategories = categoryString.split(BUTTON_FILTER_DELIMITER);
intent.putExtra(EXTRA_BUTTON_CATEGORIES, mButtonCategories);
}
if (componentNameString != null) {
mComponentNames = componentNameString.split(BUTTON_FILTER_DELIMITER);
}
}
} catch (URISyntaxException e) {
throw new RuntimeException("Failed to attach intent", e);
}
try {
if (mLongIntent != null && (Build.IS_ENG || Build.IS_USERDEBUG)) {
final Intent intent = Intent.parseUri(mLongIntent, Intent.URI_INTENT_SCHEME);
setOnLongClickListener(getButtonLongClickListener(intent));
}
} catch (URISyntaxException e) {
throw new RuntimeException("Failed to attach long press intent", e);
}
}
/** Defines the behavior of a button click. */
protected OnClickListener getButtonClickListener(Intent toSend) {
return v -> {
if (mDisabled) {
runOnClickWhileDisabled();
return;
}
boolean startState = mSelected;
Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
intent.putExtra(EXTRA_DIALOG_CLOSE_REASON, DIALOG_CLOSE_REASON_CAR_SYSTEMBAR_BUTTON);
mContext.sendBroadcastAsUser(intent, getCurrentUserHandle(mContext, mUserTracker));
boolean intentLaunched = false;
try {
if (mBroadcastIntent) {
mContext.sendBroadcastAsUser(toSend,
getCurrentUserHandle(mContext, mUserTracker));
return;
}
ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchDisplayId(mContext.getDisplayId());
mContext.startActivityAsUser(toSend, options.toBundle(),
getCurrentUserHandle(mContext, mUserTracker));
intentLaunched = true;
} catch (Exception e) {
Log.e(TAG, "Failed to launch intent", e);
}
if (intentLaunched && mClearBackStack) {
try {
ActivityTaskManager.RootTaskInfo rootTaskInfo =
ActivityTaskManager.getService().getRootTaskInfoOnDisplay(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_UNDEFINED,
mContext.getDisplayId());
if (rootTaskInfo != null) {
mActivityManager.moveTaskToFront(rootTaskInfo.taskId,
ActivityManager.MOVE_TASK_WITH_HOME);
}
} catch (RemoteException e) {
Log.e(TAG, "Failed getting root task info", e);
}
}
if (mToggleSelectedState && (startState == mSelected)) {
setSelected(!mSelected);
}
};
}
/** Defines the behavior of a long click. */
protected OnLongClickListener getButtonLongClickListener(Intent toSend) {
return v -> {
Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
intent.putExtra(EXTRA_DIALOG_CLOSE_REASON, DIALOG_CLOSE_REASON_CAR_SYSTEMBAR_BUTTON);
mContext.sendBroadcastAsUser(intent, getCurrentUserHandle(mContext, mUserTracker));
try {
ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchDisplayId(mContext.getDisplayId());
mContext.startActivityAsUser(toSend, options.toBundle(),
getCurrentUserHandle(mContext, mUserTracker));
} catch (Exception e) {
Log.e(TAG, "Failed to launch intent", e);
}
// consume event either way
return true;
};
}
void setUserTracker(UserTracker userTracker) {
mUserTracker = userTracker;
}
/**
* Initializes view-related aspects of the button.
*/
private void setUpIcons(TypedArray typedArray) {
mSelectedAlpha = typedArray.getFloat(
R.styleable.CarSystemBarButton_selectedAlpha, DEFAULT_SELECTED_ALPHA);
mUnselectedAlpha = typedArray.getFloat(
R.styleable.CarSystemBarButton_unselectedAlpha, DEFAULT_UNSELECTED_ALPHA);
mHighlightWhenSelected = typedArray.getBoolean(
R.styleable.CarSystemBarButton_highlightWhenSelected,
mHighlightWhenSelected);
mShowMoreWhenSelected = typedArray.getBoolean(
R.styleable.CarSystemBarButton_showMoreWhenSelected,
mShowMoreWhenSelected);
mIconResourceId = typedArray.getResourceId(
R.styleable.CarSystemBarButton_icon, 0);
mSelectedIconResourceId = typedArray.getResourceId(
R.styleable.CarSystemBarButton_selectedIcon, mIconResourceId);
mIsDefaultAppIconForRoleEnabled = typedArray.getBoolean(
R.styleable.CarSystemBarButton_useDefaultAppIconForRole, false);
mToggleSelectedState = typedArray.getBoolean(
R.styleable.CarSystemBarButton_toggleSelected, false);
mIcon = findViewById(R.id.car_nav_button_icon_image);
refreshIconAlpha(mIcon);
mMoreIcon = findViewById(R.id.car_nav_button_more_icon);
mUnseenIcon = findViewById(R.id.car_nav_button_unseen_icon);
updateImage(mIcon);
}
protected void updateImage(AlphaOptimizedImageView icon) {
if (mIsDefaultAppIconForRoleEnabled && mAppIcon != null) {
icon.setImageDrawable(mAppIcon);
} else {
icon.setImageResource(mSelected ? mSelectedIconResourceId : mIconResourceId);
}
mUnseenIcon.setVisibility(mHasUnseen ? VISIBLE : GONE);
}
protected void refreshIconAlpha(AlphaOptimizedImageView icon) {
if (mDisabled) {
icon.setAlpha(DISABLED_ALPHA);
} else {
icon.setAlpha(mHighlightWhenSelected && mSelected ? mSelectedAlpha : mUnselectedAlpha);
}
}
@Nullable
protected UserTracker getUserTracker() {
return mUserTracker;
}
}