1 /* 2 * Copyright (C) 2023 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.qc; 18 19 import static android.car.Car.CarServiceLifecycleListener; 20 21 import static com.android.systemui.car.users.CarSystemUIUserUtil.getCurrentUserHandle; 22 23 import android.app.ActivityOptions; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.res.TypedArray; 27 import android.util.AttributeSet; 28 import android.util.Log; 29 import android.view.View; 30 31 import androidx.annotation.Nullable; 32 import androidx.annotation.VisibleForTesting; 33 import androidx.constraintlayout.widget.ConstraintLayout; 34 35 import com.android.car.ui.utils.CarUxRestrictionsUtil; 36 import com.android.systemui.R; 37 import com.android.systemui.broadcast.BroadcastDispatcher; 38 import com.android.systemui.settings.UserTracker; 39 40 import java.net.URISyntaxException; 41 42 /** 43 * Footer button layout which contains one or multiple views for quick control panels. 44 * 45 * Allows for an intent action to be specified via the 46 * {@link R.styleable.QCFooterButtonView_intent} attribute and for enabled state to be set 47 * according to driving mode via the {@link R.styleable.QCFooterButtonView_disableWhileDriving} 48 * attribute. 49 */ 50 public class QCFooterButtonView extends ConstraintLayout { 51 private static final String TAG = QCFooterButtonView.class.getSimpleName(); 52 53 private boolean mDisableWhileDriving; 54 private final CarUxRestrictionsUtil.OnUxRestrictionsChangedListener mListener = 55 carUxRestrictions -> setEnabled(!carUxRestrictions.isRequiresDistractionOptimization()); 56 @Nullable 57 protected UserTracker mUserTracker; 58 59 @Nullable 60 protected BroadcastDispatcher mBroadcastDispatcher; 61 62 @Nullable 63 View.OnClickListener mOnClickListener; 64 65 @Nullable 66 CarServiceLifecycleListener mCarServiceLifecycleListener; 67 QCFooterButtonView(Context context)68 public QCFooterButtonView(Context context) { 69 this(context, null); 70 } 71 QCFooterButtonView(Context context, AttributeSet attrs)72 public QCFooterButtonView(Context context, AttributeSet attrs) { 73 this(context, attrs, 0); 74 } 75 QCFooterButtonView(Context context, AttributeSet attrs, int defStyleAttr)76 public QCFooterButtonView(Context context, AttributeSet attrs, int defStyleAttr) { 77 this(context, attrs, defStyleAttr, 0); 78 } 79 QCFooterButtonView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)80 public QCFooterButtonView(Context context, AttributeSet attrs, int defStyleAttr, 81 int defStyleRes) { 82 super(context, attrs, defStyleAttr, defStyleRes); 83 if (attrs == null) { 84 return; 85 } 86 87 TypedArray typedArray = context.obtainStyledAttributes(attrs, 88 R.styleable.QCFooterButtonView); 89 String intentString = typedArray.getString(R.styleable.QCFooterButtonView_intent); 90 if (intentString != null) { 91 Intent intent; 92 try { 93 intent = Intent.parseUri(intentString, Intent.URI_INTENT_SCHEME); 94 } catch (URISyntaxException e) { 95 throw new RuntimeException("Failed to attach intent", e); 96 } 97 Intent finalIntent = intent; 98 99 mOnClickListener = v -> { 100 mContext.sendBroadcastAsUser(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), 101 getCurrentUserHandle(mContext, mUserTracker)); 102 try { 103 ActivityOptions options = ActivityOptions.makeBasic(); 104 options.setLaunchDisplayId(mContext.getDisplayId()); 105 mContext.startActivityAsUser(finalIntent, options.toBundle(), 106 getCurrentUserHandle(mContext, mUserTracker)); 107 } catch (Exception e) { 108 Log.e(TAG, "Failed to launch intent", e); 109 } 110 }; 111 setOnClickListener(mOnClickListener); 112 } 113 114 mDisableWhileDriving = typedArray.getBoolean( 115 R.styleable.QCFooterButtonView_disableWhileDriving, /* defValue= */ false); 116 117 typedArray.recycle(); 118 } 119 setUserTracker(UserTracker userTracker)120 public void setUserTracker(UserTracker userTracker) { 121 mUserTracker = userTracker; 122 } 123 setBroadcastDispatcher(BroadcastDispatcher broadcastDispatcher)124 public void setBroadcastDispatcher(BroadcastDispatcher broadcastDispatcher) { 125 mBroadcastDispatcher = broadcastDispatcher; 126 } 127 128 @Override onAttachedToWindow()129 protected void onAttachedToWindow() { 130 super.onAttachedToWindow(); 131 if (mDisableWhileDriving) { 132 CarUxRestrictionsUtil.getInstance(mContext).register(mListener); 133 } 134 } 135 136 @Override onDetachedFromWindow()137 protected void onDetachedFromWindow() { 138 super.onDetachedFromWindow(); 139 if (mDisableWhileDriving) { 140 CarUxRestrictionsUtil.getInstance(mContext).unregister(mListener); 141 } 142 } 143 144 @Nullable 145 @VisibleForTesting getOnClickListener()146 protected View.OnClickListener getOnClickListener() { 147 return mOnClickListener; 148 } 149 150 @Nullable 151 @VisibleForTesting getCarServiceLifecycleListener()152 protected CarServiceLifecycleListener getCarServiceLifecycleListener() { 153 return mCarServiceLifecycleListener; 154 } 155 } 156