blob: a6fcde3be2a897277a8df4e56a4bf9d5e714ca5f [file] [log] [blame]
/*
* Copyright (C) 2014 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.statusbar.phone;
import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE;
import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE;
import android.app.ActivityTaskManager;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.quickaccesswallet.GetWalletCardsError;
import android.service.quickaccesswallet.GetWalletCardsResponse;
import android.service.quickaccesswallet.QuickAccessWalletClient;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.controls.dagger.ControlsComponent;
import com.android.systemui.controls.management.ControlsListingController;
import com.android.systemui.controls.ui.ControlsActivity;
import com.android.systemui.controls.ui.ControlsUiController;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.wallet.controller.QuickAccessWalletController;
/**
* Implementation for the bottom area of the Keyguard, including camera/phone affordance and status
* text.
*/
public class KeyguardBottomAreaView extends FrameLayout {
private static final String TAG = "CentralSurfaces/KeyguardBottomAreaView";
private static final int DOZE_ANIMATION_ELEMENT_DURATION = 250;
private ImageView mWalletButton;
private ImageView mQRCodeScannerButton;
private ImageView mControlsButton;
private boolean mHasCard = false;
private final WalletCardRetriever mCardRetriever = new WalletCardRetriever();
private QuickAccessWalletController mQuickAccessWalletController;
private QRCodeScannerController mQRCodeScannerController;
private ControlsComponent mControlsComponent;
private boolean mControlServicesAvailable = false;
@Nullable private View mAmbientIndicationArea;
private ViewGroup mIndicationArea;
private TextView mIndicationText;
private TextView mIndicationTextBottom;
private ViewGroup mOverlayContainer;
private ActivityStarter mActivityStarter;
private KeyguardStateController mKeyguardStateController;
private FalsingManager mFalsingManager;
private boolean mDozing;
private int mIndicationBottomMargin;
private int mIndicationPadding;
private float mDarkAmount;
private int mBurnInXOffset;
private int mBurnInYOffset;
private final ControlsListingController.ControlsListingCallback mListingCallback =
serviceInfos -> post(() -> {
boolean available = !serviceInfos.isEmpty();
if (available != mControlServicesAvailable) {
mControlServicesAvailable = available;
updateControlsVisibility();
updateAffordanceColors();
}
});
private final KeyguardStateController.Callback mKeyguardStateCallback =
new KeyguardStateController.Callback() {
@Override
public void onKeyguardShowingChanged() {
if (mKeyguardStateController.isShowing()) {
if (mQuickAccessWalletController != null) {
mQuickAccessWalletController.queryWalletCards(mCardRetriever);
}
}
}
};
public KeyguardBottomAreaView(Context context) {
this(context, null);
}
public KeyguardBottomAreaView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
/** Initializes the {@link KeyguardBottomAreaView} with the given dependencies */
public void init(
FalsingManager falsingManager,
QuickAccessWalletController controller,
ControlsComponent controlsComponent,
QRCodeScannerController qrCodeScannerController) {
mFalsingManager = falsingManager;
mQuickAccessWalletController = controller;
mQuickAccessWalletController.setupWalletChangeObservers(
mCardRetriever, WALLET_PREFERENCE_CHANGE, DEFAULT_PAYMENT_APP_CHANGE);
mQuickAccessWalletController.updateWalletPreference();
mQuickAccessWalletController.queryWalletCards(mCardRetriever);
updateWalletVisibility();
mControlsComponent = controlsComponent;
mControlsComponent.getControlsListingController().ifPresent(
c -> c.addCallback(mListingCallback));
mQRCodeScannerController = qrCodeScannerController;
mQRCodeScannerController.registerQRCodeScannerChangeObservers(
QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE);
updateQRCodeButtonVisibility();
updateAffordanceColors();
}
/**
* Initializes this instance of {@link KeyguardBottomAreaView} based on the given instance of
* another {@link KeyguardBottomAreaView}
*/
public void initFrom(KeyguardBottomAreaView oldBottomArea) {
// if it exists, continue to use the original ambient indication container
// instead of the newly inflated one
if (mAmbientIndicationArea != null) {
// remove old ambient indication from its parent
View originalAmbientIndicationView =
oldBottomArea.findViewById(R.id.ambient_indication_container);
((ViewGroup) originalAmbientIndicationView.getParent())
.removeView(originalAmbientIndicationView);
// remove current ambient indication from its parent (discard)
ViewGroup ambientIndicationParent = (ViewGroup) mAmbientIndicationArea.getParent();
int ambientIndicationIndex =
ambientIndicationParent.indexOfChild(mAmbientIndicationArea);
ambientIndicationParent.removeView(mAmbientIndicationArea);
// add the old ambient indication to this view
ambientIndicationParent.addView(originalAmbientIndicationView, ambientIndicationIndex);
mAmbientIndicationArea = originalAmbientIndicationView;
// update burn-in offsets
dozeTimeTick();
}
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mOverlayContainer = findViewById(R.id.overlay_container);
mWalletButton = findViewById(R.id.wallet_button);
mQRCodeScannerButton = findViewById(R.id.qr_code_scanner_button);
mControlsButton = findViewById(R.id.controls_button);
mIndicationArea = findViewById(R.id.keyguard_indication_area);
mAmbientIndicationArea = findViewById(R.id.ambient_indication_container);
mIndicationText = findViewById(R.id.keyguard_indication_text);
mIndicationTextBottom = findViewById(R.id.keyguard_indication_text_bottom);
mIndicationBottomMargin = getResources().getDimensionPixelSize(
R.dimen.keyguard_indication_margin_bottom);
mBurnInYOffset = getResources().getDimensionPixelSize(
R.dimen.default_burn_in_prevention_offset);
mKeyguardStateController = Dependency.get(KeyguardStateController.class);
mKeyguardStateController.addCallback(mKeyguardStateCallback);
setClipChildren(false);
setClipToPadding(false);
mActivityStarter = Dependency.get(ActivityStarter.class);
mIndicationPadding = getResources().getDimensionPixelSize(
R.dimen.keyguard_indication_area_padding);
updateWalletVisibility();
updateQRCodeButtonVisibility();
updateControlsVisibility();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
final IntentFilter filter = new IntentFilter();
filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
mKeyguardStateController.addCallback(mKeyguardStateCallback);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mKeyguardStateController.removeCallback(mKeyguardStateCallback);
if (mQuickAccessWalletController != null) {
mQuickAccessWalletController.unregisterWalletChangeObservers(
WALLET_PREFERENCE_CHANGE, DEFAULT_PAYMENT_APP_CHANGE);
}
if (mQRCodeScannerController != null) {
mQRCodeScannerController.unregisterQRCodeScannerChangeObservers(
QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE);
}
if (mControlsComponent != null) {
mControlsComponent.getControlsListingController().ifPresent(
c -> c.removeCallback(mListingCallback));
}
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mIndicationBottomMargin = getResources().getDimensionPixelSize(
R.dimen.keyguard_indication_margin_bottom);
mBurnInYOffset = getResources().getDimensionPixelSize(
R.dimen.default_burn_in_prevention_offset);
MarginLayoutParams mlp = (MarginLayoutParams) mIndicationArea.getLayoutParams();
if (mlp.bottomMargin != mIndicationBottomMargin) {
mlp.bottomMargin = mIndicationBottomMargin;
mIndicationArea.setLayoutParams(mlp);
}
// Respect font size setting.
mIndicationTextBottom.setTextSize(TypedValue.COMPLEX_UNIT_PX,
getResources().getDimensionPixelSize(
com.android.internal.R.dimen.text_size_small_material));
mIndicationText.setTextSize(TypedValue.COMPLEX_UNIT_PX,
getResources().getDimensionPixelSize(
com.android.internal.R.dimen.text_size_small_material));
ViewGroup.LayoutParams lp = mWalletButton.getLayoutParams();
lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width);
lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height);
mWalletButton.setLayoutParams(lp);
lp = mQRCodeScannerButton.getLayoutParams();
lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width);
lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height);
mQRCodeScannerButton.setLayoutParams(lp);
lp = mControlsButton.getLayoutParams();
lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width);
lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height);
mControlsButton.setLayoutParams(lp);
mIndicationPadding = getResources().getDimensionPixelSize(
R.dimen.keyguard_indication_area_padding);
updateWalletVisibility();
updateQRCodeButtonVisibility();
updateAffordanceColors();
}
private void updateWalletVisibility() {
if (mDozing
|| mQuickAccessWalletController == null
|| !mQuickAccessWalletController.isWalletEnabled()
|| !mHasCard) {
mWalletButton.setVisibility(GONE);
if (mControlsButton.getVisibility() == GONE) {
mIndicationArea.setPadding(0, 0, 0, 0);
}
} else {
mWalletButton.setVisibility(VISIBLE);
mWalletButton.setOnClickListener(this::onWalletClick);
mIndicationArea.setPadding(mIndicationPadding, 0, mIndicationPadding, 0);
}
}
private void updateControlsVisibility() {
if (mControlsComponent == null) return;
mControlsButton.setImageResource(mControlsComponent.getTileImageId());
mControlsButton.setContentDescription(getContext()
.getString(mControlsComponent.getTileTitleId()));
updateAffordanceColors();
boolean hasFavorites = mControlsComponent.getControlsController()
.map(c -> c.getFavorites().size() > 0)
.orElse(false);
if (mDozing
|| !hasFavorites
|| !mControlServicesAvailable
|| mControlsComponent.getVisibility() != AVAILABLE) {
mControlsButton.setVisibility(GONE);
if (mWalletButton.getVisibility() == GONE) {
mIndicationArea.setPadding(0, 0, 0, 0);
}
} else {
mControlsButton.setVisibility(VISIBLE);
mControlsButton.setOnClickListener(this::onControlsClick);
mIndicationArea.setPadding(mIndicationPadding, 0, mIndicationPadding, 0);
}
}
public void setDarkAmount(float darkAmount) {
if (darkAmount == mDarkAmount) {
return;
}
mDarkAmount = darkAmount;
dozeTimeTick();
}
public View getIndicationArea() {
return mIndicationArea;
}
@Override
public boolean hasOverlappingRendering() {
return false;
}
private void startFinishDozeAnimation() {
long delay = 0;
if (mWalletButton.getVisibility() == View.VISIBLE) {
startFinishDozeAnimationElement(mWalletButton, delay);
}
if (mQRCodeScannerButton.getVisibility() == View.VISIBLE) {
startFinishDozeAnimationElement(mQRCodeScannerButton, delay);
}
if (mControlsButton.getVisibility() == View.VISIBLE) {
startFinishDozeAnimationElement(mControlsButton, delay);
}
}
private void startFinishDozeAnimationElement(View element, long delay) {
element.setAlpha(0f);
element.setTranslationY(element.getHeight() / 2);
element.animate()
.alpha(1f)
.translationY(0f)
.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
.setStartDelay(delay)
.setDuration(DOZE_ANIMATION_ELEMENT_DURATION);
}
public void setDozing(boolean dozing, boolean animate) {
mDozing = dozing;
updateWalletVisibility();
updateControlsVisibility();
updateQRCodeButtonVisibility();
if (dozing) {
mOverlayContainer.setVisibility(INVISIBLE);
} else {
mOverlayContainer.setVisibility(VISIBLE);
if (animate) {
startFinishDozeAnimation();
}
}
}
public void dozeTimeTick() {
int burnInYOffset = getBurnInOffset(mBurnInYOffset * 2, false /* xAxis */)
- mBurnInYOffset;
mIndicationArea.setTranslationY(burnInYOffset * mDarkAmount);
if (mAmbientIndicationArea != null) {
mAmbientIndicationArea.setTranslationY(burnInYOffset * mDarkAmount);
}
}
public void setAntiBurnInOffsetX(int burnInXOffset) {
if (mBurnInXOffset == burnInXOffset) {
return;
}
mBurnInXOffset = burnInXOffset;
mIndicationArea.setTranslationX(burnInXOffset);
if (mAmbientIndicationArea != null) {
mAmbientIndicationArea.setTranslationX(burnInXOffset);
}
}
/**
* Sets the alpha of the indication areas and affordances, excluding the lock icon.
*/
public void setAffordanceAlpha(float alpha) {
mIndicationArea.setAlpha(alpha);
mWalletButton.setAlpha(alpha);
mQRCodeScannerButton.setAlpha(alpha);
mControlsButton.setAlpha(alpha);
}
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
int bottom = insets.getDisplayCutout() != null
? insets.getDisplayCutout().getSafeInsetBottom() : 0;
if (isPaddingRelative()) {
setPaddingRelative(getPaddingStart(), getPaddingTop(), getPaddingEnd(), bottom);
} else {
setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), bottom);
}
return insets;
}
private void updateQRCodeButtonVisibility() {
if (mQuickAccessWalletController != null
&& mQuickAccessWalletController.isWalletEnabled()) {
// Don't enable if quick access wallet is enabled
return;
}
if (mQRCodeScannerController != null
&& mQRCodeScannerController.isEnabledForLockScreenButton()) {
mQRCodeScannerButton.setVisibility(VISIBLE);
mQRCodeScannerButton.setOnClickListener(this::onQRCodeScannerClicked);
mIndicationArea.setPadding(mIndicationPadding, 0, mIndicationPadding, 0);
} else {
mQRCodeScannerButton.setVisibility(GONE);
if (mControlsButton.getVisibility() == GONE) {
mIndicationArea.setPadding(0, 0, 0, 0);
}
}
}
private void onQRCodeScannerClicked(View view) {
Intent intent = mQRCodeScannerController.getIntent();
if (intent != null) {
try {
ActivityTaskManager.getService().startActivityAsUser(
null, getContext().getBasePackageName(),
getContext().getAttributionTag(), intent,
intent.resolveTypeIfNeeded(getContext().getContentResolver()),
null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, null,
UserHandle.CURRENT.getIdentifier());
} catch (RemoteException e) {
// This is unexpected. Nonetheless, just log the error and prevent the UI from
// crashing
Log.e(TAG, "Unexpected intent: " + intent
+ " when the QR code scanner button was clicked");
}
}
}
private void updateAffordanceColors() {
int iconColor = Utils.getColorAttrDefaultColor(
mContext,
com.android.internal.R.attr.textColorPrimary);
mWalletButton.getDrawable().setTint(iconColor);
mControlsButton.getDrawable().setTint(iconColor);
mQRCodeScannerButton.getDrawable().setTint(iconColor);
ColorStateList bgColor = Utils.getColorAttr(
mContext,
com.android.internal.R.attr.colorSurface);
mWalletButton.setBackgroundTintList(bgColor);
mControlsButton.setBackgroundTintList(bgColor);
mQRCodeScannerButton.setBackgroundTintList(bgColor);
}
private void onWalletClick(View v) {
// More coming here; need to inform the user about how to proceed
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return;
}
ActivityLaunchAnimator.Controller animationController = createLaunchAnimationController(v);
mQuickAccessWalletController.startQuickAccessUiIntent(
mActivityStarter, animationController, mHasCard);
}
protected ActivityLaunchAnimator.Controller createLaunchAnimationController(View view) {
return ActivityLaunchAnimator.Controller.fromView(view, null);
}
private void onControlsClick(View v) {
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return;
}
Intent intent = new Intent(mContext, ControlsActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(ControlsUiController.EXTRA_ANIMATE, true);
ActivityLaunchAnimator.Controller controller =
v != null ? ActivityLaunchAnimator.Controller.fromView(v, null /* cujType */)
: null;
if (mControlsComponent.getVisibility() == AVAILABLE) {
mActivityStarter.startActivity(intent, true /* dismissShade */, controller,
true /* showOverLockscreenWhenLocked */);
} else {
mActivityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */, controller);
}
}
private class WalletCardRetriever implements
QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
@Override
public void onWalletCardsRetrieved(@NonNull GetWalletCardsResponse response) {
mHasCard = !response.getWalletCards().isEmpty();
Drawable tileIcon = mQuickAccessWalletController.getWalletClient().getTileIcon();
if (tileIcon != null) {
mWalletButton.setImageDrawable(tileIcon);
}
post(() -> {
updateWalletVisibility();
updateAffordanceColors();
});
}
@Override
public void onWalletCardRetrievalError(@NonNull GetWalletCardsError error) {
mHasCard = false;
post(() -> {
updateWalletVisibility();
updateAffordanceColors();
});
}
}
}