diff options
Diffstat (limited to 'packages/SystemUI/src')
140 files changed, 6169 insertions, 3647 deletions
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java index 2511f74e4ab6..658d9e3593c0 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java @@ -46,6 +46,7 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout protected View mEcaView; protected boolean mEnableHaptics; private boolean mDismissing; + protected boolean mResumed; private CountDownTimer mCountdownTimer = null; // To avoid accidental lockout due to events while the device in in the pocket, ignore @@ -264,6 +265,8 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout @Override public void onPause() { + mResumed = false; + if (mCountdownTimer != null) { mCountdownTimer.cancel(); mCountdownTimer = null; @@ -277,6 +280,7 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout @Override public void onResume(int reason) { + mResumed = true; } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 570d351a8b71..1aff3949a74b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -1,9 +1,15 @@ package com.android.keyguard; +import android.content.ContentResolver; import android.content.Context; +import android.database.ContentObserver; import android.graphics.Paint; import android.graphics.Paint.Style; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; import android.util.AttributeSet; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -12,18 +18,29 @@ import android.widget.TextClock; import androidx.annotation.VisibleForTesting; +import com.android.keyguard.clock.BubbleClockController; +import com.android.keyguard.clock.StretchAnalogClockController; +import com.android.keyguard.clock.TypeClockController; import com.android.systemui.Dependency; import com.android.systemui.plugins.ClockPlugin; -import com.android.systemui.plugins.PluginListener; -import com.android.systemui.shared.plugins.PluginManager; +import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.policy.ExtensionController; +import com.android.systemui.statusbar.policy.ExtensionController.Extension; import java.util.Objects; import java.util.TimeZone; +import java.util.function.Consumer; +import java.util.function.Supplier; /** * Switch to show plugin clock when plugin is connected, otherwise it will show default clock. */ public class KeyguardClockSwitch extends RelativeLayout { + + private LayoutInflater mLayoutInflater; + + private final ContentResolver mContentResolver; /** * Optional/alternative clock injected via plugin. */ @@ -45,43 +62,48 @@ public class KeyguardClockSwitch extends RelativeLayout { * or not to show it below the alternate clock. */ private View mKeyguardStatusArea; + /** + * Used to select between plugin or default implementations of ClockPlugin interface. + */ + private Extension<ClockPlugin> mClockExtension; + /** + * Consumer that accepts the a new ClockPlugin implementation when the Extension reloads. + */ + private final Consumer<ClockPlugin> mClockPluginConsumer = plugin -> setClockPlugin(plugin); + /** + * Maintain state so that a newly connected plugin can be initialized. + */ + private float mDarkAmount; - private final PluginListener<ClockPlugin> mClockPluginListener = - new PluginListener<ClockPlugin>() { + private final StatusBarStateController.StateListener mStateListener = + new StatusBarStateController.StateListener() { @Override - public void onPluginConnected(ClockPlugin plugin, Context pluginContext) { - disconnectPlugin(); - View smallClockView = plugin.getView(); - if (smallClockView != null) { - // For now, assume that the most recently connected plugin is the - // selected clock face. In the future, the user should be able to - // pick a clock face from the available plugins. - mSmallClockFrame.addView(smallClockView, -1, - new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT)); - initPluginParams(); - mClockView.setVisibility(View.GONE); - } - View bigClockView = plugin.getBigClockView(); - if (bigClockView != null && mBigClockContainer != null) { - mBigClockContainer.addView(bigClockView); - mBigClockContainer.setVisibility(View.VISIBLE); + public void onStateChanged(int newState) { + if (mBigClockContainer == null) { + return; } - if (!plugin.shouldShowStatusArea()) { - mKeyguardStatusArea.setVisibility(View.GONE); + if (newState == StatusBarState.SHADE) { + if (mBigClockContainer.getVisibility() == View.VISIBLE) { + mBigClockContainer.setVisibility(View.INVISIBLE); + } + } else { + if (mBigClockContainer.getVisibility() == View.INVISIBLE) { + mBigClockContainer.setVisibility(View.VISIBLE); + } } - mClockPlugin = plugin; } + }; + private final ContentObserver mContentObserver = + new ContentObserver(new Handler(Looper.getMainLooper())) { @Override - public void onPluginDisconnected(ClockPlugin plugin) { - if (Objects.equals(plugin, mClockPlugin)) { - disconnectPlugin(); - mClockView.setVisibility(View.VISIBLE); - mKeyguardStatusArea.setVisibility(View.VISIBLE); + public void onChange(boolean selfChange) { + super.onChange(selfChange); + if (mClockExtension != null) { + mClockExtension.reload(); } } - }; + }; public KeyguardClockSwitch(Context context) { this(context, null); @@ -89,6 +111,15 @@ public class KeyguardClockSwitch extends RelativeLayout { public KeyguardClockSwitch(Context context, AttributeSet attrs) { super(context, attrs); + mLayoutInflater = LayoutInflater.from(context); + mContentResolver = context.getContentResolver(); + } + + /** + * Returns if this view is presenting a custom clock, or the default implementation. + */ + public boolean hasCustomClock() { + return mClockPlugin != null; } @Override @@ -102,14 +133,88 @@ public class KeyguardClockSwitch extends RelativeLayout { @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - Dependency.get(PluginManager.class).addPluginListener(mClockPluginListener, - ClockPlugin.class); + mClockExtension = Dependency.get(ExtensionController.class).newExtension(ClockPlugin.class) + .withPlugin(ClockPlugin.class) + .withCallback(mClockPluginConsumer) + // Using withDefault even though this isn't the default as a workaround. + // ExtensionBulider doesn't provide the ability to supply a ClockPlugin + // instance based off of the value of a setting. Since multiple "default" + // can be provided, using a supplier that changes the settings value. + // A null return will cause Extension#reload to look at the next "default" + // supplier. + .withDefault( + new SettingsGattedSupplier( + mContentResolver, + Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, + BubbleClockController.class.getName(), + () -> BubbleClockController.build(mLayoutInflater))) + .withDefault( + new SettingsGattedSupplier( + mContentResolver, + Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, + StretchAnalogClockController.class.getName(), + () -> StretchAnalogClockController.build(mLayoutInflater))) + .withDefault( + new SettingsGattedSupplier( + mContentResolver, + Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, + TypeClockController.class.getName(), + () -> TypeClockController.build(mLayoutInflater))) + .build(); + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE), + false, mContentObserver); + Dependency.get(StatusBarStateController.class).addCallback(mStateListener); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - Dependency.get(PluginManager.class).removePluginListener(mClockPluginListener); + mClockExtension.destroy(); + mContentResolver.unregisterContentObserver(mContentObserver); + Dependency.get(StatusBarStateController.class).removeCallback(mStateListener); + } + + private void setClockPlugin(ClockPlugin plugin) { + // Disconnect from existing plugin. + if (mClockPlugin != null) { + View smallClockView = mClockPlugin.getView(); + if (smallClockView != null && smallClockView.getParent() == mSmallClockFrame) { + mSmallClockFrame.removeView(smallClockView); + } + if (mBigClockContainer != null) { + mBigClockContainer.removeAllViews(); + mBigClockContainer.setVisibility(View.GONE); + } + mClockPlugin = null; + } + if (plugin == null) { + mClockView.setVisibility(View.VISIBLE); + mKeyguardStatusArea.setVisibility(View.VISIBLE); + return; + } + // Attach small and big clock views to hierarchy. + View smallClockView = plugin.getView(); + if (smallClockView != null) { + mSmallClockFrame.addView(smallClockView, -1, + new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + mClockView.setVisibility(View.GONE); + } + View bigClockView = plugin.getBigClockView(); + if (bigClockView != null && mBigClockContainer != null) { + mBigClockContainer.addView(bigClockView); + mBigClockContainer.setVisibility(View.VISIBLE); + } + // Hide default clock. + if (!plugin.shouldShowStatusArea()) { + mKeyguardStatusArea.setVisibility(View.GONE); + } + // Initialize plugin parameters. + mClockPlugin = plugin; + mClockPlugin.setStyle(getPaint().getStyle()); + mClockPlugin.setTextColor(getCurrentTextColor()); + mClockPlugin.setDarkAmount(mDarkAmount); } /** @@ -150,10 +255,6 @@ public class KeyguardClockSwitch extends RelativeLayout { mClockView.setShowCurrentUserTime(showCurrentUserTime); } - public void setElegantTextHeight(boolean elegant) { - mClockView.setElegantTextHeight(elegant); - } - public void setTextSize(int unit, float size) { mClockView.setTextSize(unit, size); } @@ -171,6 +272,7 @@ public class KeyguardClockSwitch extends RelativeLayout { * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake. */ public void setDarkAmount(float darkAmount) { + mDarkAmount = darkAmount; if (mClockPlugin != null) { mClockPlugin.setDarkAmount(darkAmount); } @@ -210,32 +312,53 @@ public class KeyguardClockSwitch extends RelativeLayout { } } + @VisibleForTesting (otherwise = VisibleForTesting.NONE) + Consumer<ClockPlugin> getClockPluginConsumer() { + return mClockPluginConsumer; + } + + @VisibleForTesting (otherwise = VisibleForTesting.NONE) + StatusBarStateController.StateListener getStateListener() { + return mStateListener; + } + /** - * When plugin changes, set all kept parameters into newer plugin. + * Supplier that only gets an instance when a settings value matches expected value. */ - private void initPluginParams() { - if (mClockPlugin != null) { - mClockPlugin.setStyle(getPaint().getStyle()); - mClockPlugin.setTextColor(getCurrentTextColor()); - } - } + private static class SettingsGattedSupplier implements Supplier<ClockPlugin> { - private void disconnectPlugin() { - if (mClockPlugin != null) { - View smallClockView = mClockPlugin.getView(); - if (smallClockView != null) { - mSmallClockFrame.removeView(smallClockView); - } - if (mBigClockContainer != null) { - mBigClockContainer.removeAllViews(); - mBigClockContainer.setVisibility(View.GONE); - } - mClockPlugin = null; + private final ContentResolver mContentResolver; + private final String mKey; + private final String mValue; + private final Supplier<ClockPlugin> mSupplier; + + /** + * Constructs a supplier that changes secure setting key against value. + * + * @param contentResolver Used to look up settings value. + * @param key Settings key. + * @param value If the setting matches this values that get supplies a ClockPlugin + * instance. + * @param supplier Supplier of ClockPlugin instance, only used if the setting + * matches value. + */ + SettingsGattedSupplier(ContentResolver contentResolver, String key, String value, + Supplier<ClockPlugin> supplier) { + mContentResolver = contentResolver; + mKey = key; + mValue = value; + mSupplier = supplier; } - } - @VisibleForTesting (otherwise = VisibleForTesting.NONE) - PluginListener getClockPluginListener() { - return mClockPluginListener; + /** + * Returns null if the settings value doesn't match the expected value. + * + * A null return causes Extension#reload to skip this supplier and move to the next. + */ + @Override + public ClockPlugin get() { + final String currentValue = Settings.Secure.getString(mContentResolver, mKey); + return Objects.equals(currentValue, mValue) ? mSupplier.get() : null; + } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java index 41afa9a21128..64c5b1754fa8 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java @@ -18,6 +18,7 @@ package com.android.keyguard; import android.content.Context; import android.graphics.Rect; +import android.os.UserHandle; import android.text.Editable; import android.text.InputType; import android.text.TextUtils; @@ -79,8 +80,14 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView @Override protected void resetState() { + mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser())); mSecurityMessageDisplay.setMessage(""); final boolean wasDisabled = mPasswordEntry.isEnabled(); + // Don't set enabled password entry & showSoftInput when PasswordEntry is invisible or in + // pausing stage. + if (!mResumed || !mPasswordEntry.isVisibleToUser()) { + return; + } setPasswordEntryEnabled(true); setPasswordEntryInputEnabled(true); if (wasDisabled) { @@ -169,6 +176,7 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView Context.INPUT_METHOD_SERVICE); mPasswordEntry = findViewById(getPasswordTextViewId()); + mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser())); mPasswordEntryDisabler = new TextViewInputDisabler(mPasswordEntry); mPasswordEntry.setKeyListener(TextKeyListener.getInstance()); mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java index 669e6fff525b..bac7844c024b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -90,7 +90,7 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe */ private Runnable mContentChangeListener; private Slice mSlice; - private boolean mPulsing; + private boolean mHasHeader; public KeyguardSliceView(Context context) { this(context, null, 0); @@ -150,10 +150,18 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe Dependency.get(ConfigurationController.class).removeCallback(this); } + /** + * Returns whether the current visible slice has a title/header. + */ + public boolean hasHeader() { + return mHasHeader; + } + private void showSlice() { Trace.beginSection("KeyguardSliceView#showSlice"); - if (mPulsing || mSlice == null) { + if (mSlice == null) { mRow.setVisibility(GONE); + mHasHeader = false; if (mContentChangeListener != null) { mContentChangeListener.run(); } @@ -162,7 +170,7 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe ListContent lc = new ListContent(getContext(), mSlice); SliceContent headerContent = lc.getHeader(); - boolean hasHeader = headerContent != null + mHasHeader = headerContent != null && !headerContent.getSliceItem().hasHint(HINT_LIST_ITEM); List<SliceContent> subItems = new ArrayList<>(); for (int i = 0; i < lc.getRowItems().size(); i++) { @@ -177,7 +185,7 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe mClickActions.clear(); final int subItemsCount = subItems.size(); final int blendedColor = getTextColor(); - final int startIndex = hasHeader ? 1 : 0; // First item is header; skip it + final int startIndex = mHasHeader ? 1 : 0; // First item is header; skip it mRow.setVisibility(subItemsCount > 0 ? VISIBLE : GONE); for (int i = startIndex; i < subItemsCount; i++) { RowContent rc = (RowContent) subItems.get(i); @@ -189,7 +197,7 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe button = new KeyguardSliceButton(mContext); button.setTextColor(blendedColor); button.setTag(itemTag); - final int viewIndex = i - (hasHeader ? 1 : 0); + final int viewIndex = i - (mHasHeader ? 1 : 0); mRow.addView(button, viewIndex); } @@ -234,18 +242,6 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe Trace.endSection(); } - public void setPulsing(boolean pulsing, boolean animate) { - mPulsing = pulsing; - LayoutTransition transition = getLayoutTransition(); - if (!animate) { - setLayoutTransition(null); - } - showSlice(); - if (!animate) { - setLayoutTransition(transition); - } - } - public void setDarkAmount(float darkAmount) { mDarkAmount = darkAmount; mRow.setDarkAmount(darkAmount); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java index c6f172684686..bb549ada6830 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java @@ -72,7 +72,6 @@ public class KeyguardStatusView extends GridLayout implements private ArraySet<View> mVisibleInDoze; private boolean mPulsing; - private boolean mWasPulsing; private float mDarkAmount = 0; private int mTextColor; private int mLastLayoutHeight; @@ -141,6 +140,13 @@ public class KeyguardStatusView extends GridLayout implements onDensityOrFontScaleChanged(); } + /** + * If we're presenting a custom clock of just the default one. + */ + public boolean hasCustomClock() { + return mClockView.hasCustomClock(); + } + private void setEnableMarquee(boolean enabled) { if (DEBUG) Log.v(TAG, "Schedule setEnableMarquee: " + (enabled ? "Enable" : "Disable")); if (enabled) { @@ -195,10 +201,6 @@ public class KeyguardStatusView extends GridLayout implements updateOwnerInfo(); updateLogoutView(); updateDark(); - - // Disable elegant text height because our fancy colon makes the ymin value huge for no - // reason. - mClockView.setElegantTextHeight(false); } /** @@ -207,7 +209,7 @@ public class KeyguardStatusView extends GridLayout implements private void onSliceContentChanged() { LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) mClockView.getLayoutParams(); - layoutParams.bottomMargin = mPulsing ? mSmallClockPadding : 0; + layoutParams.bottomMargin = mKeyguardSlice.hasHeader() ? mSmallClockPadding : 0; mClockView.setLayoutParams(layoutParams); } @@ -217,16 +219,16 @@ public class KeyguardStatusView extends GridLayout implements @Override public void onLayoutChange(View view, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { - int heightOffset = mPulsing || mWasPulsing ? 0 : getHeight() - mLastLayoutHeight; + boolean smallClock = mKeyguardSlice.hasHeader(); + int heightOffset = smallClock ? 0 : getHeight() - mLastLayoutHeight; long duration = KeyguardSliceView.DEFAULT_ANIM_DURATION; - long delay = mPulsing || mWasPulsing ? 0 : duration / 4; - mWasPulsing = false; + long delay = smallClock ? 0 : duration / 4; boolean shouldAnimate = mKeyguardSlice.getLayoutTransition() != null && mKeyguardSlice.getLayoutTransition().isRunning(); if (view == mClockView) { - float clockScale = mPulsing ? mSmallClockScale : 1; - Paint.Style style = mPulsing ? Paint.Style.FILL_AND_STROKE : Paint.Style.FILL; + float clockScale = smallClock ? mSmallClockScale : 1; + Paint.Style style = smallClock ? Paint.Style.FILL_AND_STROKE : Paint.Style.FILL; mClockView.animate().cancel(); if (shouldAnimate) { mClockView.setY(oldTop + heightOffset); @@ -431,15 +433,11 @@ public class KeyguardStatusView extends GridLayout implements } } - public void setPulsing(boolean pulsing, boolean animate) { + public void setPulsing(boolean pulsing) { if (mPulsing == pulsing) { return; } - if (mPulsing) { - mWasPulsing = true; - } mPulsing = pulsing; - mKeyguardSlice.setPulsing(pulsing, animate); updateDozeVisibleViews(); } diff --git a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java new file mode 100644 index 000000000000..db6127f1d573 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2019 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.keyguard.clock; + +import android.graphics.Paint.Style; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextClock; + +import com.android.keyguard.R; +import com.android.systemui.plugins.ClockPlugin; + +import java.util.TimeZone; + +/** + * Controller for Bubble clock that can appear on lock screen and AOD. + */ +public class BubbleClockController implements ClockPlugin { + + /** + * Custom clock shown on AOD screen and behind stack scroller on lock. + */ + private View mView; + private TextClock mDigitalClock; + private ImageClock mAnalogClock; + + /** + * Small clock shown on lock screen above stack scroller. + */ + private View mLockClockContainer; + private TextClock mLockClock; + + /** + * Controller for transition to dark state. + */ + private CrossFadeDarkController mDarkController; + + private BubbleClockController() { } + + /** + * Create a BubbleClockController instance. + * + * @param layoutInflater Inflater used to inflate custom clock views. + */ + public static BubbleClockController build(LayoutInflater layoutInflater) { + BubbleClockController controller = new BubbleClockController(); + controller.createViews(layoutInflater); + return controller; + } + + private void createViews(LayoutInflater layoutInflater) { + mView = layoutInflater.inflate(R.layout.bubble_clock, null); + mDigitalClock = (TextClock) mView.findViewById(R.id.digital_clock); + mAnalogClock = (ImageClock) mView.findViewById(R.id.analog_clock); + + mLockClockContainer = layoutInflater.inflate(R.layout.digital_clock, null); + mLockClock = (TextClock) mLockClockContainer.findViewById(R.id.lock_screen_clock); + mLockClock.setVisibility(View.GONE); + + mDarkController = new CrossFadeDarkController(mDigitalClock, mLockClock); + } + + @Override + public View getView() { + return mLockClockContainer; + } + + @Override + public View getBigClockView() { + return mView; + } + + @Override + public void setStyle(Style style) {} + + @Override + public void setTextColor(int color) { + mLockClock.setTextColor(color); + mDigitalClock.setTextColor(color); + } + + @Override + public void dozeTimeTick() { + mAnalogClock.onTimeChanged(); + } + + @Override + public void setDarkAmount(float darkAmount) { + mDarkController.setDarkAmount(darkAmount); + } + + @Override + public void onTimeZoneChanged(TimeZone timeZone) { + mAnalogClock.onTimeZoneChanged(timeZone); + } + + @Override + public boolean shouldShowStatusArea() { + return false; + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockLayout.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockLayout.java new file mode 100644 index 000000000000..3591dc82c8ec --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockLayout.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2019 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.keyguard.clock; + +import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset; + +import android.content.Context; +import android.content.res.Resources; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; + +import com.android.keyguard.R; + +/** + * Positions clock faces (analog, digital, typographic) and handles pixel shifting + * to prevent screen burn-in. + */ +public class ClockLayout extends FrameLayout { + + /** + * Clock face views. + */ + private View mDigitalClock; + private View mAnalogClock; + private View mTypeClock; + + /** + * Pixel shifting amplitidues used to prevent screen burn-in. + */ + private int mBurnInPreventionOffsetX; + private int mBurnInPreventionOffsetY; + + public ClockLayout(Context context) { + this(context, null); + } + + public ClockLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ClockLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mDigitalClock = findViewById(R.id.digital_clock); + mAnalogClock = findViewById(R.id.analog_clock); + mTypeClock = findViewById(R.id.type_clock); + + // Get pixel shifting X, Y amplitudes from resources. + Resources resources = getResources(); + mBurnInPreventionOffsetX = resources.getDimensionPixelSize( + R.dimen.burn_in_prevention_offset_x); + mBurnInPreventionOffsetY = resources.getDimensionPixelSize( + R.dimen.burn_in_prevention_offset_y); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + final float offsetX = getBurnInOffset(mBurnInPreventionOffsetX, true); + final float offsetY = getBurnInOffset(mBurnInPreventionOffsetY, false); + + // Put digital clock in two left corner of the screen. + if (mDigitalClock != null) { + mDigitalClock.setX(0.1f * getWidth() + offsetX); + mDigitalClock.setY(0.1f * getHeight() + offsetY); + } + + // Put the analog clock in the middle of the screen. + if (mAnalogClock != null) { + mAnalogClock.setX(Math.max(0f, 0.5f * (getWidth() - mAnalogClock.getWidth())) + + offsetX); + mAnalogClock.setY(Math.max(0f, 0.5f * (getHeight() - mAnalogClock.getHeight())) + + offsetY); + } + + // Put the typographic clock part way down the screen. + if (mTypeClock != null) { + mTypeClock.setX(offsetX); + mTypeClock.setY(0.2f * getHeight() + offsetY); + } + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/clock/CrossFadeDarkController.java b/packages/SystemUI/src/com/android/keyguard/clock/CrossFadeDarkController.java new file mode 100644 index 000000000000..3c3f4759614b --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/clock/CrossFadeDarkController.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2019 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.keyguard.clock; + +import android.view.View; + +/** + * Controls transition to dark state by cross fading between views. + */ +final class CrossFadeDarkController { + + private final View mFadeInView; + private final View mFadeOutView; + + /** + * Creates a new controller that fades between views. + * + * @param fadeInView View to fade in when transitioning to AOD. + * @param fadeOutView View to fade out when transitioning to AOD. + */ + CrossFadeDarkController(View fadeInView, View fadeOutView) { + mFadeInView = fadeInView; + mFadeOutView = fadeOutView; + } + + /** + * Sets the amount the system has transitioned to the dark state. + * + * @param darkAmount Amount of transition to dark state: 1f for AOD and 0f for lock screen. + */ + void setDarkAmount(float darkAmount) { + mFadeInView.setAlpha(Math.max(0f, 2f * darkAmount - 1f)); + if (darkAmount == 0f) { + mFadeInView.setVisibility(View.GONE); + } else { + if (mFadeInView.getVisibility() == View.GONE) { + mFadeInView.setVisibility(View.VISIBLE); + } + } + mFadeOutView.setAlpha(Math.max(0f, 1f - 2f * darkAmount)); + if (darkAmount == 1f) { + mFadeOutView.setVisibility(View.GONE); + } else { + if (mFadeOutView.getVisibility() == View.GONE) { + mFadeOutView.setVisibility(View.VISIBLE); + } + } + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ImageClock.java b/packages/SystemUI/src/com/android/keyguard/clock/ImageClock.java new file mode 100644 index 000000000000..2c709e0393bd --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/clock/ImageClock.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2019 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.keyguard.clock; + +import android.content.Context; +import android.text.format.DateFormat; +import android.util.AttributeSet; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import com.android.keyguard.R; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.TimeZone; + +/** + * Clock composed of two images that rotate with the time. + * + * The images are the clock hands. ImageClock expects two child ImageViews + * with ids hour_hand and minute_hand. + */ +public class ImageClock extends FrameLayout { + + private ImageView mHourHand; + private ImageView mMinuteHand; + private Calendar mTime; + private String mDescFormat; + private TimeZone mTimeZone; + + public ImageClock(Context context) { + this(context, null); + } + + public ImageClock(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ImageClock(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mTime = Calendar.getInstance(); + mDescFormat = ((SimpleDateFormat) DateFormat.getTimeFormat(context)).toLocalizedPattern(); + } + + /** + * Call when the time changes to update the rotation of the clock hands. + */ + public void onTimeChanged() { + mTime.setTimeInMillis(System.currentTimeMillis()); + final float hourAngle = mTime.get(Calendar.HOUR) * 30f; + mHourHand.setRotation(hourAngle); + final float minuteAngle = mTime.get(Calendar.MINUTE) * 6f; + mMinuteHand.setRotation(minuteAngle); + setContentDescription(DateFormat.format(mDescFormat, mTime)); + invalidate(); + } + + /** + * Call when the time zone has changed to update clock hands. + * + * @param timeZone The updated time zone that will be used. + */ + public void onTimeZoneChanged(TimeZone timeZone) { + mTimeZone = timeZone; + mTime.setTimeZone(timeZone); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mHourHand = findViewById(R.id.hour_hand); + mMinuteHand = findViewById(R.id.minute_hand); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mTime = Calendar.getInstance(mTimeZone != null ? mTimeZone : TimeZone.getDefault()); + onTimeChanged(); + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClock.java b/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClock.java new file mode 100644 index 000000000000..8734754541a6 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClock.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2019 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.keyguard.clock; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.view.View; + +import java.util.Calendar; +import java.util.TimeZone; + +/** + * Analog clock where the minute hand extends off of the screen. + */ +public class StretchAnalogClock extends View { + + private static final int DEFAULT_COLOR = Color.parseColor("#F5C983"); + private static final float HOUR_STROKE_WIDTH = 60f; + private static final float MINUTE_STROKE_WIDTH = 20f; + private static final float CENTER_GAP_AND_CIRCLE_RADIUS = 80f; + + private final Paint mHourPaint = new Paint(); + private final Paint mMinutePaint = new Paint(); + private Calendar mTime; + private TimeZone mTimeZone; + + public StretchAnalogClock(Context context) { + this(context, null); + } + + public StretchAnalogClock(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public StretchAnalogClock(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public StretchAnalogClock(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(); + } + + /** + * Call when the time changes to update the clock hands. + */ + public void onTimeChanged() { + mTime.setTimeInMillis(System.currentTimeMillis()); + invalidate(); + } + + /** + * Call when the time zone has changed to update clock hands. + * + * @param timeZone The updated time zone that will be used. + */ + public void onTimeZoneChanged(TimeZone timeZone) { + mTime.setTimeZone(timeZone); + } + + /** + * Set the color of the minute hand. + */ + public void setMinuteHandColor(int color) { + mMinutePaint.setColor(color); + invalidate(); + } + + private void init() { + mHourPaint.setColor(DEFAULT_COLOR); + mHourPaint.setStrokeWidth(HOUR_STROKE_WIDTH); + mHourPaint.setAntiAlias(true); + mHourPaint.setStrokeCap(Paint.Cap.ROUND); + + mMinutePaint.setColor(Color.WHITE); + mMinutePaint.setStrokeWidth(MINUTE_STROKE_WIDTH); + mMinutePaint.setAntiAlias(true); + mMinutePaint.setStrokeCap(Paint.Cap.ROUND); + } + + @Override + protected void onDraw(Canvas canvas) { + final float centerX = getWidth() / 2f; + final float centerY = getHeight() / 2f; + + final float minutesRotation = mTime.get(Calendar.MINUTE) * 6f; + final float hoursRotation = (mTime.get(Calendar.HOUR) * 30); + + // Compute length of clock hands. Hour hand is 60% the length from center to edge + // and minute hand is twice the length to make sure it extends past screen edge. + double sMinuteHandLengthFactor = Math.sin(2d * Math.PI * minutesRotation / 360d); + float sMinuteHandLength = (float) (2d * (centerY + (centerX - centerY) + * sMinuteHandLengthFactor * sMinuteHandLengthFactor)); + double sHourHandLengthFactor = Math.sin(2d * Math.PI * hoursRotation / 360d); + float sHourHandLength = (float) (0.6d * (centerY + (centerX - centerY) + * sHourHandLengthFactor * sHourHandLengthFactor)); + + canvas.save(); + + canvas.rotate(minutesRotation, centerX, centerY); + canvas.drawLine( + centerX, + centerY + CENTER_GAP_AND_CIRCLE_RADIUS, + centerX, + centerY - sMinuteHandLength, + mMinutePaint); + + canvas.rotate(hoursRotation - minutesRotation, centerX, centerY); + canvas.drawLine( + centerX, + centerY + CENTER_GAP_AND_CIRCLE_RADIUS, + centerX, + centerY - sHourHandLength, + mHourPaint); + + canvas.restore(); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mTime = Calendar.getInstance(mTimeZone != null ? mTimeZone : TimeZone.getDefault()); + onTimeChanged(); + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClockController.java new file mode 100644 index 000000000000..0a39158cd4be --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClockController.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2019 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.keyguard.clock; + +import android.graphics.Paint.Style; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextClock; + +import com.android.keyguard.R; +import com.android.systemui.plugins.ClockPlugin; + +import java.util.TimeZone; + +/** + * Controller for Stretch clock that can appear on lock screen and AOD. + */ +public class StretchAnalogClockController implements ClockPlugin { + + /** + * Custom clock shown on AOD screen and behind stack scroller on lock. + */ + private View mBigClockView; + private TextClock mDigitalClock; + private StretchAnalogClock mAnalogClock; + + /** + * Small clock shown on lock screen above stack scroller. + */ + private View mView; + private TextClock mLockClock; + + /** + * Controller for transition to dark state. + */ + private CrossFadeDarkController mDarkController; + + private StretchAnalogClockController() { } + + /** + * Create a BubbleClockController instance. + * + * @param layoutInflater Inflater used to inflate custom clock views. + */ + public static StretchAnalogClockController build(LayoutInflater layoutInflater) { + StretchAnalogClockController controller = new StretchAnalogClockController(); + controller.createViews(layoutInflater); + return controller; + } + + private void createViews(LayoutInflater layoutInflater) { + mBigClockView = layoutInflater.inflate(R.layout.stretchanalog_clock, null); + mAnalogClock = mBigClockView.findViewById(R.id.analog_clock); + mDigitalClock = mBigClockView.findViewById(R.id.digital_clock); + + mView = layoutInflater.inflate(R.layout.digital_clock, null); + mLockClock = mView.findViewById(R.id.lock_screen_clock); + mLockClock.setVisibility(View.GONE); + + mDarkController = new CrossFadeDarkController(mDigitalClock, mLockClock); + } + + @Override + public View getView() { + return mView; + } + + @Override + public View getBigClockView() { + return mBigClockView; + } + + @Override + public void setStyle(Style style) {} + + @Override + public void setTextColor(int color) { + mLockClock.setTextColor(color); + mDigitalClock.setTextColor(color); + mAnalogClock.setMinuteHandColor(color); + } + + @Override + public void dozeTimeTick() { + mAnalogClock.onTimeChanged(); + } + + @Override + public void setDarkAmount(float darkAmount) { + mDarkController.setDarkAmount(darkAmount); + } + + @Override + public void onTimeZoneChanged(TimeZone timeZone) { + mAnalogClock.onTimeZoneChanged(timeZone); + } + + @Override + public boolean shouldShowStatusArea() { + return false; + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/clock/TypeClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/TypeClockController.java new file mode 100644 index 000000000000..17d929dc8a3b --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/clock/TypeClockController.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2019 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.keyguard.clock; + +import android.graphics.Paint.Style; +import android.view.LayoutInflater; +import android.view.View; + +import com.android.keyguard.R; +import com.android.systemui.plugins.ClockPlugin; + +import java.util.TimeZone; + +/** + * Plugin for a custom Typographic clock face that displays the time in words. + */ +public class TypeClockController implements ClockPlugin { + + /** + * Custom clock shown on AOD screen and behind stack scroller on lock. + */ + private View mView; + private TypographicClock mTypeClock; + + /** + * Small clock shown on lock screen above stack scroller. + */ + private View mLockClockContainer; + + /** + * Controller for transition into dark state. + */ + private CrossFadeDarkController mDarkController; + + private TypeClockController() {} + + /** + * Create a TypeClockController instance. + * + * @param inflater Inflater used to inflate custom clock views. + */ + public static TypeClockController build(LayoutInflater inflater) { + TypeClockController controller = new TypeClockController(); + controller.createViews(inflater); + return controller; + } + + private void createViews(LayoutInflater inflater) { + mView = inflater.inflate(R.layout.type_clock, null); + mTypeClock = mView.findViewById(R.id.type_clock); + + // For now, this view is used to hide the default digital clock. + // Need better transition to lock screen. + mLockClockContainer = inflater.inflate(R.layout.digital_clock, null); + mLockClockContainer.setVisibility(View.GONE); + } + + @Override + public View getView() { + return mLockClockContainer; + } + + @Override + public View getBigClockView() { + return mView; + } + + @Override + public void setStyle(Style style) {} + + @Override + public void setTextColor(int color) { + mTypeClock.setTextColor(color); + } + + @Override + public void dozeTimeTick() { + mTypeClock.onTimeChanged(); + } + + @Override + public void setDarkAmount(float darkAmount) {} + + @Override + public void onTimeZoneChanged(TimeZone timeZone) { + mTypeClock.onTimeZoneChanged(timeZone); + } + + @Override + public boolean shouldShowStatusArea() { + return false; + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/clock/TypographicClock.java b/packages/SystemUI/src/com/android/keyguard/clock/TypographicClock.java new file mode 100644 index 000000000000..5f9da3ee33bb --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/clock/TypographicClock.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2019 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.keyguard.clock; + +import android.content.Context; +import android.content.res.Resources; +import android.text.format.DateFormat; +import android.util.AttributeSet; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.keyguard.R; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.TimeZone; + +/** + * Clock that presents the time in words. + */ +public class TypographicClock extends LinearLayout { + + private final String[] mHours; + private final String[] mMinutes; + private TextView mHeaderText; + private TextView mHourText; + private TextView mMinuteText; + private Calendar mTime; + private String mDescFormat; + private TimeZone mTimeZone; + + public TypographicClock(Context context) { + this(context, null); + } + + public TypographicClock(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public TypographicClock(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mTime = Calendar.getInstance(); + mDescFormat = ((SimpleDateFormat) DateFormat.getTimeFormat(context)).toLocalizedPattern(); + Resources res = context.getResources(); + mHours = res.getStringArray(R.array.type_clock_hours); + mMinutes = res.getStringArray(R.array.type_clock_minutes); + } + + /** + * Call when the time changes to update the text of the time. + */ + public void onTimeChanged() { + mTime.setTimeInMillis(System.currentTimeMillis()); + setContentDescription(DateFormat.format(mDescFormat, mTime)); + final int hour = mTime.get(Calendar.HOUR); + mHourText.setText(mHours[hour % 12]); + final int minute = mTime.get(Calendar.MINUTE); + mMinuteText.setText(mMinutes[minute % 60]); + invalidate(); + } + + /** + * Call when the time zone has changed to update clock time. + * + * @param timeZone The updated time zone that will be used. + */ + public void onTimeZoneChanged(TimeZone timeZone) { + mTimeZone = timeZone; + mTime.setTimeZone(timeZone); + } + + /** + * Set the color of the text used to display the time. + * + * This is necessary when the wallpaper shown behind the clock on the + * lock screen changes. + */ + public void setTextColor(int color) { + mHourText.setTextColor(color); + mMinuteText.setTextColor(color); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mHeaderText = findViewById(R.id.header); + mHourText = findViewById(R.id.hour); + mMinuteText = findViewById(R.id.minute); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mTime = Calendar.getInstance(mTimeZone != null ? mTimeZone : TimeZone.getDefault()); + onTimeChanged(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java index 6864ea185834..200679432200 100644 --- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java @@ -40,7 +40,6 @@ import android.util.TypedValue; import android.view.ContextThemeWrapper; import android.view.Gravity; import android.view.LayoutInflater; -import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.LinearLayout; @@ -56,7 +55,6 @@ import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; -import com.android.systemui.statusbar.policy.IconLogger; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; import com.android.systemui.util.Utils.DisableStateTracker; @@ -71,11 +69,12 @@ public class BatteryMeterView extends LinearLayout implements @Retention(SOURCE) - @IntDef({MODE_DEFAULT, MODE_ON, MODE_OFF}) + @IntDef({MODE_DEFAULT, MODE_ON, MODE_OFF, MODE_ESTIMATE}) public @interface BatteryPercentMode {} public static final int MODE_DEFAULT = 0; public static final int MODE_ON = 1; public static final int MODE_OFF = 2; + public static final int MODE_ESTIMATE = 3; private final BatteryMeterDrawableBase mDrawable; private final String mSlotBattery; @@ -93,6 +92,7 @@ public class BatteryMeterView extends LinearLayout implements // Some places may need to show the battery conditionally, and not obey the tuner private boolean mIgnoreTunerUpdates; private boolean mIsSubscribedForTunerUpdates; + private boolean mCharging; private int mDarkModeBackgroundColor; private int mDarkModeFillColor; @@ -276,9 +276,6 @@ public class BatteryMeterView extends LinearLayout implements public void onTuningChanged(String key, String newValue) { if (StatusBarIconController.ICON_BLACKLIST.equals(key)) { ArraySet<String> icons = StatusBarIconController.getIconBlacklist(newValue); - boolean hidden = icons.contains(mSlotBattery); - Dependency.get(IconLogger.class).onIconVisibility(mSlotBattery, !hidden); - setVisibility(hidden ? View.GONE : View.VISIBLE); } } @@ -308,6 +305,7 @@ public class BatteryMeterView extends LinearLayout implements public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { mDrawable.setBatteryLevel(level); mDrawable.setCharging(pluggedIn); + mCharging = pluggedIn; mLevel = level; updatePercentText(); setContentDescription( @@ -337,9 +335,19 @@ public class BatteryMeterView extends LinearLayout implements } private void updatePercentText() { + if (mBatteryController == null) { + return; + } + if (mBatteryPercentView != null) { - mBatteryPercentView.setText( - NumberFormat.getPercentInstance().format(mLevel / 100f)); + if (mShowPercentMode == MODE_ESTIMATE && !mCharging) { + mBatteryController.getEstimatedTimeRemainingString((String estimate) -> { + mBatteryPercentView.setText(estimate); + }); + } else { + mBatteryPercentView.setText( + NumberFormat.getPercentInstance().format(mLevel / 100f)); + } } } @@ -350,7 +358,7 @@ public class BatteryMeterView extends LinearLayout implements SHOW_BATTERY_PERCENT, 0, mUser); if ((mShowPercentAvailable && systemSetting && mShowPercentMode != MODE_OFF) - || mShowPercentMode == MODE_ON) { + || mShowPercentMode == MODE_ON || mShowPercentMode == MODE_ESTIMATE) { if (!showing) { mBatteryPercentView = loadPercentView(); if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor); diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index 3cc9bb6f668f..ec6ecc64d07e 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -56,12 +56,12 @@ import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.StatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.notification.NotificationAlertingManager; -import com.android.systemui.statusbar.notification.NotificationData.KeyguardEnvironment; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationFilter; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.NotificationRowBinder; import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -86,7 +86,6 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.FlashlightController; import com.android.systemui.statusbar.policy.HotspotController; -import com.android.systemui.statusbar.policy.IconLogger; import com.android.systemui.statusbar.policy.KeyguardMonitor; import com.android.systemui.statusbar.policy.LocationController; import com.android.systemui.statusbar.policy.NetworkController; @@ -217,7 +216,6 @@ public class Dependency extends SystemUI { @Inject Lazy<LeakDetector> mLeakDetector; @Inject Lazy<LeakReporter> mLeakReporter; @Inject Lazy<GarbageMonitor> mGarbageMonitor; - @Inject Lazy<IconLogger> mIconLogger; @Inject Lazy<TunerService> mTunerService; @Inject Lazy<StatusBarWindowController> mStatusBarWindowController; @Inject Lazy<DarkIconDispatcher> mDarkIconDispatcher; @@ -392,8 +390,6 @@ public class Dependency extends SystemUI { mProviders.put(PowerUI.WarningsUI.class, mWarningsUI::get); - mProviders.put(IconLogger.class, mIconLogger::get); - mProviders.put(LightBarController.class, mLightBarController::get); mProviders.put(IWindowManager.class, mIWindowManager::get); diff --git a/packages/SystemUI/src/com/android/systemui/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/DependencyBinder.java index f324a05bafff..ce9c637cebf6 100644 --- a/packages/SystemUI/src/com/android/systemui/DependencyBinder.java +++ b/packages/SystemUI/src/com/android/systemui/DependencyBinder.java @@ -46,8 +46,6 @@ import com.android.systemui.statusbar.policy.FlashlightController; import com.android.systemui.statusbar.policy.FlashlightControllerImpl; import com.android.systemui.statusbar.policy.HotspotController; import com.android.systemui.statusbar.policy.HotspotControllerImpl; -import com.android.systemui.statusbar.policy.IconLogger; -import com.android.systemui.statusbar.policy.IconLoggerImpl; import com.android.systemui.statusbar.policy.KeyguardMonitor; import com.android.systemui.statusbar.policy.KeyguardMonitorImpl; import com.android.systemui.statusbar.policy.LocationController; @@ -133,11 +131,6 @@ public abstract class DependencyBinder { /** */ @Binds - public abstract IconLogger provideIconLogger(IconLoggerImpl loggerImpl); - - /** - */ - @Binds public abstract CastController provideCastController(CastControllerImpl controllerImpl); /** diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java index 151a6b013110..96b62ac918ab 100644 --- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java +++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java @@ -24,9 +24,9 @@ import android.service.notification.StatusBarNotification; import android.util.Log; import com.android.internal.statusbar.NotificationVisibility; -import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import javax.inject.Inject; import javax.inject.Singleton; @@ -49,26 +49,21 @@ public class ForegroundServiceNotificationListener { mForegroundServiceController = foregroundServiceController; notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() { @Override - public void onPendingEntryAdded(NotificationData.Entry entry) { + public void onPendingEntryAdded(NotificationEntry entry) { addNotification(entry.notification, entry.importance); } @Override - public void onEntryUpdated(NotificationData.Entry entry) { + public void onPostEntryUpdated(NotificationEntry entry) { updateNotification(entry.notification, entry.importance); } @Override public void onEntryRemoved( - NotificationData.Entry entry, - String key, - StatusBarNotification old, + NotificationEntry entry, NotificationVisibility visibility, - boolean lifetimeExtended, boolean removedByUser) { - if (entry != null && !lifetimeExtended) { - removeNotification(entry.notification); - } + removeNotification(entry.notification); } }); } diff --git a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java index 16e869e9d317..e28aa9d369cb 100644 --- a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java +++ b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java @@ -37,23 +37,25 @@ import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; import com.android.systemui.util.leak.RotationUtils; -public class HardwareUiLayout extends LinearLayout implements Tunable { +/** + * Layout for placing two containers at a specific physical position on the device, relative to the + * device's hardware, regardless of screen rotation. + */ +public class HardwareUiLayout extends MultiListLayout implements Tunable { private static final String EDGE_BLEED = "sysui_hwui_edge_bleed"; private static final String ROUNDED_DIVIDER = "sysui_hwui_rounded_divider"; private final int[] mTmp2 = new int[2]; - private View mList; - private View mSeparatedView; + private ViewGroup mList; + private ViewGroup mSeparatedView; private int mOldHeight; private boolean mAnimating; private AnimatorSet mAnimation; private View mDivision; - private boolean mHasOutsideTouch; private HardwareBgDrawable mListBackground; private HardwareBgDrawable mSeparatedViewBackground; private Animator mAnimator; private boolean mCollapse; - private boolean mHasSeparatedButton; private int mEndPoint; private boolean mEdgeBleed; private boolean mRoundedDivider; @@ -67,6 +69,35 @@ public class HardwareUiLayout extends LinearLayout implements Tunable { } @Override + protected ViewGroup getSeparatedView() { + return findViewById(com.android.systemui.R.id.separated_button); + } + + @Override + protected ViewGroup getListView() { + return findViewById(android.R.id.list); + } + + @Override + public void removeAllItems() { + if (mList != null) { + mList.removeAllViews(); + } + if (mSeparatedView != null) { + mSeparatedView.removeAllViews(); + } + } + + @Override + public ViewGroup getParentView(boolean separated, int index) { + if (separated) { + return getSeparatedView(); + } else { + return getListView(); + } + } + + @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); updateSettings(); @@ -137,9 +168,9 @@ public class HardwareUiLayout extends LinearLayout implements Tunable { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mList == null) { if (getChildCount() != 0) { - mList = getChildAt(0); + mList = getListView(); mList.setBackground(mListBackground); - mSeparatedView = getChildAt(1); + mSeparatedView = getSeparatedView(); mSeparatedView.setBackground(mSeparatedViewBackground); updateEdgeMargin(mEdgeBleed ? 0 : getEdgePadding()); mOldHeight = mList.getMeasuredHeight(); @@ -187,7 +218,7 @@ public class HardwareUiLayout extends LinearLayout implements Tunable { } else { rotateLeft(); } - if (mHasSeparatedButton) { + if (mHasSeparatedView) { if (from == ROTATION_SEASCAPE || to == ROTATION_SEASCAPE) { // Separated view has top margin, so seascape separated view need special rotation, // not a full left or right rotation. @@ -408,8 +439,8 @@ public class HardwareUiLayout extends LinearLayout implements Tunable { if (mList == null) return; // If got separated button, setRotatedBackground to false, // all items won't get white background. - mListBackground.setRotatedBackground(mHasSeparatedButton); - mSeparatedViewBackground.setRotatedBackground(mHasSeparatedButton); + mListBackground.setRotatedBackground(mHasSeparatedView); + mSeparatedViewBackground.setRotatedBackground(mHasSeparatedView); if (mDivision != null && mDivision.getVisibility() == VISIBLE) { int index = mRotatedBackground ? 0 : 1; mDivision.getLocationOnScreen(mTmp2); @@ -460,21 +491,21 @@ public class HardwareUiLayout extends LinearLayout implements Tunable { case RotationUtils.ROTATION_LANDSCAPE: defaultTopPadding = getPaddingLeft(); viewsTotalHeight = mList.getMeasuredWidth() + mSeparatedView.getMeasuredWidth(); - separatedViewTopMargin = mHasSeparatedButton ? params.leftMargin : 0; + separatedViewTopMargin = mHasSeparatedView ? params.leftMargin : 0; screenHeight = getMeasuredWidth(); targetGravity = Gravity.CENTER_HORIZONTAL|Gravity.TOP; break; case RotationUtils.ROTATION_SEASCAPE: defaultTopPadding = getPaddingRight(); viewsTotalHeight = mList.getMeasuredWidth() + mSeparatedView.getMeasuredWidth(); - separatedViewTopMargin = mHasSeparatedButton ? params.leftMargin : 0; + separatedViewTopMargin = mHasSeparatedView ? params.leftMargin : 0; screenHeight = getMeasuredWidth(); targetGravity = Gravity.CENTER_HORIZONTAL|Gravity.BOTTOM; break; default: // Portrait defaultTopPadding = getPaddingTop(); viewsTotalHeight = mList.getMeasuredHeight() + mSeparatedView.getMeasuredHeight(); - separatedViewTopMargin = mHasSeparatedButton ? params.topMargin : 0; + separatedViewTopMargin = mHasSeparatedView ? params.topMargin : 0; screenHeight = getMeasuredHeight(); targetGravity = Gravity.CENTER_VERTICAL|Gravity.RIGHT; break; @@ -491,30 +522,10 @@ public class HardwareUiLayout extends LinearLayout implements Tunable { return super.getOutlineProvider(); } - public void setOutsideTouchListener(OnClickListener onClickListener) { - mHasOutsideTouch = true; - requestLayout(); - setOnClickListener(onClickListener); - setClickable(true); - setFocusable(true); - } - public void setCollapse() { mCollapse = true; } - public void setHasSeparatedButton(boolean hasSeparatedButton) { - mHasSeparatedButton = hasSeparatedButton; - } - - public static HardwareUiLayout get(View v) { - if (v instanceof HardwareUiLayout) return (HardwareUiLayout) v; - if (v.getParent() instanceof View) { - return get((View) v.getParent()); - } - return null; - } - private final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsListener = inoutInfo -> { if (mHasOutsideTouch || (mList == null)) { inoutInfo.setTouchableInsets( diff --git a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java new file mode 100644 index 000000000000..0c7a9a9fffd2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2019 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; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; + +/** + * Layout class representing the Global Actions menu which appears when the power button is held. + */ +public abstract class MultiListLayout extends LinearLayout { + boolean mHasOutsideTouch; + boolean mHasSeparatedView; + + int mExpectedSeparatedItemCount; + int mExpectedListItemCount; + + public MultiListLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + protected abstract ViewGroup getSeparatedView(); + + protected abstract ViewGroup getListView(); + + /** + * Removes all child items from the separated and list views, if they exist. + */ + public abstract void removeAllItems(); + + /** + * Get the parent view which will be used to contain the item at the specified index. + * @param separated Whether or not this index refers to a position in the separated or list + * container. + * @param index The index of the item within the container. + * @return The parent ViewGroup which will be used to contain the specified item + * after it has been added to the layout. + */ + public abstract ViewGroup getParentView(boolean separated, int index); + + /** + * Sets the divided view, which may have a differently-colored background. + */ + public abstract void setDivisionView(View v); + + /** + * Set the view accessibility delegate for the list view container. + */ + public void setListViewAccessibilityDelegate(View.AccessibilityDelegate delegate) { + getListView().setAccessibilityDelegate(delegate); + } + + protected void setSeparatedViewVisibility(boolean visible) { + getSeparatedView().setVisibility(visible ? View.VISIBLE : View.GONE); + } + + /** + * Sets the number of items expected to be rendered in the separated container. This allows the + * layout to correctly determine which parent containers will be used for items before they have + * beenadded to the layout. + * @param count The number of items expected. + */ + public void setExpectedSeparatedItemCount(int count) { + mExpectedSeparatedItemCount = count; + } + + /** + * Sets the number of items expected to be rendered in the list container. This allows the + * layout to correctly determine which parent containers will be used for items before they have + * beenadded to the layout. + * @param count The number of items expected. + */ + public void setExpectedListItemCount(int count) { + mExpectedListItemCount = count; + } + + /** + * Sets whether the separated view should be shown, and handles updating visibility on + * that view. + */ + public void setHasSeparatedView(boolean hasSeparatedView) { + mHasSeparatedView = hasSeparatedView; + setSeparatedViewVisibility(hasSeparatedView); + } + + /** + * Sets this layout to respond to an outside touch listener. + */ + public void setOutsideTouchListener(OnClickListener onClickListener) { + mHasOutsideTouch = true; + requestLayout(); + setOnClickListener(onClickListener); + setClickable(true); + setFocusable(true); + } + + /** + * Retrieve the MultiListLayout associated with the given view. + */ + public static MultiListLayout get(View v) { + if (v instanceof MultiListLayout) return (MultiListLayout) v; + if (v.getParent() instanceof View) { + return get((View) v.getParent()); + } + return null; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 5347a5cb16b6..f66a57b6133c 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -40,9 +40,10 @@ import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl; import com.android.systemui.statusbar.ScrimView; -import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; +import com.android.systemui.statusbar.notification.collection.NotificationData; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl; @@ -136,8 +137,8 @@ public class SystemUIFactory { } public NotificationIconAreaController createNotificationIconAreaController(Context context, - StatusBar statusBar) { - return new NotificationIconAreaController(context, statusBar); + StatusBar statusBar, StatusBarStateController statusBarStateController) { + return new NotificationIconAreaController(context, statusBar, statusBarStateController); } public KeyguardIndicationController createKeyguardIndicationController(Context context, diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java index 9f363f681b04..7e5b42653210 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java @@ -25,12 +25,20 @@ public class AppOpItem { private int mUid; private String mPackageName; private long mTimeStarted; + private String mState; public AppOpItem(int code, int uid, String packageName, long timeStarted) { this.mCode = code; this.mUid = uid; this.mPackageName = packageName; this.mTimeStarted = timeStarted; + mState = new StringBuilder() + .append("AppOpItem(") + .append("Op code=").append(code).append(", ") + .append("UID=").append(uid).append(", ") + .append("Package name=").append(packageName) + .append(")") + .toString(); } public int getCode() { @@ -48,4 +56,9 @@ public class AppOpItem { public long getTimeStarted() { return mTimeStarted; } + + @Override + public String toString() { + return mState; + } } diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java index e3bcb37474db..c013df385987 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java @@ -29,7 +29,10 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.Dumpable; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -47,7 +50,7 @@ import javax.inject.Singleton; @Singleton public class AppOpsControllerImpl implements AppOpsController, AppOpsManager.OnOpActiveChangedListener, - AppOpsManager.OnOpNotedListener { + AppOpsManager.OnOpNotedListener, Dumpable { private static final long NOTED_OP_TIME_DELAY_MS = 5000; private static final String TAG = "AppOpsControllerImpl"; @@ -271,6 +274,22 @@ public class AppOpsControllerImpl implements AppOpsController, } } + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("AppOpsController state:"); + pw.println(" Active Items:"); + for (int i = 0; i < mActiveItems.size(); i++) { + final AppOpItem item = mActiveItems.get(i); + pw.print(" "); pw.println(item.toString()); + } + pw.println(" Noted Items:"); + for (int i = 0; i < mNotedItems.size(); i++) { + final AppOpItem item = mNotedItems.get(i); + pw.print(" "); pw.println(item.toString()); + } + + } + protected final class H extends Handler { H(Looper looper) { super(looper); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java index 3167b9e0a458..016b8fe93093 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java @@ -33,9 +33,6 @@ import com.android.internal.os.SomeArgs; import com.android.systemui.SystemUI; import com.android.systemui.statusbar.CommandQueue; -import java.util.HashMap; -import java.util.Map; - /** * Receives messages sent from AuthenticationClient and shows the appropriate biometric UI (e.g. * BiometricDialogView). @@ -52,10 +49,8 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba private static final int MSG_BUTTON_NEGATIVE = 6; private static final int MSG_USER_CANCELED = 7; private static final int MSG_BUTTON_POSITIVE = 8; - private static final int MSG_BIOMETRIC_SHOW_TRY_AGAIN = 9; - private static final int MSG_TRY_AGAIN_PRESSED = 10; + private static final int MSG_TRY_AGAIN_PRESSED = 9; - private Map<Integer, BiometricDialogView> mDialogs; // BiometricAuthenticator type, view private SomeArgs mCurrentDialogArgs; private BiometricDialogView mCurrentDialog; private WindowManager mWindowManager; @@ -63,21 +58,22 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba private boolean mDialogShowing; private Callback mCallback = new Callback(); - private boolean mTryAgainShowing; // No good place to save state before config change :/ - private boolean mConfirmShowing; // No good place to save state before config change :/ - private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch(msg.what) { case MSG_SHOW_DIALOG: - handleShowDialog((SomeArgs) msg.obj, false /* skipAnimation */); + handleShowDialog((SomeArgs) msg.obj, false /* skipAnimation */, + null /* savedState */); break; case MSG_BIOMETRIC_AUTHENTICATED: - handleBiometricAuthenticated(); + handleBiometricAuthenticated((boolean) msg.obj); break; case MSG_BIOMETRIC_HELP: - handleBiometricHelp((String) msg.obj); + SomeArgs args = (SomeArgs) msg.obj; + handleBiometricHelp((String) args.arg1 /* message */, + (boolean) args.arg2 /* requireTryAgain */); + args.recycle(); break; case MSG_BIOMETRIC_ERROR: handleBiometricError((String) msg.obj); @@ -94,9 +90,6 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba case MSG_BUTTON_POSITIVE: handleButtonPositive(); break; - case MSG_BIOMETRIC_SHOW_TRY_AGAIN: - handleShowTryAgain(); - break; case MSG_TRY_AGAIN_PRESSED: handleTryAgainPressed(); break; @@ -137,30 +130,22 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba @Override public void start() { - createDialogs(); - - if (!mDialogs.isEmpty()) { + final PackageManager pm = mContext.getPackageManager(); + if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT) + || pm.hasSystemFeature(PackageManager.FEATURE_FACE) + || pm.hasSystemFeature(PackageManager.FEATURE_IRIS)) { getComponent(CommandQueue.class).addCallback(this); mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); } } - private void createDialogs() { - final PackageManager pm = mContext.getPackageManager(); - mDialogs = new HashMap<>(); - if (pm.hasSystemFeature(PackageManager.FEATURE_FACE)) { - mDialogs.put(BiometricAuthenticator.TYPE_FACE, new FaceDialogView(mContext, mCallback)); - } - if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { - mDialogs.put(BiometricAuthenticator.TYPE_FINGERPRINT, - new FingerprintDialogView(mContext, mCallback)); - } - } - @Override public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, int type, boolean requireConfirmation, int userId) { - if (DEBUG) Log.d(TAG, "showBiometricDialog, type: " + type); + if (DEBUG) { + Log.d(TAG, "showBiometricDialog, type: " + type + + ", requireConfirmation: " + requireConfirmation); + } // Remove these messages as they are part of the previous client mHandler.removeMessages(MSG_BIOMETRIC_ERROR); mHandler.removeMessages(MSG_BIOMETRIC_HELP); @@ -176,15 +161,18 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba } @Override - public void onBiometricAuthenticated() { - if (DEBUG) Log.d(TAG, "onBiometricAuthenticated"); - mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED).sendToTarget(); + public void onBiometricAuthenticated(boolean authenticated) { + if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: " + authenticated); + mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, authenticated).sendToTarget(); } @Override public void onBiometricHelp(String message) { if (DEBUG) Log.d(TAG, "onBiometricHelp: " + message); - mHandler.obtainMessage(MSG_BIOMETRIC_HELP, message).sendToTarget(); + SomeArgs args = SomeArgs.obtain(); + args.arg1 = message; + args.arg2 = false; // requireTryAgain + mHandler.obtainMessage(MSG_BIOMETRIC_HELP, args).sendToTarget(); } @Override @@ -199,16 +187,21 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba mHandler.obtainMessage(MSG_HIDE_DIALOG, false /* userCanceled */).sendToTarget(); } - @Override - public void showBiometricTryAgain() { - if (DEBUG) Log.d(TAG, "showBiometricTryAgain"); - mHandler.obtainMessage(MSG_BIOMETRIC_SHOW_TRY_AGAIN).sendToTarget(); - } - - private void handleShowDialog(SomeArgs args, boolean skipAnimation) { + private void handleShowDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) { mCurrentDialogArgs = args; final int type = args.argi1; - mCurrentDialog = mDialogs.get(type); + + if (type == BiometricAuthenticator.TYPE_FINGERPRINT) { + mCurrentDialog = new FingerprintDialogView(mContext, mCallback); + } else if (type == BiometricAuthenticator.TYPE_FACE) { + mCurrentDialog = new FaceDialogView(mContext, mCallback); + } else { + Log.e(TAG, "Unsupported type: " + type); + } + + if (savedState != null) { + mCurrentDialog.restoreState(savedState); + } if (DEBUG) Log.d(TAG, "handleShowDialog, isAnimatingAway: " + mCurrentDialog.isAnimatingAway() + " type: " + type); @@ -224,32 +217,36 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba mCurrentDialog.setRequireConfirmation((boolean) args.arg3); mCurrentDialog.setUserId(args.argi2); mCurrentDialog.setSkipIntro(skipAnimation); - mCurrentDialog.setPendingTryAgain(mTryAgainShowing); - mCurrentDialog.setPendingConfirm(mConfirmShowing); mWindowManager.addView(mCurrentDialog, mCurrentDialog.getLayoutParams()); mDialogShowing = true; } - private void handleBiometricAuthenticated() { - if (DEBUG) Log.d(TAG, "handleBiometricAuthenticated"); - - mCurrentDialog.announceForAccessibility( - mContext.getResources() - .getText(mCurrentDialog.getAuthenticatedAccessibilityResourceId())); - if (mCurrentDialog.requiresConfirmation()) { - mConfirmShowing = true; - mCurrentDialog.showConfirmationButton(true /* show */); + private void handleBiometricAuthenticated(boolean authenticated) { + if (DEBUG) Log.d(TAG, "handleBiometricAuthenticated: " + authenticated); + + if (authenticated) { + mCurrentDialog.announceForAccessibility( + mContext.getResources() + .getText(mCurrentDialog.getAuthenticatedAccessibilityResourceId())); + if (mCurrentDialog.requiresConfirmation()) { + mCurrentDialog.showConfirmationButton(true /* show */); + } else { + mCurrentDialog.updateState(BiometricDialogView.STATE_AUTHENTICATED); + mHandler.postDelayed(() -> { + handleHideDialog(false /* userCanceled */); + }, mCurrentDialog.getDelayAfterAuthenticatedDurationMs()); + } } else { - mCurrentDialog.updateState(BiometricDialogView.STATE_AUTHENTICATED); - mHandler.postDelayed(() -> { - handleHideDialog(false /* userCanceled */); - }, mCurrentDialog.getDelayAfterAuthenticatedDurationMs()); + handleBiometricHelp(mContext.getResources() + .getString(com.android.internal.R.string.biometric_not_recognized), + true /* requireTryAgain */); + mCurrentDialog.showTryAgainButton(true /* show */); } } - private void handleBiometricHelp(String message) { + private void handleBiometricHelp(String message, boolean requireTryAgain) { if (DEBUG) Log.d(TAG, "handleBiometricHelp: " + message); - mCurrentDialog.showHelpMessage(message); + mCurrentDialog.showHelpMessage(message, requireTryAgain); } private void handleBiometricError(String error) { @@ -258,7 +255,6 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba if (DEBUG) Log.d(TAG, "Dialog already dismissed"); return; } - mTryAgainShowing = false; mCurrentDialog.showErrorMessage(error); } @@ -279,8 +275,6 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba } mReceiver = null; mDialogShowing = false; - mConfirmShowing = false; - mTryAgainShowing = false; mCurrentDialog.startDismiss(); } @@ -294,7 +288,6 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba } catch (RemoteException e) { Log.e(TAG, "Remote exception when handling negative button", e); } - mTryAgainShowing = false; handleHideDialog(false /* userCanceled */); } @@ -308,25 +301,16 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba } catch (RemoteException e) { Log.e(TAG, "Remote exception when handling positive button", e); } - mConfirmShowing = false; handleHideDialog(false /* userCanceled */); } private void handleUserCanceled() { - mTryAgainShowing = false; - mConfirmShowing = false; handleHideDialog(true /* userCanceled */); } - private void handleShowTryAgain() { - mCurrentDialog.showTryAgainButton(true /* show */); - mTryAgainShowing = true; - } - private void handleTryAgainPressed() { try { mCurrentDialog.clearTemporaryMessage(); - mTryAgainShowing = false; mReceiver.onTryAgainPressed(); } catch (RemoteException e) { Log.e(TAG, "RemoteException when handling try again", e); @@ -337,13 +321,20 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); final boolean wasShowing = mDialogShowing; + + // Save the state of the current dialog (buttons showing, etc) + final Bundle savedState = new Bundle(); + if (mCurrentDialog != null) { + mCurrentDialog.onSaveState(savedState); + } + if (mDialogShowing) { mCurrentDialog.forceRemove(); mDialogShowing = false; } - createDialogs(); + if (wasShowing) { - handleShowDialog(mCurrentDialogArgs, true /* skipAnimation */); + handleShowDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState); } } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java index 9934bfd11f12..c92767763cb4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java @@ -56,12 +56,15 @@ public abstract class BiometricDialogView extends LinearLayout { private static final String TAG = "BiometricDialogView"; + private static final String KEY_TRY_AGAIN_VISIBILITY = "key_try_again_visibility"; + private static final String KEY_CONFIRM_VISIBILITY = "key_confirm_visibility"; + private static final int ANIMATION_DURATION_SHOW = 250; // ms private static final int ANIMATION_DURATION_AWAY = 350; // ms private static final int MSG_CLEAR_MESSAGE = 1; - protected static final int STATE_NONE = 0; + protected static final int STATE_IDLE = 0; protected static final int STATE_AUTHENTICATING = 1; protected static final int STATE_ERROR = 2; protected static final int STATE_PENDING_CONFIRMATION = 3; @@ -74,16 +77,24 @@ public abstract class BiometricDialogView extends LinearLayout { private final DevicePolicyManager mDevicePolicyManager; private final float mAnimationTranslationOffset; private final int mErrorColor; - private final int mTextColor; private final float mDialogWidth; private final DialogViewCallback mCallback; - private ViewGroup mLayout; - private final Button mPositiveButton; - private final Button mNegativeButton; - private final TextView mErrorText; + protected final ViewGroup mLayout; + protected final LinearLayout mDialog; + protected final TextView mTitleText; + protected final TextView mSubtitleText; + protected final TextView mDescriptionText; + protected final ImageView mBiometricIcon; + protected final TextView mErrorText; + protected final Button mPositiveButton; + protected final Button mNegativeButton; + protected final Button mTryAgainButton; + + protected final int mTextColor; + private Bundle mBundle; - private final LinearLayout mDialog; + private int mLastState; private boolean mAnimatingAway; private boolean mWasForceRemoved; @@ -91,15 +102,14 @@ public abstract class BiometricDialogView extends LinearLayout { protected boolean mRequireConfirmation; private int mUserId; // used to determine if we should show work background - private boolean mPendingShowTryAgain; - private boolean mPendingShowConfirm; - protected abstract int getHintStringResourceId(); protected abstract int getAuthenticatedAccessibilityResourceId(); protected abstract int getIconDescriptionResourceId(); protected abstract Drawable getAnimationForTransition(int oldState, int newState); protected abstract boolean shouldAnimateForTransition(int oldState, int newState); protected abstract int getDelayAfterAuthenticatedDurationMs(); + protected abstract boolean shouldGrayAreaDismissDialog(); + protected abstract void handleClearMessage(boolean requireTryAgain); private final Runnable mShowAnimationRunnable = new Runnable() { @Override @@ -124,7 +134,7 @@ public abstract class BiometricDialogView extends LinearLayout { public void handleMessage(Message msg) { switch(msg.what) { case MSG_CLEAR_MESSAGE: - handleClearMessage(); + handleClearMessage((boolean) msg.obj /* requireTryAgain */); break; default: Log.e(TAG, "Unhandled message: " + msg.what); @@ -158,10 +168,6 @@ public abstract class BiometricDialogView extends LinearLayout { mLayout = (ViewGroup) factory.inflate(R.layout.biometric_dialog, this, false); addView(mLayout); - mDialog = mLayout.findViewById(R.id.dialog); - - mErrorText = mLayout.findViewById(R.id.error); - mLayout.setOnKeyListener(new View.OnKeyListener() { boolean downPressed = false; @Override @@ -184,12 +190,19 @@ public abstract class BiometricDialogView extends LinearLayout { final View space = mLayout.findViewById(R.id.space); final View leftSpace = mLayout.findViewById(R.id.left_space); final View rightSpace = mLayout.findViewById(R.id.right_space); - final ImageView icon = mLayout.findViewById(R.id.biometric_icon); - final Button tryAgain = mLayout.findViewById(R.id.button_try_again); + + mDialog = mLayout.findViewById(R.id.dialog); + mTitleText = mLayout.findViewById(R.id.title); + mSubtitleText = mLayout.findViewById(R.id.subtitle); + mDescriptionText = mLayout.findViewById(R.id.description); + mBiometricIcon = mLayout.findViewById(R.id.biometric_icon); + mErrorText = mLayout.findViewById(R.id.error); mNegativeButton = mLayout.findViewById(R.id.button2); mPositiveButton = mLayout.findViewById(R.id.button1); + mTryAgainButton = mLayout.findViewById(R.id.button_try_again); - icon.setContentDescription(getResources().getString(getIconDescriptionResourceId())); + mBiometricIcon.setContentDescription( + getResources().getString(getIconDescriptionResourceId())); setDismissesDialog(space); setDismissesDialog(leftSpace); @@ -206,8 +219,9 @@ public abstract class BiometricDialogView extends LinearLayout { }, getDelayAfterAuthenticatedDurationMs()); }); - tryAgain.setOnClickListener((View v) -> { + mTryAgainButton.setOnClickListener((View v) -> { showTryAgainButton(false /* show */); + handleClearMessage(false /* requireTryAgain */); mCallback.onTryAgainPressed(); }); @@ -215,15 +229,17 @@ public abstract class BiometricDialogView extends LinearLayout { mLayout.requestFocus(); } + public void onSaveState(Bundle bundle) { + bundle.putInt(KEY_TRY_AGAIN_VISIBILITY, mTryAgainButton.getVisibility()); + bundle.putInt(KEY_CONFIRM_VISIBILITY, mPositiveButton.getVisibility()); + } + @Override public void onAttachedToWindow() { super.onAttachedToWindow(); mErrorText.setText(getHintStringResourceId()); - final TextView title = mLayout.findViewById(R.id.title); - final TextView subtitle = mLayout.findViewById(R.id.subtitle); - final TextView description = mLayout.findViewById(R.id.description); final ImageView backgroundView = mLayout.findViewById(R.id.background); if (mUserManager.isManagedProfile(mUserId)) { @@ -244,36 +260,34 @@ public abstract class BiometricDialogView extends LinearLayout { mDialog.getLayoutParams().width = (int) mDialogWidth; } - mLastState = STATE_NONE; + mLastState = STATE_IDLE; updateState(STATE_AUTHENTICATING); CharSequence titleText = mBundle.getCharSequence(BiometricPrompt.KEY_TITLE); - title.setText(titleText); - title.setSelected(true); + mTitleText.setVisibility(View.VISIBLE); + mTitleText.setText(titleText); + mTitleText.setSelected(true); final CharSequence subtitleText = mBundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE); if (TextUtils.isEmpty(subtitleText)) { - subtitle.setVisibility(View.GONE); + mSubtitleText.setVisibility(View.GONE); } else { - subtitle.setVisibility(View.VISIBLE); - subtitle.setText(subtitleText); + mSubtitleText.setVisibility(View.VISIBLE); + mSubtitleText.setText(subtitleText); } final CharSequence descriptionText = mBundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION); if (TextUtils.isEmpty(descriptionText)) { - description.setVisibility(View.GONE); + mDescriptionText.setVisibility(View.GONE); } else { - description.setVisibility(View.VISIBLE); - description.setText(descriptionText); + mDescriptionText.setVisibility(View.VISIBLE); + mDescriptionText.setText(descriptionText); } mNegativeButton.setText(mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT)); - showTryAgainButton(mPendingShowTryAgain); - showConfirmationButton(mPendingShowConfirm); - if (mWasForceRemoved || mSkipIntro) { // Show the dialog immediately mLayout.animate().cancel(); @@ -302,8 +316,7 @@ public abstract class BiometricDialogView extends LinearLayout { ? (AnimatedVectorDrawable) icon : null; - final ImageView imageView = getLayout().findViewById(R.id.biometric_icon); - imageView.setImageDrawable(icon); + mBiometricIcon.setImageDrawable(icon); if (animation != null && shouldAnimateForTransition(lastState, newState)) { animation.forceAnimationOnUI(); @@ -314,7 +327,7 @@ public abstract class BiometricDialogView extends LinearLayout { private void setDismissesDialog(View v) { v.setClickable(true); v.setOnTouchListener((View view, MotionEvent event) -> { - if (mLastState != STATE_AUTHENTICATED) { + if (mLastState != STATE_AUTHENTICATED && shouldGrayAreaDismissDialog()) { mCallback.onUserCanceled(); } return true; @@ -331,11 +344,9 @@ public abstract class BiometricDialogView extends LinearLayout { mWindowManager.removeView(BiometricDialogView.this); mAnimatingAway = false; // Set the icons / text back to normal state - handleClearMessage(); + handleClearMessage(false /* requireTryAgain */); showTryAgainButton(false /* show */); - mPendingShowTryAgain = false; - mPendingShowConfirm = false; - updateState(STATE_NONE); + updateState(STATE_IDLE); } }; @@ -412,35 +423,28 @@ public abstract class BiometricDialogView extends LinearLayout { return mLayout; } - // Clears the temporary message and shows the help message. - private void handleClearMessage() { - updateState(STATE_AUTHENTICATING); - mErrorText.setText(getHintStringResourceId()); - mErrorText.setTextColor(mTextColor); - } - // Shows an error/help message - private void showTemporaryMessage(String message) { + private void showTemporaryMessage(String message, boolean requireTryAgain) { mHandler.removeMessages(MSG_CLEAR_MESSAGE); updateState(STATE_ERROR); mErrorText.setText(message); mErrorText.setTextColor(mErrorColor); mErrorText.setContentDescription(message); - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_MESSAGE), + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_MESSAGE, requireTryAgain), BiometricPrompt.HIDE_DIALOG_DELAY); } public void clearTemporaryMessage() { mHandler.removeMessages(MSG_CLEAR_MESSAGE); - mHandler.obtainMessage(MSG_CLEAR_MESSAGE).sendToTarget(); + mHandler.obtainMessage(MSG_CLEAR_MESSAGE, false /* requireTryAgain */).sendToTarget(); } - public void showHelpMessage(String message) { - showTemporaryMessage(message); + public void showHelpMessage(String message, boolean requireTryAgain) { + showTemporaryMessage(message, requireTryAgain); } public void showErrorMessage(String error) { - showTemporaryMessage(error); + showTemporaryMessage(error, false /* requireTryAgain */); showTryAgainButton(false /* show */); mCallback.onErrorShown(); } @@ -459,22 +463,11 @@ public abstract class BiometricDialogView extends LinearLayout { } public void showTryAgainButton(boolean show) { - final Button tryAgain = mLayout.findViewById(R.id.button_try_again); - if (show) { - tryAgain.setVisibility(View.VISIBLE); - } else { - tryAgain.setVisibility(View.GONE); - } - } - - // Set the state before the window is attached, so we know if the dialog should be started - // with or without the button. This is because there's no good onPause signal - public void setPendingTryAgain(boolean show) { - mPendingShowTryAgain = show; } - public void setPendingConfirm(boolean show) { - mPendingShowConfirm = show; + public void restoreState(Bundle bundle) { + mTryAgainButton.setVisibility(bundle.getInt(KEY_TRY_AGAIN_VISIBILITY)); + mPositiveButton.setVisibility(bundle.getInt(KEY_CONFIRM_VISIBILITY)); } public WindowManager.LayoutParams getLayoutParams() { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java index de3f9471a6ba..9fba44b76863 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java @@ -16,8 +16,18 @@ package com.android.systemui.biometrics; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ValueAnimator; import android.content.Context; +import android.graphics.Outline; import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.view.View; +import android.view.ViewOutlineProvider; import com.android.systemui.R; @@ -28,13 +38,263 @@ import com.android.systemui.R; */ public class FaceDialogView extends BiometricDialogView { + private static final String TAG = "FaceDialogView"; + private static final String KEY_DIALOG_SIZE = "key_dialog_size"; + private static final int HIDE_DIALOG_DELAY = 500; // ms + private static final int IMPLICIT_Y_PADDING = 16; // dp + private static final int GROW_DURATION = 150; // ms + private static final int TEXT_ANIMATE_DISTANCE = 32; // dp + + private static final int SIZE_UNKNOWN = 0; + private static final int SIZE_SMALL = 1; + private static final int SIZE_GROWING = 2; + private static final int SIZE_BIG = 3; + + private int mSize; + private float mIconOriginalY; + private DialogOutlineProvider mOutlineProvider = new DialogOutlineProvider(); + + private final class DialogOutlineProvider extends ViewOutlineProvider { + + float mY; + + @Override + public void getOutline(View view, Outline outline) { + outline.setRoundRect( + 0 /* left */, + (int) mY, /* top */ + mDialog.getWidth() /* right */, + mDialog.getBottom(), /* bottom */ + getResources().getDimension(R.dimen.biometric_dialog_corner_size)); + } + + int calculateSmall() { + final float padding = dpToPixels(IMPLICIT_Y_PADDING); + return mDialog.getHeight() - mBiometricIcon.getHeight() - 2 * (int) padding; + } + + void setOutlineY(float y) { + mY = y; + } + } public FaceDialogView(Context context, DialogViewCallback callback) { super(context, callback); } + private void updateSize(int newSize) { + final float padding = dpToPixels(IMPLICIT_Y_PADDING); + final float iconSmallPositionY = mDialog.getHeight() - mBiometricIcon.getHeight() - padding; + + if (newSize == SIZE_SMALL) { + // These fields are required and/or always hold a spot on the UI, so should be set to + // INVISIBLE so they keep their position + mTitleText.setVisibility(View.INVISIBLE); + mErrorText.setVisibility(View.INVISIBLE); + mNegativeButton.setVisibility(View.INVISIBLE); + + // These fields are optional, so set them to gone or invisible depending on their + // usage. If they're empty, they're already set to GONE in BiometricDialogView. + if (!TextUtils.isEmpty(mSubtitleText.getText())) { + mSubtitleText.setVisibility(View.INVISIBLE); + } + if (!TextUtils.isEmpty(mDescriptionText.getText())) { + mDescriptionText.setVisibility(View.INVISIBLE); + } + + // Move the biometric icon to the small spot + mBiometricIcon.setY(iconSmallPositionY); + + // Clip the dialog to the small size + mDialog.setOutlineProvider(mOutlineProvider); + mOutlineProvider.setOutlineY(mOutlineProvider.calculateSmall()); + + mDialog.setClipToOutline(true); + mDialog.invalidateOutline(); + + mSize = newSize; + } else if (mSize == SIZE_SMALL && newSize == SIZE_BIG) { + mSize = SIZE_GROWING; + + // Animate the outline + final ValueAnimator outlineAnimator = + ValueAnimator.ofFloat(mOutlineProvider.calculateSmall(), 0); + outlineAnimator.addUpdateListener((animation) -> { + final float y = (float) animation.getAnimatedValue(); + mOutlineProvider.setOutlineY(y); + mDialog.invalidateOutline(); + }); + + // Animate the icon back to original big position + final ValueAnimator iconAnimator = + ValueAnimator.ofFloat(iconSmallPositionY, mIconOriginalY); + iconAnimator.addUpdateListener((animation) -> { + final float y = (float) animation.getAnimatedValue(); + mBiometricIcon.setY(y); + }); + + // Animate the error text so it slides up with the icon + final ValueAnimator textSlideAnimator = + ValueAnimator.ofFloat(dpToPixels(TEXT_ANIMATE_DISTANCE), 0); + textSlideAnimator.addUpdateListener((animation) -> { + final float y = (float) animation.getAnimatedValue(); + mErrorText.setTranslationY(y); + }); + + // Opacity animator for things that should fade in (title, subtitle, details, negative + // button) + final ValueAnimator opacityAnimator = ValueAnimator.ofFloat(0, 1); + opacityAnimator.addUpdateListener((animation) -> { + final float opacity = (float) animation.getAnimatedValue(); + + // These fields are required and/or always hold a spot on the UI + mTitleText.setAlpha(opacity); + mErrorText.setAlpha(opacity); + mNegativeButton.setAlpha(opacity); + mTryAgainButton.setAlpha(opacity); + + // These fields are optional, so only animate them if they're supposed to be showing + if (!TextUtils.isEmpty(mSubtitleText.getText())) { + mSubtitleText.setAlpha(opacity); + } + if (!TextUtils.isEmpty(mDescriptionText.getText())) { + mDescriptionText.setAlpha(opacity); + } + }); + + // Choreograph together + final AnimatorSet as = new AnimatorSet(); + as.setDuration(GROW_DURATION); + as.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + super.onAnimationStart(animation); + // Set the visibility of opacity-animating views back to VISIBLE + mTitleText.setVisibility(View.VISIBLE); + mErrorText.setVisibility(View.VISIBLE); + mNegativeButton.setVisibility(View.VISIBLE); + mTryAgainButton.setVisibility(View.VISIBLE); + + if (!TextUtils.isEmpty(mSubtitleText.getText())) { + mSubtitleText.setVisibility(View.VISIBLE); + } + if (!TextUtils.isEmpty(mDescriptionText.getText())) { + mDescriptionText.setVisibility(View.VISIBLE); + } + } + + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mSize = SIZE_BIG; + } + }); + as.play(outlineAnimator).with(iconAnimator).with(opacityAnimator) + .with(textSlideAnimator); + as.start(); + } else if (mSize == SIZE_BIG) { + mDialog.setClipToOutline(false); + mDialog.invalidateOutline(); + + mBiometricIcon.setY(mIconOriginalY); + + mSize = newSize; + } + } + + @Override + public void onSaveState(Bundle bundle) { + super.onSaveState(bundle); + bundle.putInt(KEY_DIALOG_SIZE, mSize); + } + + + @Override + protected void handleClearMessage(boolean requireTryAgain) { + // Clears the temporary message and shows the help message. If requireTryAgain is true, + // we will start the authenticating state again. + if (!requireTryAgain) { + updateState(STATE_AUTHENTICATING); + mErrorText.setText(getHintStringResourceId()); + mErrorText.setTextColor(mTextColor); + mErrorText.setVisibility(View.VISIBLE); + } else { + updateState(STATE_IDLE); + mErrorText.setVisibility(View.INVISIBLE); + } + } + + @Override + public void restoreState(Bundle bundle) { + super.restoreState(bundle); + // Keep in mind that this happens before onAttachedToWindow() + mSize = bundle.getInt(KEY_DIALOG_SIZE); + } + + /** + * Do small/big layout here instead of onAttachedToWindow, since: + * 1) We need the big layout to be measured, etc for small -> big animation + * 2) We need the dialog measurements to know where to move the biometric icon to + * + * BiometricDialogView already sets the views to their default big state, so here we only + * need to hide the ones that are unnecessary. + */ + @Override + public void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + if (mIconOriginalY == 0) { + mIconOriginalY = mBiometricIcon.getY(); + } + + // UNKNOWN means size hasn't been set yet. First time we create the dialog. + // onLayout can happen when visibility of views change (during animation, etc). + if (mSize != SIZE_UNKNOWN) { + // Probably not the cleanest way to do this, but since dialog is big by default, + // and small dialogs can persist across orientation changes, we need to set it to + // small size here again. + if (mSize == SIZE_SMALL) { + updateSize(SIZE_SMALL); + } + return; + } + + // If we don't require confirmation, show the small dialog first (until errors occur). + if (!requiresConfirmation()) { + updateSize(SIZE_SMALL); + } else { + updateSize(SIZE_BIG); + } + } + + @Override + public void showErrorMessage(String error) { + super.showErrorMessage(error); + + // All error messages will cause the dialog to go from small -> big. Error messages + // are messages such as lockout, auth failed, etc. + if (mSize == SIZE_SMALL) { + updateSize(SIZE_BIG); + } + } + + @Override + public void showTryAgainButton(boolean show) { + if (show && mSize == SIZE_SMALL) { + // Do not call super, we will nicely animate the alpha together with the rest + // of the elements in here. + updateSize(SIZE_BIG); + } else { + if (show) { + mTryAgainButton.setVisibility(View.VISIBLE); + } else { + mTryAgainButton.setVisibility(View.GONE); + } + } + } + @Override protected int getHintStringResourceId() { return R.string.face_dialog_looking_for_face; @@ -56,7 +316,9 @@ public class FaceDialogView extends BiometricDialogView { @Override protected boolean shouldAnimateForTransition(int oldState, int newState) { - if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) { + if (oldState == STATE_ERROR && newState == STATE_IDLE) { + return true; + } else if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) { return false; } else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) { return true; @@ -78,9 +340,19 @@ public class FaceDialogView extends BiometricDialogView { } @Override + protected boolean shouldGrayAreaDismissDialog() { + if (mSize == SIZE_SMALL) { + return false; + } + return true; + } + + @Override protected Drawable getAnimationForTransition(int oldState, int newState) { int iconRes; - if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) { + if (oldState == STATE_ERROR && newState == STATE_IDLE) { + iconRes = R.drawable.face_dialog_error_to_face; + } else if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) { iconRes = R.drawable.face_dialog_face_to_error; } else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) { iconRes = R.drawable.face_dialog_face_to_error; @@ -97,4 +369,14 @@ public class FaceDialogView extends BiometricDialogView { } return mContext.getDrawable(iconRes); } + + private float dpToPixels(float dp) { + return dp * ((float) mContext.getResources().getDisplayMetrics().densityDpi + / DisplayMetrics.DENSITY_DEFAULT); + } + + private float pixelsToDp(float pixels) { + return pixels / ((float) mContext.getResources().getDisplayMetrics().densityDpi + / DisplayMetrics.DENSITY_DEFAULT); + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java index 1a6cee281c84..c9b30ba3ce8d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java @@ -32,6 +32,14 @@ public class FingerprintDialogView extends BiometricDialogView { DialogViewCallback callback) { super(context, callback); } + + @Override + protected void handleClearMessage(boolean requireTryAgain) { + updateState(STATE_AUTHENTICATING); + mErrorText.setText(getHintStringResourceId()); + mErrorText.setTextColor(mTextColor); + } + @Override protected int getHintStringResourceId() { return R.string.fingerprint_dialog_touch_sensor; @@ -49,7 +57,7 @@ public class FingerprintDialogView extends BiometricDialogView { @Override protected boolean shouldAnimateForTransition(int oldState, int newState) { - if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) { + if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) { return false; } else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) { return true; @@ -68,9 +76,15 @@ public class FingerprintDialogView extends BiometricDialogView { } @Override + protected boolean shouldGrayAreaDismissDialog() { + // Fingerprint dialog always dismisses when region outside the dialog is tapped + return true; + } + + @Override protected Drawable getAnimationForTransition(int oldState, int newState) { int iconRes; - if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) { + if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) { iconRes = R.drawable.fingerprint_dialog_fp_to_error; } else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) { iconRes = R.drawable.fingerprint_dialog_fp_to_error; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java new file mode 100644 index 000000000000..845b08483064 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2018 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.bubbles; + +import static android.graphics.Paint.ANTI_ALIAS_FLAG; +import static android.graphics.Paint.FILTER_BITMAP_FLAG; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Point; +import android.graphics.Rect; +import android.util.Log; + +// XXX: Mostly opied from launcher code / can we share? +/** + * Contains parameters necessary to draw a badge for an icon (e.g. the size of the badge). + */ +public class BadgeRenderer { + + private static final String TAG = "BadgeRenderer"; + + // The badge sizes are defined as percentages of the app icon size. + private static final float SIZE_PERCENTAGE = 0.38f; + + // Extra scale down of the dot + private static final float DOT_SCALE = 0.6f; + + private final float mDotCenterOffset; + private final float mCircleRadius; + private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG); + + public BadgeRenderer(int iconSizePx) { + mDotCenterOffset = SIZE_PERCENTAGE * iconSizePx; + int size = (int) (DOT_SCALE * mDotCenterOffset); + mCircleRadius = size / 2f; + } + + /** + * Draw a circle in the top right corner of the given bounds. + * + * @param color The color (based on the icon) to use for the badge. + * @param iconBounds The bounds of the icon being badged. + * @param badgeScale The progress of the animation, from 0 to 1. + * @param spaceForOffset How much space to offset the badge up and to the left or right. + * @param onLeft Whether the badge should be draw on left or right side. + */ + public void draw(Canvas canvas, int color, Rect iconBounds, float badgeScale, + Point spaceForOffset, boolean onLeft) { + if (iconBounds == null) { + Log.e(TAG, "Invalid null argument(s) passed in call to draw."); + return; + } + canvas.save(); + // We draw the badge relative to its center. + int x = onLeft ? iconBounds.left : iconBounds.right; + float offset = onLeft ? (mDotCenterOffset / 2) : -(mDotCenterOffset / 2); + float badgeCenterX = x + offset; + float badgeCenterY = iconBounds.top + mDotCenterOffset / 2; + + canvas.translate(badgeCenterX + spaceForOffset.x, badgeCenterY - spaceForOffset.y); + + canvas.scale(badgeScale, badgeScale); + mCirclePaint.setColor(color); + canvas.drawCircle(0, 0, mCircleRadius, mCirclePaint); + canvas.restore(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java new file mode 100644 index 000000000000..92d3cc1ae34f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2018 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.bubbles; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Path; +import android.graphics.Point; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.widget.ImageView; + +import com.android.systemui.R; + +/** + * View that circle crops its contents and supports displaying a coloured dot on a top corner. + */ +public class BadgedImageView extends ImageView { + + private BadgeRenderer mDotRenderer; + private int mIconSize; + private Rect mTempBounds = new Rect(); + private Point mTempPoint = new Point(); + private Path mClipPath = new Path(); + + private float mDotScale = 0f; + private int mUpdateDotColor; + private boolean mShowUpdateDot; + private boolean mOnLeft; + + public BadgedImageView(Context context) { + this(context, null); + } + + public BadgedImageView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public BadgedImageView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public BadgedImageView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + setScaleType(ScaleType.CENTER_CROP); + mIconSize = getResources().getDimensionPixelSize(R.dimen.bubble_size); + mDotRenderer = new BadgeRenderer(mIconSize); + } + + // TODO: Clipping oval path isn't great: rerender image into a separate, rounded bitmap and + // then draw would be better + @Override + public void onDraw(Canvas canvas) { + canvas.save(); + // Circle crop + mClipPath.addOval(getPaddingStart(), getPaddingTop(), + getWidth() - getPaddingEnd(), getHeight() - getPaddingBottom(), Path.Direction.CW); + canvas.clipPath(mClipPath); + super.onDraw(canvas); + + // After we've circle cropped what we're showing, restore so we don't clip the badge + canvas.restore(); + + // Draw the badge + if (mShowUpdateDot) { + getDrawingRect(mTempBounds); + mTempPoint.set((getWidth() - mIconSize) / 2, getPaddingTop()); + mDotRenderer.draw(canvas, mUpdateDotColor, mTempBounds, mDotScale, mTempPoint, + mOnLeft); + } + } + + /** + * Set whether the dot should appear on left or right side of the view. + */ + public void setDotPosition(boolean onLeft) { + mOnLeft = onLeft; + invalidate(); + } + + /** + * Set whether the dot should show or not. + */ + public void setShowDot(boolean showBadge) { + mShowUpdateDot = showBadge; + invalidate(); + } + + /** + * @return whether the dot is being displayed. + */ + public boolean isShowingDot() { + return mShowUpdateDot; + } + + /** + * The colour to use for the dot. + */ + public void setDotColor(int color) { + mUpdateDotColor = color; + invalidate(); + } + + /** + * How big the dot should be, fraction from 0 to 1. + */ + public void setDotScale(float fraction) { + mDotScale = fraction; + invalidate(); + } + + public float getDotScale() { + return mDotScale; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 881aa18285ff..957d772be730 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -21,25 +21,42 @@ import static android.view.View.VISIBLE; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.systemui.bubbles.BubbleMovementHelper.EDGE_OVERLAP; +import static com.android.systemui.statusbar.StatusBarState.SHADE; +import static com.android.systemui.statusbar.notification.NotificationAlertingManager.alertAgain; +import android.annotation.Nullable; +import android.app.INotificationManager; import android.app.Notification; +import android.app.PendingIntent; import android.content.Context; +import android.content.pm.ActivityInfo; import android.graphics.Point; import android.graphics.Rect; +import android.os.RemoteException; +import android.os.ServiceManager; import android.provider.Settings; import android.service.notification.StatusBarNotification; +import android.util.Log; +import android.view.LayoutInflater; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.FrameLayout; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.statusbar.NotificationVisibility; +import com.android.systemui.Dependency; import com.android.systemui.R; -import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.notification.NotificationEntryListener; +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.NotificationInflater; import com.android.systemui.statusbar.phone.StatusBarWindowController; -import java.util.ArrayList; import java.util.HashMap; import java.util.Map; +import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; @@ -57,45 +74,36 @@ public class BubbleController { private static final String TAG = "BubbleController"; // Enables some subset of notifs to automatically become bubbles - public static final boolean DEBUG_ENABLE_AUTO_BUBBLE = false; - // When a bubble is dismissed, recreate it as a notification - public static final boolean DEBUG_DEMOTE_TO_NOTIF = false; + private static final boolean DEBUG_ENABLE_AUTO_BUBBLE = false; // Secure settings private static final String ENABLE_AUTO_BUBBLE_MESSAGES = "experiment_autobubble_messaging"; private static final String ENABLE_AUTO_BUBBLE_ONGOING = "experiment_autobubble_ongoing"; private static final String ENABLE_AUTO_BUBBLE_ALL = "experiment_autobubble_all"; + private static final String ENABLE_BUBBLE_ACTIVITY_VIEW = "experiment_bubble_activity_view"; + private static final String ENABLE_BUBBLE_CONTENT_INTENT = "experiment_bubble_content_intent"; - private Context mContext; - private BubbleDismissListener mDismissListener; + private final Context mContext; + private final NotificationEntryManager mNotificationEntryManager; private BubbleStateChangeListener mStateChangeListener; private BubbleExpandListener mExpandListener; + private LayoutInflater mInflater; - private Map<String, BubbleView> mBubbles = new HashMap<>(); + private final Map<String, BubbleView> mBubbles = new HashMap<>(); private BubbleStackView mStackView; - private Point mDisplaySize; + private final Point mDisplaySize; // Bubbles get added to the status bar view - @VisibleForTesting - protected StatusBarWindowController mStatusBarWindowController; + private final StatusBarWindowController mStatusBarWindowController; + private StatusBarStateListener mStatusBarStateListener; - // Used for determining view rect for touch interaction - private Rect mTempRect = new Rect(); + private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider = + Dependency.get(NotificationInterruptionStateProvider.class); - /** - * Listener to find out about bubble / bubble stack dismissal events. - */ - public interface BubbleDismissListener { - /** - * Called when the entire stack of bubbles is dismissed by the user. - */ - void onStackDismissed(); + private INotificationManager mNotificationManagerService; - /** - * Called when a specific bubble is dismissed by the user. - */ - void onBubbleDismissed(String key); - } + // Used for determining view rect for touch interaction + private Rect mTempRect = new Rect(); /** * Listener to be notified when some states of the bubbles change. @@ -113,11 +121,30 @@ public class BubbleController { public interface BubbleExpandListener { /** * Called when the expansion state of the bubble stack changes. - * * @param isExpanding whether it's expanding or collapsing - * @param amount fraction of how expanded or collapsed it is, 1 being fully, 0 at the start + * @param key the notification key associated with bubble being expanded */ - void onBubbleExpandChanged(boolean isExpanding, float amount); + void onBubbleExpandChanged(boolean isExpanding, String key); + } + + /** + * Listens for the current state of the status bar and updates the visibility state + * of bubbles as needed. + */ + private class StatusBarStateListener implements StatusBarStateController.StateListener { + private int mState; + /** + * Returns the current status bar state. + */ + public int getCurrentState() { + return mState; + } + + @Override + public void onStateChanged(int newState) { + mState = newState; + updateVisibility(); + } } @Inject @@ -126,14 +153,21 @@ public class BubbleController { WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); mDisplaySize = new Point(); wm.getDefaultDisplay().getSize(mDisplaySize); - mStatusBarWindowController = statusBarWindowController; - } + mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - /** - * Set a listener to be notified of bubble dismissal events. - */ - public void setDismissListener(BubbleDismissListener listener) { - mDismissListener = listener; + mNotificationEntryManager = Dependency.get(NotificationEntryManager.class); + mNotificationEntryManager.addNotificationEntryListener(mEntryListener); + + try { + mNotificationManagerService = INotificationManager.Stub.asInterface( + ServiceManager.getServiceOrThrow(Context.NOTIFICATION_SERVICE)); + } catch (ServiceManager.ServiceNotFoundException e) { + e.printStackTrace(); + } + + mStatusBarWindowController = statusBarWindowController; + mStatusBarStateListener = new StatusBarStateListener(); + Dependency.get(StatusBarStateController.class).addCallback(mStatusBarStateListener); } /** @@ -158,7 +192,12 @@ public class BubbleController { * screen (e.g. if on AOD). */ public boolean hasBubbles() { - return mBubbles.size() > 0; + for (BubbleView bv : mBubbles.values()) { + if (!bv.getEntry().isBubbleDismissed()) { + return true; + } + } + return false; } /** @@ -173,43 +212,43 @@ public class BubbleController { */ public void collapseStack() { if (mStackView != null) { - mStackView.animateExpansion(false); + mStackView.collapseStack(); } } /** * Tell the stack of bubbles to be dismissed, this will remove all of the bubbles in the stack. */ - public void dismissStack() { + void dismissStack() { if (mStackView == null) { return; } - Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize); + Set<String> keys = mBubbles.keySet(); + for (String key: keys) { + mBubbles.get(key).getEntry().setBubbleDismissed(true); + } + mStackView.stackDismissed(); + // Reset the position of the stack (TODO - or should we save / respect last user position?) + Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize); mStackView.setPosition(startPoint.x, startPoint.y); - for (String key: mBubbles.keySet()) { - removeBubble(key); - } - if (mDismissListener != null) { - mDismissListener.onStackDismissed(); - } - updateBubblesShowing(); + + updateVisibility(); + mNotificationEntryManager.updateNotifications(); } /** - * Adds a bubble associated with the provided notification entry or updates it if it exists. + * Adds or updates a bubble associated with the provided notification entry. + * + * @param notif the notification associated with this bubble. + * @param updatePosition whether this update should promote the bubble to the top of the stack. */ - public void addBubble(NotificationData.Entry notif) { + public void updateBubble(NotificationEntry notif, boolean updatePosition) { if (mBubbles.containsKey(notif.key)) { // It's an update BubbleView bubble = mBubbles.get(notif.key); - mStackView.updateBubble(bubble, notif); + mStackView.updateBubble(bubble, notif, updatePosition); } else { - // It's new - BubbleView bubble = new BubbleView(mContext); - bubble.setNotif(notif); - mBubbles.put(bubble.getKey(), bubble); - boolean setPosition = mStackView != null && mStackView.getVisibility() != VISIBLE; if (mStackView == null) { setPosition = true; @@ -224,75 +263,134 @@ public class BubbleController { mStackView.setExpandListener(mExpandListener); } } + // It's new + BubbleView bubble = (BubbleView) mInflater.inflate( + R.layout.bubble_view, mStackView, false /* attachToRoot */); + bubble.setNotif(notif); + if (shouldUseActivityView(mContext)) { + bubble.setAppOverlayIntent(getAppOverlayIntent(notif)); + } + mBubbles.put(bubble.getKey(), bubble); mStackView.addBubble(bubble); if (setPosition) { // Need to add the bubble to the stack before we can know the width Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize); mStackView.setPosition(startPoint.x, startPoint.y); - mStackView.setVisibility(VISIBLE); } - updateBubblesShowing(); } + updateVisibility(); + } + + @Nullable + private PendingIntent getAppOverlayIntent(NotificationEntry notif) { + Notification notification = notif.notification.getNotification(); + if (canLaunchInActivityView(notification.getBubbleMetadata() != null + ? notification.getBubbleMetadata().getIntent() : null)) { + return notification.getBubbleMetadata().getIntent(); + } else if (shouldUseContentIntent(mContext) + && canLaunchInActivityView(notification.contentIntent)) { + Log.d(TAG, "[addBubble " + notif.key + + "]: No appOverlayIntent, using contentIntent."); + return notification.contentIntent; + } + Log.d(TAG, "[addBubble " + notif.key + "]: No supported intent for ActivityView."); + return null; } /** * Removes the bubble associated with the {@param uri}. */ - public void removeBubble(String key) { - BubbleView bv = mBubbles.get(key); + void removeBubble(String key) { + BubbleView bv = mBubbles.remove(key); if (mStackView != null && bv != null) { mStackView.removeBubble(bv); - bv.getEntry().setBubbleDismissed(true); + bv.destroyActivityView(mStackView); } - if (mDismissListener != null) { - mDismissListener.onBubbleDismissed(key); + + NotificationEntry entry = bv != null ? bv.getEntry() : null; + if (entry != null) { + entry.setBubbleDismissed(true); + mNotificationEntryManager.updateNotifications(); } - updateBubblesShowing(); + updateVisibility(); } - private void updateBubblesShowing() { - boolean hasBubblesShowing = false; - for (BubbleView bv : mBubbles.values()) { - if (!bv.getEntry().isBubbleDismissed()) { - hasBubblesShowing = true; - break; + @SuppressWarnings("FieldCanBeLocal") + private final NotificationEntryListener mEntryListener = new NotificationEntryListener() { + @Override + public void onPendingEntryAdded(NotificationEntry entry) { + if (shouldAutoBubble(mContext, entry) || shouldBubble(entry)) { + // TODO: handle group summaries + // It's a new notif, it shows in the shade and as a bubble + entry.setIsBubble(true); + entry.setShowInShadeWhenBubble(true); } } - boolean hadBubbles = mStatusBarWindowController.getBubblesShowing(); - mStatusBarWindowController.setBubblesShowing(hasBubblesShowing); - if (mStackView != null && !hasBubblesShowing) { - mStackView.setVisibility(INVISIBLE); + + @Override + public void onEntryInflated(NotificationEntry entry, + @NotificationInflater.InflationFlag int inflatedFlags) { + if (entry.isBubble() && mNotificationInterruptionStateProvider.shouldBubbleUp(entry)) { + updateBubble(entry, true /* updatePosition */); + } } - if (mStateChangeListener != null && hadBubbles != hasBubblesShowing) { - mStateChangeListener.onHasBubblesChanged(hasBubblesShowing); + + @Override + public void onPreEntryUpdated(NotificationEntry entry) { + if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry) + && alertAgain(entry, entry.notification.getNotification())) { + entry.setShowInShadeWhenBubble(true); + entry.setBubbleDismissed(false); // updates come back as bubbles even if dismissed + if (mBubbles.containsKey(entry.key)) { + mBubbles.get(entry.key).updateDotVisibility(); + } + updateBubble(entry, true /* updatePosition */); + } } - } + + @Override + public void onEntryRemoved(NotificationEntry entry, + @Nullable NotificationVisibility visibility, + boolean removedByUser) { + entry.setShowInShadeWhenBubble(false); + if (mBubbles.containsKey(entry.key)) { + mBubbles.get(entry.key).updateDotVisibility(); + } + if (!removedByUser) { + // This was a cancel so we should remove the bubble + removeBubble(entry.key); + } + } + }; /** - * Sets the visibility of the bubbles, doesn't un-bubble them, just changes visibility. + * Lets any listeners know if bubble state has changed. */ - public void updateVisibility(boolean visible) { + private void updateBubblesShowing() { if (mStackView == null) { return; } - ArrayList<BubbleView> viewsToRemove = new ArrayList<>(); - for (BubbleView bv : mBubbles.values()) { - NotificationData.Entry entry = bv.getEntry(); - if (entry != null) { - if (entry.isRowRemoved() || entry.isBubbleDismissed() || entry.isRowDismissed()) { - viewsToRemove.add(bv); - } - } - } - for (BubbleView view : viewsToRemove) { - mBubbles.remove(view.getKey()); - mStackView.removeBubble(view); + + boolean hadBubbles = mStatusBarWindowController.getBubblesShowing(); + boolean hasBubblesShowing = hasBubbles() && mStackView.getVisibility() == VISIBLE; + mStatusBarWindowController.setBubblesShowing(hasBubblesShowing); + if (mStateChangeListener != null && hadBubbles != hasBubblesShowing) { + mStateChangeListener.onHasBubblesChanged(hasBubblesShowing); } - if (mStackView != null) { - mStackView.setVisibility(visible ? VISIBLE : INVISIBLE); - if (!visible) { - collapseStack(); - } + } + + /** + * Updates the visibility of the bubbles based on current state. + * Does not un-bubble, just hides or un-hides. Will notify any + * {@link BubbleStateChangeListener}s if visibility changes. + */ + public void updateVisibility() { + if (mStatusBarStateListener.getCurrentState() == SHADE && hasBubbles()) { + // Bubbles only appear in unlocked shade + mStackView.setVisibility(hasBubbles() ? VISIBLE : INVISIBLE); + } else if (mStackView != null) { + mStackView.setVisibility(INVISIBLE); + collapseStack(); } updateBubblesShowing(); } @@ -308,8 +406,19 @@ public class BubbleController { return mTempRect; } + private boolean canLaunchInActivityView(PendingIntent intent) { + if (intent == null) { + return false; + } + ActivityInfo info = + intent.getIntent().resolveActivityInfo(mContext.getPackageManager(), 0); + return info != null + && ActivityInfo.isResizeableMode(info.resizeMode) + && (info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) != 0; + } + @VisibleForTesting - public BubbleStackView getStackView() { + BubbleStackView getStackView() { return mStackView; } @@ -317,7 +426,7 @@ public class BubbleController { /** * Gets an appropriate starting point to position the bubble stack. */ - public static Point getStartPoint(int size, Point displaySize) { + private static Point getStartPoint(int size, Point displaySize) { final int x = displaySize.x - size + EDGE_OVERLAP; final int y = displaySize.y / 4; return new Point(x, y); @@ -326,24 +435,48 @@ public class BubbleController { /** * Gets an appropriate position for the bubble when the stack is expanded. */ - public static Point getExpandPoint(BubbleStackView view, int size, Point displaySize) { + static Point getExpandPoint(BubbleStackView view, int size, Point displaySize) { // Same place for now.. return new Point(EDGE_OVERLAP, size); } /** - * Whether the notification should bubble or not. + * Whether the notification has been developer configured to bubble and is allowed by the user. */ - public static boolean shouldAutoBubble(Context context, NotificationData.Entry entry) { + private boolean shouldBubble(NotificationEntry entry) { + StatusBarNotification n = entry.notification; + boolean canAppOverlay = false; + try { + canAppOverlay = mNotificationManagerService.areBubblesAllowedForPackage( + n.getPackageName(), n.getUid()); + } catch (RemoteException e) { + Log.w(TAG, "Error calling NoMan to determine if app can overlay", e); + } + + boolean canChannelOverlay = mNotificationEntryManager.getNotificationData().getChannel( + entry.key).canBubble(); + boolean hasOverlayIntent = n.getNotification().getBubbleMetadata() != null + && n.getNotification().getBubbleMetadata().getIntent() != null; + return hasOverlayIntent && canChannelOverlay && canAppOverlay; + } + + /** + * Whether the notification should bubble or not. Gated by debug flag. + * <p> + * If a notification has been set to bubble via proper bubble APIs or if it is an important + * message-like notification. + * </p> + */ + private boolean shouldAutoBubble(Context context, NotificationEntry entry) { if (entry.isBubbleDismissed()) { return false; } + StatusBarNotification n = entry.notification; boolean autoBubbleMessages = shouldAutoBubbleMessages(context) || DEBUG_ENABLE_AUTO_BUBBLE; boolean autoBubbleOngoing = shouldAutoBubbleOngoing(context) || DEBUG_ENABLE_AUTO_BUBBLE; boolean autoBubbleAll = shouldAutoBubbleAll(context) || DEBUG_ENABLE_AUTO_BUBBLE; - StatusBarNotification n = entry.notification; boolean hasRemoteInput = false; if (n.getNotification().actions != null) { for (Notification.Action action : n.getNotification().actions) { @@ -380,4 +513,14 @@ public class BubbleController { return Settings.Secure.getInt(context.getContentResolver(), ENABLE_AUTO_BUBBLE_ALL, 0) != 0; } + + private static boolean shouldUseActivityView(Context context) { + return Settings.Secure.getInt(context.getContentResolver(), + ENABLE_BUBBLE_ACTIVITY_VIEW, 0) != 0; + } + + private static boolean shouldUseContentIntent(Context context) { + return Settings.Secure.getInt(context.getContentResolver(), + ENABLE_BUBBLE_CONTENT_INTENT, 0) != 0; + } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java index e28d96b2def9..71ae1f8620f6 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java @@ -21,9 +21,11 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Color; import android.graphics.drawable.ShapeDrawable; +import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.widget.LinearLayout; +import android.widget.TextView; import com.android.systemui.R; import com.android.systemui.recents.TriangleShape; @@ -35,6 +37,8 @@ public class BubbleExpandedViewContainer extends LinearLayout { // The triangle pointing to the expanded view private View mPointerView; + // The view displayed between the pointer and the expanded view + private TextView mHeaderView; // The view that is being displayed for the expanded state private View mExpandedView; @@ -68,6 +72,7 @@ public class BubbleExpandedViewContainer extends LinearLayout { TriangleShape.create(width, height, true /* pointUp */)); triangleDrawable.setTint(Color.WHITE); // TODO: dark mode mPointerView.setBackground(triangleDrawable); + mHeaderView = findViewById(R.id.bubble_content_header); } /** @@ -80,9 +85,20 @@ public class BubbleExpandedViewContainer extends LinearLayout { } /** + * Set the text displayed within the header. + */ + public void setHeaderText(CharSequence text) { + mHeaderView.setText(text); + mHeaderView.setVisibility(TextUtils.isEmpty(text) ? GONE : VISIBLE); + } + + /** * Set the view to display for the expanded state. Passing null will clear the view. */ public void setExpandedView(View view) { + if (mExpandedView == view) { + return; + } if (mExpandedView != null) { removeView(mExpandedView); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index dfd18b23a5e3..9a11b965b319 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -21,10 +21,13 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; +import android.app.ActivityView; +import android.app.PendingIntent; import android.content.Context; import android.content.res.Resources; import android.graphics.Point; import android.graphics.RectF; +import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -40,7 +43,7 @@ import androidx.annotation.Nullable; import com.android.internal.widget.ViewClippingUtil; import com.android.systemui.R; -import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import com.android.systemui.statusbar.notification.stack.ViewState; @@ -50,6 +53,7 @@ import com.android.systemui.statusbar.notification.stack.ViewState; */ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.FloatingView { + private static final String TAG = "BubbleStackView"; private Point mDisplaySize; private FrameLayout mBubbleContainer; @@ -59,9 +63,10 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F private int mBubblePadding; private boolean mIsExpanded; + private int mExpandedBubbleHeight; + private BubbleTouchHandler mTouchHandler; private BubbleView mExpandedBubble; private Point mCollapsedPosition; - private BubbleTouchHandler mTouchHandler; private BubbleController.BubbleExpandListener mExpandListener; private boolean mViewUpdatedRequested = false; @@ -106,6 +111,7 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size); mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding); + mExpandedBubbleHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height); mDisplaySize = new Point(); WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); wm.getDefaultDisplay().getSize(mDisplaySize); @@ -205,13 +211,24 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F */ public void setExpandedBubble(BubbleView bubbleToExpand) { mExpandedBubble = bubbleToExpand; + boolean prevExpanded = mIsExpanded; mIsExpanded = true; - updateExpandedBubble(); - requestUpdate(); + if (!prevExpanded) { + // If we weren't previously expanded we should animate open. + animateExpansion(true /* expand */); + } else { + // If we were expanded just update the views + updateExpandedBubble(); + requestUpdate(); + } + mExpandedBubble.getEntry().setShowInShadeWhenBubble(false); + notifyExpansionChanged(mExpandedBubble, true /* expanded */); } /** - * Adds a bubble to the stack. + * Adds a bubble to the top of the stack. + * + * @param bubbleView the view to add to the stack. */ public void addBubble(BubbleView bubbleView) { mBubbleContainer.addView(bubbleView, 0, @@ -228,17 +245,26 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F mBubbleContainer.removeView(bubbleView); boolean wasExpanded = mIsExpanded; int bubbleCount = mBubbleContainer.getChildCount(); - if (bubbleView.equals(mExpandedBubble) && bubbleCount > 0) { + if (mIsExpanded && bubbleView.equals(mExpandedBubble) && bubbleCount > 0) { // If we have other bubbles and are expanded go to the next one or previous // if the bubble removed was last int nextIndex = bubbleCount > removedIndex ? removedIndex : bubbleCount - 1; - mExpandedBubble = (BubbleView) mBubbleContainer.getChildAt(nextIndex); + BubbleView expandedBubble = (BubbleView) mBubbleContainer.getChildAt(nextIndex); + setExpandedBubble(expandedBubble); } mIsExpanded = wasExpanded && mBubbleContainer.getChildCount() > 0; - requestUpdate(); - if (wasExpanded && !mIsExpanded && mExpandListener != null) { - mExpandListener.onBubbleExpandChanged(mIsExpanded, 1 /* amount */); + if (wasExpanded != mIsExpanded) { + notifyExpansionChanged(mExpandedBubble, mIsExpanded); } + requestUpdate(); + } + + /** + * Dismiss the stack of bubbles. + */ + public void stackDismissed() { + collapseStack(); + mBubbleContainer.removeAllViews(); } /** @@ -246,11 +272,19 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F * * @param bubbleView the view to update in the stack. * @param entry the entry to update it with. + * @param updatePosition whether this bubble should be moved to top of the stack. */ - public void updateBubble(BubbleView bubbleView, NotificationData.Entry entry) { - // TODO - move to top of bubble stack, make it show its update if it makes sense + public void updateBubble(BubbleView bubbleView, NotificationEntry entry, + boolean updatePosition) { bubbleView.update(entry); - if (bubbleView.equals(mExpandedBubble)) { + if (updatePosition && !mIsExpanded) { + // If alerting it gets promoted to top of the stack + mBubbleContainer.removeView(bubbleView); + mBubbleContainer.addView(bubbleView, 0); + requestUpdate(); + } + if (mIsExpanded && bubbleView.equals(mExpandedBubble)) { + entry.setShowInShadeWhenBubble(false); requestUpdate(); } } @@ -281,17 +315,36 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F } /** + * Collapses the stack of bubbles. + */ + public void collapseStack() { + if (mIsExpanded) { + // TODO: Save opened bubble & move it to top of stack + animateExpansion(false /* shouldExpand */); + notifyExpansionChanged(mExpandedBubble, mIsExpanded); + } + } + + /** + * Expands the stack fo bubbles. + */ + public void expandStack() { + if (!mIsExpanded) { + mExpandedBubble = getTopBubble(); + mExpandedBubble.getEntry().setShowInShadeWhenBubble(false); + animateExpansion(true /* shouldExpand */); + notifyExpansionChanged(mExpandedBubble, true /* expanded */); + } + } + + /** * Tell the stack to animate to collapsed or expanded state. */ - public void animateExpansion(boolean shouldExpand) { + private void animateExpansion(boolean shouldExpand) { if (mIsExpanded != shouldExpand) { mIsExpanded = shouldExpand; - mExpandedBubble = shouldExpand ? getTopBubble() : null; updateExpandedBubble(); - if (mExpandListener != null) { - mExpandListener.onBubbleExpandChanged(mIsExpanded, 1 /* amount */); - } if (shouldExpand) { // Save current position so that we might return there savePosition(); @@ -341,6 +394,13 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F mCollapsedPosition = getPosition(); } + private void notifyExpansionChanged(BubbleView bubbleView, boolean expanded) { + if (mExpandListener != null) { + NotificationEntry entry = bubbleView != null ? bubbleView.getEntry() : null; + mExpandListener.onBubbleExpandChanged(expanded, entry != null ? entry.key : null); + } + } + private BubbleView getTopBubble() { return getBubbleAt(0); } @@ -389,32 +449,78 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F } private void updateExpandedBubble() { - if (mExpandedBubble != null) { + if (mExpandedBubble == null) { + return; + } + + if (mExpandedBubble.hasAppOverlayIntent()) { + // Bubble with activity view expanded state + ActivityView expandedView = mExpandedBubble.getActivityView(); + expandedView.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, mExpandedBubbleHeight)); + + final PendingIntent intent = mExpandedBubble.getAppOverlayIntent(); + mExpandedViewContainer.setHeaderText(intent.getIntent().getComponent().toShortString()); + mExpandedViewContainer.setExpandedView(expandedView); + expandedView.setCallback(new ActivityView.StateCallback() { + @Override + public void onActivityViewReady(ActivityView view) { + Log.d(TAG, "onActivityViewReady(" + + mExpandedBubble.getEntry().key + "): " + view); + view.startActivity(intent); + } + + @Override + public void onActivityViewDestroyed(ActivityView view) { + NotificationEntry entry = mExpandedBubble != null + ? mExpandedBubble.getEntry() : null; + Log.d(TAG, "onActivityViewDestroyed(key=" + + ((entry != null) ? entry.key : "(none)") + "): " + view); + } + }); + } else { + // Bubble with notification view expanded state ExpandableNotificationRow row = mExpandedBubble.getRowView(); - if (!row.equals(mExpandedViewContainer.getChildAt(0))) { - // Different expanded view than what we have + if (row.getParent() != null) { + // Row might still be in the shade when we expand + ((ViewGroup) row.getParent()).removeView(row); + } + if (mIsExpanded) { + mExpandedViewContainer.setExpandedView(row); + } else { mExpandedViewContainer.setExpandedView(null); } - int pointerPosition = mExpandedBubble.getPosition().x - + (mExpandedBubble.getWidth() / 2); - mExpandedViewContainer.setPointerPosition(pointerPosition); - mExpandedViewContainer.setExpandedView(row); + // Bubble with notification as expanded state doesn't need a header / title + mExpandedViewContainer.setHeaderText(null); + } + int pointerPosition = mExpandedBubble.getPosition().x + + (mExpandedBubble.getWidth() / 2); + mExpandedViewContainer.setPointerPosition(pointerPosition); } private void applyCurrentState() { + Log.d(TAG, "applyCurrentState: mIsExpanded=" + mIsExpanded); + mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE); if (!mIsExpanded) { mExpandedViewContainer.setExpandedView(null); } else { mExpandedViewContainer.setTranslationY(mBubbleContainer.getHeight()); - ExpandableNotificationRow row = mExpandedBubble.getRowView(); - applyRowState(row); + View expandedView = mExpandedViewContainer.getExpandedView(); + if (expandedView instanceof ActivityView) { + if (expandedView.isAttachedToWindow()) { + ((ActivityView) expandedView).onLocationChanged(); + } + } else { + applyRowState(mExpandedBubble.getRowView()); + } } int bubbsCount = mBubbleContainer.getChildCount(); for (int i = 0; i < bubbsCount; i++) { BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i); - bv.setZ(bubbsCount - 1); + bv.updateDotVisibility(); + bv.setZ(bubbsCount - i); int transX = mIsExpanded ? (bv.getWidth() + mBubblePadding) * i : mBubblePadding * i; ViewState viewState = new ViewState(); @@ -468,6 +574,7 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F private void applyRowState(ExpandableNotificationRow view) { view.reset(); view.setHeadsUp(false); + view.resetTranslation(); view.setOnKeyguard(false); view.setOnAmbient(false); view.setClipBottomAmount(0); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java index 88030eefdf54..97784b0f4f93 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java @@ -33,7 +33,7 @@ import com.android.systemui.pip.phone.PipDismissViewController; * Handles interpreting touches on a {@link BubbleStackView}. This includes expanding, collapsing, * dismissing, and flings. */ -public class BubbleTouchHandler implements View.OnTouchListener { +class BubbleTouchHandler implements View.OnTouchListener { private BubbleController mController = Dependency.get(BubbleController.class); private PipDismissViewController mDismissViewController; @@ -110,7 +110,7 @@ public class BubbleTouchHandler implements View.OnTouchListener { : stack.getTargetView(event); boolean isFloating = targetView instanceof FloatingView; if (!isFloating || targetView == null || action == MotionEvent.ACTION_OUTSIDE) { - stack.animateExpansion(false /* shouldExpand */); + stack.collapseStack(); cleanUpDismissTarget(); resetTouches(); return false; @@ -196,9 +196,13 @@ public class BubbleTouchHandler implements View.OnTouchListener { mMovementHelper.getTranslateAnim(floatingView, toGoTo, 100, 0).start(); } } else if (floatingView.equals(stack.getExpandedBubble())) { - stack.animateExpansion(false /* shouldExpand */); + stack.collapseStack(); } else if (isBubbleStack) { - stack.animateExpansion(!stack.isExpanded() /* shouldExpand */); + if (stack.isExpanded()) { + stack.collapseStack(); + } else { + stack.expandStack(); + } } else { stack.setExpandedBubble((BubbleView) floatingView); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java index 6c47aac712f6..91893ef3db00 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Android Open Source Project + * Copyright (C) 2018 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. @@ -16,36 +16,49 @@ package com.android.systemui.bubbles; +import android.annotation.Nullable; +import android.app.ActivityView; import android.app.Notification; +import android.app.PendingIntent; import android.content.Context; import android.graphics.Color; import android.graphics.Point; +import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; -import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.shapes.OvalShape; +import android.graphics.drawable.InsetDrawable; +import android.graphics.drawable.LayerDrawable; import android.util.AttributeSet; +import android.util.Log; import android.view.View; -import android.widget.ImageView; -import android.widget.LinearLayout; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.TextView; -import com.android.internal.util.ContrastColorUtil; +import com.android.internal.graphics.ColorUtils; +import com.android.systemui.Interpolators; import com.android.systemui.R; -import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; /** - * A floating object on the screen that has a collapsed and expanded state. + * A floating object on the screen that can post message updates. */ -public class BubbleView extends LinearLayout implements BubbleTouchHandler.FloatingView { +public class BubbleView extends FrameLayout implements BubbleTouchHandler.FloatingView { private static final String TAG = "BubbleView"; + // Same value as Launcher3 badge code + private static final float WHITE_SCRIM_ALPHA = 0.54f; private Context mContext; - private View mIconView; - private NotificationData.Entry mEntry; - private int mBubbleSize; - private int mIconSize; + private BadgedImageView mBadgedImageView; + private TextView mMessageView; + private int mPadding; + private int mIconInset; + + private NotificationEntry mEntry; + private PendingIntent mAppOverlayIntent; + private ActivityView mActivityView; public BubbleView(Context context) { this(context, null); @@ -61,72 +74,201 @@ public class BubbleView extends LinearLayout implements BubbleTouchHandler.Float public BubbleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - setOrientation(LinearLayout.VERTICAL); mContext = context; - mBubbleSize = getResources().getDimensionPixelSize(R.dimen.bubble_size); - mIconSize = getResources().getDimensionPixelSize(R.dimen.bubble_icon_size); + // XXX: can this padding just be on the view and we look it up? + mPadding = getResources().getDimensionPixelSize(R.dimen.bubble_view_padding); + mIconInset = getResources().getDimensionPixelSize(R.dimen.bubble_icon_inset); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mBadgedImageView = (BadgedImageView) findViewById(R.id.bubble_image); + mMessageView = (TextView) findViewById(R.id.message_view); + mMessageView.setVisibility(GONE); + mMessageView.setPivotX(0); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + updateViews(); + } + + @Override + protected void onMeasure(int widthSpec, int heightSpec) { + measureChild(mBadgedImageView, widthSpec, heightSpec); + measureChild(mMessageView, widthSpec, heightSpec); + boolean messageGone = mMessageView.getVisibility() == GONE; + int imageHeight = mBadgedImageView.getMeasuredHeight(); + int imageWidth = mBadgedImageView.getMeasuredWidth(); + int messageHeight = messageGone ? 0 : mMessageView.getMeasuredHeight(); + int messageWidth = messageGone ? 0 : mMessageView.getMeasuredWidth(); + setMeasuredDimension( + getPaddingStart() + imageWidth + mPadding + messageWidth + getPaddingEnd(), + getPaddingTop() + Math.max(imageHeight, messageHeight) + getPaddingBottom()); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + left = getPaddingStart(); + top = getPaddingTop(); + int imageWidth = mBadgedImageView.getMeasuredWidth(); + int imageHeight = mBadgedImageView.getMeasuredHeight(); + int messageWidth = mMessageView.getMeasuredWidth(); + int messageHeight = mMessageView.getMeasuredHeight(); + mBadgedImageView.layout(left, top, left + imageWidth, top + imageHeight); + mMessageView.layout(left + imageWidth + mPadding, top, + left + imageWidth + mPadding + messageWidth, top + messageHeight); } /** * Populates this view with a notification. + * <p> + * This should only be called when a new notification is being set on the view, updates to the + * current notification should use {@link #update(NotificationEntry)}. * * @param entry the notification to display as a bubble. */ - public void setNotif(NotificationData.Entry entry) { - removeAllViews(); - // TODO: migrate to inflater - mIconView = new ImageView(mContext); - addView(mIconView); + public void setNotif(NotificationEntry entry) { + mEntry = entry; + updateViews(); + } - LinearLayout.LayoutParams iconLp = (LinearLayout.LayoutParams) mIconView.getLayoutParams(); - iconLp.width = mBubbleSize; - iconLp.height = mBubbleSize; - mIconView.setLayoutParams(iconLp); + /** + * The {@link NotificationEntry} associated with this view, if one exists. + */ + @Nullable + public NotificationEntry getEntry() { + return mEntry; + } - update(entry); + /** + * The key for the {@link NotificationEntry} associated with this view, if one exists. + */ + @Nullable + public String getKey() { + return (mEntry != null) ? mEntry.key : null; } /** - * Updates the UI based on the entry. + * Updates the UI based on the entry, updates badge and animates messages as needed. */ - public void update(NotificationData.Entry entry) { + public void update(NotificationEntry entry) { mEntry = entry; - Notification n = entry.notification.getNotification(); - Icon ic = n.getLargeIcon() != null ? n.getLargeIcon() : n.getSmallIcon(); + updateViews(); + } - if (n.getLargeIcon() == null) { - createCircledIcon(n.color, ic, ((ImageView) mIconView)); - } else { - ((ImageView) mIconView).setImageIcon(ic); - } + + /** + * @return the {@link ExpandableNotificationRow} view to display notification content when the + * bubble is expanded. + */ + @Nullable + public ExpandableNotificationRow getRowView() { + return (mEntry != null) ? mEntry.getRow() : null; } /** - * @return the key identifying this bubble / notification entry associated with this - * bubble, if it exists. + * Marks this bubble as "read", i.e. no badge should show. */ - public String getKey() { - return mEntry == null ? null : mEntry.key; + public void updateDotVisibility() { + boolean showDot = getEntry().showInShadeWhenBubble(); + animateDot(showDot); } /** - * @return the notification entry associated with this bubble. + * Animates the badge to show or hide. */ - public NotificationData.Entry getEntry() { - return mEntry; + private void animateDot(boolean showDot) { + if (mBadgedImageView.isShowingDot() != showDot) { + mBadgedImageView.setShowDot(showDot); + mBadgedImageView.clearAnimation(); + mBadgedImageView.animate().setDuration(200) + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + .setUpdateListener((valueAnimator) -> { + float fraction = valueAnimator.getAnimatedFraction(); + fraction = showDot ? fraction : 1 - fraction; + mBadgedImageView.setDotScale(fraction); + }).withEndAction(() -> { + if (!showDot) { + mBadgedImageView.setShowDot(false); + } + }).start(); + } + } + + private void updateViews() { + if (mEntry == null) { + return; + } + Notification n = mEntry.notification.getNotification(); + boolean isLarge = n.getLargeIcon() != null; + Icon ic = isLarge ? n.getLargeIcon() : n.getSmallIcon(); + Drawable iconDrawable = ic.loadDrawable(mContext); + if (!isLarge) { + // Center icon on coloured background + iconDrawable.setTint(Color.WHITE); // TODO: dark mode + Drawable bg = new ColorDrawable(n.color); + InsetDrawable d = new InsetDrawable(iconDrawable, mIconInset); + Drawable[] layers = {bg, d}; + mBadgedImageView.setImageDrawable(new LayerDrawable(layers)); + } else { + mBadgedImageView.setImageDrawable(iconDrawable); + } + int badgeColor = determineDominateColor(iconDrawable, n.color); + mBadgedImageView.setDotColor(badgeColor); + animateDot(mEntry.showInShadeWhenBubble() /* showDot */); + } + + private int determineDominateColor(Drawable d, int defaultTint) { + // XXX: should we pull from the drawable, app icon, notif tint? + return ColorUtils.blendARGB(defaultTint, Color.WHITE, WHITE_SCRIM_ALPHA); } /** - * @return the view to display when the bubble is expanded. + * @return a view used to display app overlay content when expanded. */ - public ExpandableNotificationRow getRowView() { - return mEntry.getRow(); + public ActivityView getActivityView() { + if (mActivityView == null) { + mActivityView = new ActivityView(mContext); + Log.d(TAG, "[getActivityView] created: " + mActivityView); + } + return mActivityView; + } + + /** + * Removes and releases an ActivityView if one was previously created for this bubble. + */ + public void destroyActivityView(ViewGroup tmpParent) { + if (mActivityView == null) { + return; + } + // HACK: Only release if initialized. There's no way to know if the ActivityView has + // been initialized. Calling release() if it hasn't been initialized will crash. + + if (!mActivityView.isAttachedToWindow()) { + // HACK: release() will crash if the view is not attached. + + mActivityView.setVisibility(View.GONE); + tmpParent.addView(mActivityView, new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + } + try { + mActivityView.release(); + } catch (IllegalStateException ex) { + Log.e(TAG, "ActivityView either already released, or not yet initialized.", ex); + } + + ((ViewGroup) mActivityView.getParent()).removeView(mActivityView); + mActivityView = null; } @Override public void setPosition(int x, int y) { - setTranslationX(x); - setTranslationY(y); + setPositionX(x); + setPositionY(y); } @Override @@ -144,22 +286,19 @@ public class BubbleView extends LinearLayout implements BubbleTouchHandler.Float return new Point((int) getTranslationX(), (int) getTranslationY()); } - // Seems sub optimal - private void createCircledIcon(int tint, Icon icon, ImageView v) { - // TODO: dark mode - icon.setTint(Color.WHITE); - icon.scaleDownIfNecessary(mIconSize, mIconSize); - v.setImageDrawable(icon.loadDrawable(mContext)); - v.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) v.getLayoutParams(); - int color = ContrastColorUtil.ensureContrast(tint, Color.WHITE, - false /* isBgDarker */, 3); - Drawable d = new ShapeDrawable(new OvalShape()); - d.setTint(color); - v.setBackgroundDrawable(d); - - lp.width = mBubbleSize; - lp.height = mBubbleSize; - v.setLayoutParams(lp); + /** + * @return whether an ActivityView should be used to display the content of this Bubble + */ + public boolean hasAppOverlayIntent() { + return mAppOverlayIntent != null; + } + + public PendingIntent getAppOverlayIntent() { + return mAppOverlayIntent; + + } + + public void setAppOverlayIntent(PendingIntent intent) { + mAppOverlayIntent = intent; } } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java index 1718cff4e62b..4ff27b1b64f0 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java @@ -48,7 +48,7 @@ import java.io.PrintWriter; * * It does not collect touch events when the bouncer shows up. */ -public class FalsingManager implements SensorEventListener { +public class FalsingManager implements SensorEventListener, StateListener { private static final String ENFORCE_BOUNCER = "falsing_manager_enforce_bouncer"; private static final int[] CLASSIFIER_SENSORS = new int[] { @@ -84,8 +84,6 @@ public class FalsingManager implements SensorEventListener { private boolean mShowingAod; private Runnable mPendingWtf; - private final StateListener mStateListener = this::setStatusBarState; - protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { @@ -108,7 +106,7 @@ public class FalsingManager implements SensorEventListener { UserHandle.USER_ALL); updateConfiguration(); - Dependency.get(StatusBarStateController.class).addCallback(mStateListener); + Dependency.get(StatusBarStateController.class).addCallback(this); } public static FalsingManager getInstance(Context context) { @@ -282,14 +280,15 @@ public class FalsingManager implements SensorEventListener { updateSessionActive(); } - private void setStatusBarState(int state) { + @Override + public void onStateChanged(int newState) { if (FalsingLog.ENABLED) { FalsingLog.i("setStatusBarState", new StringBuilder() .append("from=").append(StatusBarState.toShortString(mState)) - .append(" to=").append(StatusBarState.toShortString(state)) + .append(" to=").append(StatusBarState.toShortString(newState)) .toString()); } - mState = state; + mState = newState; updateSessionActive(); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java index 4cb1feebcce4..bd7a421e9762 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java @@ -38,7 +38,13 @@ public interface DozeHost { void setAnimateWakeup(boolean animateWakeup); void setAnimateScreenOff(boolean animateScreenOff); - void onDoubleTap(float x, float y); + /** + * Reports that a tap event happend on the Sensors Low Power Island. + * + * @param x Touch X, or -1 if sensor doesn't support touch location. + * @param y Touch Y, or -1 if sensor doesn't support touch location. + */ + void onSlpiTap(float x, float y); default void setAodDimmingScrim(float scrimOpacity) {} void setDozeScreenBrightness(int value); diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java index 50003e3a6a38..a7847739cd00 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java @@ -35,7 +35,7 @@ public class DozeLog { private static final int SIZE = Build.IS_DEBUGGABLE ? 400 : 50; static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); - private static final int REASONS = 9; + private static final int REASONS = 10; public static final int PULSE_REASON_NONE = -1; public static final int PULSE_REASON_INTENT = 0; @@ -47,6 +47,7 @@ public class DozeLog { public static final int PULSE_REASON_DOCKING = 6; public static final int REASON_SENSOR_WAKE_UP = 7; public static final int PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN = 8; + public static final int PULSE_REASON_SENSOR_TAP = 9; private static boolean sRegisterKeyguardCallback = true; @@ -207,6 +208,7 @@ public class DozeLog { case PULSE_REASON_DOCKING: return "docking"; case PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN: return "wakelockscreen"; case REASON_SENSOR_WAKE_UP: return "wakeup"; + case PULSE_REASON_SENSOR_TAP: return "tap"; default: throw new IllegalArgumentException("bad reason: " + pulseReason); } } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java index b6fc35553760..78374a0b1621 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java @@ -107,6 +107,13 @@ public class DozeSensors { dozeParameters.doubleTapReportsTouchCoordinates(), true /* touchscreen */), new TriggerSensor( + findSensorWithType(config.tapSensorType()), + Settings.Secure.DOZE_TAP_SCREEN_GESTURE, + true /* configured */, + DozeLog.PULSE_REASON_SENSOR_TAP, + false /* reports touch coordinates */, + true /* touchscreen */), + new TriggerSensor( findSensorWithType(config.longPressSensorType()), Settings.Secure.DOZE_PULSE_ON_LONG_PRESS, false /* settingDef */, diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index 6a9b6899d509..e2e448bb0d0c 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -132,6 +132,7 @@ public class DozeTriggers implements DozeMachine.Part { private void onSensor(int pulseReason, boolean sensorPerformedProxCheck, float screenX, float screenY, float[] rawValues) { boolean isDoubleTap = pulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP; + boolean isTap = pulseReason == DozeLog.PULSE_REASON_SENSOR_TAP; boolean isPickup = pulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP; boolean isLongPress = pulseReason == DozeLog.PULSE_REASON_SENSOR_LONG_PRESS; boolean isWakeDisplay = pulseReason == DozeLog.REASON_SENSOR_WAKE_UP; @@ -148,8 +149,10 @@ public class DozeTriggers implements DozeMachine.Part { // In pocket, drop event. return; } - if (isDoubleTap) { - mDozeHost.onDoubleTap(screenX, screenY); + if (isDoubleTap || isTap) { + if (screenX != -1 && screenY != -1) { + mDozeHost.onSlpiTap(screenX, screenY); + } mMachine.wakeUp(); } else if (isPickup) { mMachine.wakeUp(); diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 268245bd4acd..7b18fad0e105 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -67,10 +67,8 @@ import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityEvent; import android.widget.AdapterView.OnItemLongClickListener; import android.widget.BaseAdapter; -import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ImageView.ScaleType; -import android.widget.LinearLayout; import android.widget.TextView; import com.android.internal.R; @@ -86,8 +84,8 @@ import com.android.internal.util.ScreenRecordHelper; import com.android.internal.util.ScreenshotHelper; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.Dependency; -import com.android.systemui.HardwareUiLayout; import com.android.systemui.Interpolators; +import com.android.systemui.MultiListLayout; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; import com.android.systemui.statusbar.phone.ScrimController; @@ -490,6 +488,11 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, public boolean showBeforeProvisioning() { return true; } + + @Override + public boolean shouldBeSeparated() { + return true; + } } private final class RestartAction extends SinglePressAction implements LongPressAction { @@ -926,6 +929,34 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, return getItem(position).isEnabled(); } + public ArrayList<Action> getSeparatedActions(boolean shouldUseSeparatedView) { + ArrayList<Action> separatedActions = new ArrayList<Action>(); + if (!shouldUseSeparatedView) { + return separatedActions; + } + for (int i = 0; i < mItems.size(); i++) { + final Action action = mItems.get(i); + if (action.shouldBeSeparated()) { + separatedActions.add(action); + } + } + return separatedActions; + } + + public ArrayList<Action> getListActions(boolean shouldUseSeparatedView) { + if (!shouldUseSeparatedView) { + return new ArrayList<Action>(mItems); + } + ArrayList<Action> listActions = new ArrayList<Action>(); + for (int i = 0; i < mItems.size(); i++) { + final Action action = mItems.get(i); + if (!action.shouldBeSeparated()) { + listActions.add(action); + } + } + return listActions; + } + @Override public boolean areAllItemsEnabled() { return false; @@ -965,7 +996,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, View view = action.create(mContext, convertView, parent, LayoutInflater.from(mContext)); // Everything but screenshot, the last item, gets white background. if (position == getCount() - 1) { - HardwareUiLayout.get(parent).setDivisionView(view); + MultiListLayout.get(parent).setDivisionView(view); } return view; } @@ -1004,6 +1035,10 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, boolean showBeforeProvisioning(); boolean isEnabled(); + + default boolean shouldBeSeparated() { + return false; + } } /** @@ -1423,9 +1458,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, private final Context mContext; private final MyAdapter mAdapter; - private final LinearLayout mListView; - private final FrameLayout mSeparatedView; - private final HardwareUiLayout mHardwareLayout; + private final MultiListLayout mGlobalActionsLayout; private final OnClickListener mClickListener; private final OnItemLongClickListener mLongClickListener; private final GradientDrawable mGradientDrawable; @@ -1466,16 +1499,11 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY); setContentView(com.android.systemui.R.layout.global_actions_wrapped); - mListView = findViewById(android.R.id.list); - mSeparatedView = findViewById(com.android.systemui.R.id.separated_button); - if (!mShouldDisplaySeparatedButton) { - mSeparatedView.setVisibility(View.GONE); - } - mHardwareLayout = HardwareUiLayout.get(mListView); - mHardwareLayout.setOutsideTouchListener(view -> dismiss()); - mHardwareLayout.setHasSeparatedButton(mShouldDisplaySeparatedButton); - setTitle(R.string.global_actions); - mListView.setAccessibilityDelegate(new View.AccessibilityDelegate() { + mGlobalActionsLayout = (MultiListLayout) + findViewById(com.android.systemui.R.id.global_actions_view); + mGlobalActionsLayout.setOutsideTouchListener(view -> dismiss()); + mGlobalActionsLayout.setHasSeparatedView(mShouldDisplaySeparatedButton); + mGlobalActionsLayout.setListViewAccessibilityDelegate(new View.AccessibilityDelegate() { @Override public boolean dispatchPopulateAccessibilityEvent( View host, AccessibilityEvent event) { @@ -1484,20 +1512,33 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, return true; } }); + setTitle(R.string.global_actions); } private void updateList() { - mListView.removeAllViews(); - mSeparatedView.removeAllViews(); + mGlobalActionsLayout.removeAllItems(); + ArrayList<Action> separatedActions = + mAdapter.getSeparatedActions(mShouldDisplaySeparatedButton); + ArrayList<Action> listActions = mAdapter.getListActions(mShouldDisplaySeparatedButton); + mGlobalActionsLayout.setExpectedListItemCount(listActions.size()); + mGlobalActionsLayout.setExpectedSeparatedItemCount(separatedActions.size()); + for (int i = 0; i < mAdapter.getCount(); i++) { - ViewGroup parentView = mShouldDisplaySeparatedButton && i == mAdapter.getCount() - 1 - ? mSeparatedView : mListView; - View v = mAdapter.getView(i, null, parentView); + Action action = mAdapter.getItem(i); + int separatedIndex = separatedActions.indexOf(action); + ViewGroup parent; + if (separatedIndex != -1) { + parent = mGlobalActionsLayout.getParentView(true, separatedIndex); + } else { + int listIndex = listActions.indexOf(action); + parent = mGlobalActionsLayout.getParentView(false, listIndex); + } + View v = mAdapter.getView(i, null, parent); final int pos = i; v.setOnClickListener(view -> mClickListener.onClick(this, pos)); v.setOnLongClickListener(view -> mLongClickListener.onItemLongClick(null, v, pos, 0)); - parentView.addView(v); + parent.addView(v); } } @@ -1543,9 +1584,9 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, super.show(); mShowing = true; mGradientDrawable.setAlpha(0); - mHardwareLayout.setTranslationX(getAnimTranslation()); - mHardwareLayout.setAlpha(0); - mHardwareLayout.animate() + mGlobalActionsLayout.setTranslationX(getAnimTranslation()); + mGlobalActionsLayout.setAlpha(0); + mGlobalActionsLayout.animate() .alpha(1) .translationX(0) .setDuration(300) @@ -1564,9 +1605,9 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, return; } mShowing = false; - mHardwareLayout.setTranslationX(0); - mHardwareLayout.setAlpha(1); - mHardwareLayout.animate() + mGlobalActionsLayout.setTranslationX(0); + mGlobalActionsLayout.setAlpha(1); + mGlobalActionsLayout.animate() .alpha(0) .translationX(getAnimTranslation()) .setDuration(300) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java index 6a0e8ad35736..c4c8bc703802 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java @@ -50,9 +50,9 @@ import androidx.slice.builders.SliceAction; import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; -import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.statusbar.NotificationMediaManager; +import com.android.systemui.statusbar.StatusBarStateController; import com.android.systemui.statusbar.policy.NextAlarmController; import com.android.systemui.statusbar.policy.NextAlarmControllerImpl; import com.android.systemui.statusbar.policy.ZenModeController; @@ -68,7 +68,7 @@ import java.util.concurrent.TimeUnit; */ public class KeyguardSliceProvider extends SliceProvider implements NextAlarmController.NextAlarmChangeCallback, ZenModeController.Callback, - NotificationMediaManager.MediaListener { + NotificationMediaManager.MediaListener, StatusBarStateController.StateListener { private static final StyleSpan BOLD_STYLE = new StyleSpan(Typeface.BOLD); public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main"; @@ -109,7 +109,9 @@ public class KeyguardSliceProvider extends SliceProvider implements private AlarmManager.AlarmClockInfo mNextAlarmInfo; private PendingIntent mPendingIntent; protected NotificationMediaManager mMediaManager; + private StatusBarStateController mStatusBarStateController; protected MediaMetadata mMediaMetaData; + protected boolean mDozing; /** * Receiver responsible for time ticking and updating the date format. @@ -167,9 +169,20 @@ public class KeyguardSliceProvider extends SliceProvider implements mMediaUri = Uri.parse(KEYGUARD_MEDIA_URI); } - public void initDependencies() { - mMediaManager = Dependency.get(NotificationMediaManager.class); + /** + * Initialize dependencies that don't exist during {@link android.content.ContentProvider} + * instantiation. + * + * @param mediaManager {@link NotificationMediaManager} singleton. + * @param statusBarStateController {@link StatusBarStateController} singleton. + */ + public void initDependencies( + NotificationMediaManager mediaManager, + StatusBarStateController statusBarStateController) { + mMediaManager = mediaManager; mMediaManager.addCallback(this); + mStatusBarStateController = statusBarStateController; + mStatusBarStateController.addCallback(this); } @AnyThread @@ -179,7 +192,7 @@ public class KeyguardSliceProvider extends SliceProvider implements Slice slice; synchronized (this) { ListBuilder builder = new ListBuilder(getContext(), mSliceUri, ListBuilder.INFINITY); - if (mMediaMetaData != null) { + if (needsMediaLocked()) { addMediaLocked(builder); } else { builder.addRow(new RowBuilder(mDateUri).setTitle(mLastText)); @@ -193,6 +206,10 @@ public class KeyguardSliceProvider extends SliceProvider implements return slice; } + protected boolean needsMediaLocked() { + return mMediaMetaData != null && mDozing; + } + protected void addMediaLocked(ListBuilder listBuilder) { if (mMediaMetaData != null) { SpannableStringBuilder builder = new SpannableStringBuilder(); @@ -209,7 +226,7 @@ public class KeyguardSliceProvider extends SliceProvider implements } RowBuilder mediaBuilder = new RowBuilder(mMediaUri).setTitle(builder); - Icon notificationIcon = mMediaManager.getMediaIcon(); + Icon notificationIcon = mMediaManager == null ? null : mMediaManager.getMediaIcon(); if (notificationIcon != null) { IconCompat icon = IconCompat.createFromIcon(notificationIcon); mediaBuilder.addEndItem(icon, ListBuilder.ICON_IMAGE); @@ -389,13 +406,35 @@ public class KeyguardSliceProvider extends SliceProvider implements @Override public void onMetadataChanged(MediaMetadata metadata) { + final boolean notify; synchronized (this) { + boolean neededMedia = needsMediaLocked(); mMediaMetaData = metadata; + notify = neededMedia != needsMediaLocked(); + } + if (notify) { + notifyChange(); } - notifyChange(); } protected void notifyChange() { mContentResolver.notifyChange(mSliceUri, null /* observer */); } + + @Override + public void onDozingChanged(boolean isDozing) { + final boolean notify; + synchronized (this) { + boolean neededMedia = needsMediaLocked(); + mDozing = isDozing; + notify = neededMedia != needsMediaLocked(); + } + if (notify) { + notifyChange(); + } + } + + @Override + public void onStateChanged(int newState) { + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 323cf1fe7022..f14495bb959d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -1843,6 +1843,13 @@ public class KeyguardViewMediator extends SystemUI { */ private void handleHide() { Trace.beginSection("KeyguardViewMediator#handleHide"); + + // It's possible that the device was unlocked in a dream state. It's time to wake up. + if (mAodShowing) { + PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + pm.wakeUp(SystemClock.uptimeMillis(), "com.android.systemui:BOUNCER_DOZING"); + } + synchronized (KeyguardViewMediator.this) { if (DEBUG) Log.d(TAG, "handleHide"); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java index 1e0d4d01f167..b09d6e163b77 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java @@ -36,8 +36,7 @@ public class PipAppOpsListener { private Handler mHandler; private IActivityManager mActivityManager; private AppOpsManager mAppOpsManager; - - private PipMotionHelper mMotionHelper; + private Callback mCallback; private AppOpsManager.OnOpChangedListener mAppOpsChangedListener = new OnOpChangedListener() { @Override @@ -52,7 +51,7 @@ public class PipAppOpsListener { if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) && mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid, packageName) != MODE_ALLOWED) { - mHandler.post(() -> mMotionHelper.dismissPip()); + mHandler.post(() -> mCallback.dismissPip()); } } } catch (NameNotFoundException e) { @@ -63,12 +62,12 @@ public class PipAppOpsListener { }; public PipAppOpsListener(Context context, IActivityManager activityManager, - PipMotionHelper motionHelper) { + Callback callback) { mContext = context; mHandler = new Handler(mContext.getMainLooper()); mActivityManager = activityManager; mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); - mMotionHelper = motionHelper; + mCallback = callback; } public void onActivityPinned(String packageName) { @@ -89,4 +88,10 @@ public class PipAppOpsListener { private void unregisterAppOpsListener() { mAppOpsManager.stopWatchingMode(mAppOpsChangedListener); } -}
\ No newline at end of file + + /** Callback for PipAppOpsListener to request changes to the PIP window. */ + public interface Callback { + /** Dismisses the PIP window. */ + void dismissPip(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java index 38583564f5b0..82aa4737af99 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java @@ -55,7 +55,7 @@ import java.io.PrintWriter; /** * A helper to animate and manipulate the PiP. */ -public class PipMotionHelper implements Handler.Callback { +public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Callback { private static final String TAG = "PipMotionHelper"; private static final boolean DEBUG = false; @@ -172,7 +172,8 @@ public class PipMotionHelper implements Handler.Callback { /** * Dismisses the pinned stack. */ - void dismissPip() { + @Override + public void dismissPip() { if (DEBUG) { Log.d(TAG, "dismissPip: callers=\n" + Debug.getCallers(5, " ")); } diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt index 01ee5cabe5a0..77e25e324915 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt @@ -20,6 +20,7 @@ import android.content.Context import android.content.DialogInterface import android.content.Intent import android.content.res.ColorStateList +import android.util.IconDrawableFactory import android.view.Gravity import android.view.LayoutInflater import android.view.View @@ -37,11 +38,22 @@ class OngoingPrivacyDialog constructor( private val iconSize = context.resources.getDimensionPixelSize( R.dimen.ongoing_appops_dialog_icon_size) + private val plusSize = context.resources.getDimensionPixelSize( + R.dimen.ongoing_appops_dialog_app_plus_size) private val iconColor = context.resources.getColor( com.android.internal.R.color.text_color_primary, context.theme) + private val plusColor: Int private val iconMargin = context.resources.getDimensionPixelSize( R.dimen.ongoing_appops_dialog_icon_margin) private val MAX_ITEMS = context.resources.getInteger(R.integer.ongoing_appops_dialog_max_apps) + private val iconFactory = IconDrawableFactory.newInstance(context, true) + + init { + val a = context.theme.obtainStyledAttributes( + intArrayOf(com.android.internal.R.attr.colorAccent)) + plusColor = a.getColor(0, 0) + a.recycle() + } fun createDialog(): Dialog { val builder = AlertDialog.Builder(context).apply { @@ -52,7 +64,8 @@ class OngoingPrivacyDialog constructor( @Suppress("DEPRECATION") override fun onClick(dialog: DialogInterface?, which: Int) { - Dependency.get(ActivityStarter::class.java).startActivity(intent, false) + Dependency.get(ActivityStarter::class.java) + .postStartActivityDismissingKeyguard(intent, 0) } }) } @@ -86,9 +99,15 @@ class OngoingPrivacyDialog constructor( numItems - MAX_ITEMS ) val overflowPlus = overflow.findViewById(R.id.app_icon) as ImageView + val lp = overflowPlus.layoutParams.apply { + height = plusSize + width = plusSize + } + overflowPlus.layoutParams = lp overflowPlus.apply { - imageTintList = ColorStateList.valueOf(iconColor) - setImageDrawable(context.getDrawable(R.drawable.plus)) + val plus = context.getDrawable(R.drawable.plus) + imageTintList = ColorStateList.valueOf(plusColor) + setImageDrawable(plus) } } @@ -113,17 +132,18 @@ class OngoingPrivacyDialog constructor( } app.icon.let { - appIcon.setImageDrawable(it) + appIcon.setImageDrawable(iconFactory.getShadowedIcon(it)) } appName.text = app.applicationName if (showIcons) { - dialogBuilder.generateIconsForApp(types).forEach { + dialogBuilder.generateIconsForApp(types).forEachIndexed { index, it -> it.setBounds(0, 0, iconSize, iconSize) val image = ImageView(context).apply { imageTintList = ColorStateList.valueOf(iconColor) setImageDrawable(it) } + image.contentDescription = types[index].getName(context) icons.addView(image, lp) } icons.visibility = View.VISIBLE diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 72245999b084..75ab5dfd2977 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -141,14 +141,11 @@ public class QuickStatusBarHeader extends RelativeLayout implements private View mStatusSeparator; private ImageView mRingerModeIcon; private TextView mRingerModeTextView; - private BatteryMeterView mBatteryMeterView; private Clock mClockView; private DateView mDateView; private OngoingPrivacyChip mPrivacyChip; private Space mSpace; private BatteryMeterView mBatteryRemainingIcon; - private TextView mBatteryRemainingText; - private boolean mShowBatteryPercentAndEstimate; private PrivacyItemController mPrivacyItemController; /** Counts how many times the long press tooltip has been shown to the user. */ @@ -229,13 +226,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements // Set the correct tint for the status icons so they contrast mIconManager.setTint(fillColor); - mShowBatteryPercentAndEstimate = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_battery_percentage_setting_available); - - mBatteryMeterView = findViewById(R.id.battery); - mBatteryMeterView.setPercentShowMode(mShowBatteryPercentAndEstimate - ? BatteryMeterView.MODE_ON : BatteryMeterView.MODE_OFF); - mBatteryMeterView.setOnClickListener(this); mClockView = findViewById(R.id.clock); mClockView.setOnClickListener(this); mDateView = findViewById(R.id.date); @@ -245,13 +235,8 @@ public class QuickStatusBarHeader extends RelativeLayout implements // Tint for the battery icons are handled in setupHost() mBatteryRemainingIcon = findViewById(R.id.batteryRemainingIcon); - mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_OFF); // Don't need to worry about tuner settings for this icon mBatteryRemainingIcon.setIgnoreTunerUpdates(true); - - mBatteryRemainingText = findViewById(R.id.batteryRemainingText); - mBatteryRemainingText.setTextColor(fillColor); - updateShowPercent(); } @@ -268,10 +253,8 @@ public class QuickStatusBarHeader extends RelativeLayout implements } private void setChipVisibility(boolean chipVisible) { - mBatteryMeterView.setVisibility(View.VISIBLE); if (chipVisible) { mPrivacyChip.setVisibility(View.VISIBLE); - if (mHasTopCutout) mBatteryMeterView.setVisibility(View.GONE); } else { mPrivacyChip.setVisibility(View.GONE); } @@ -339,7 +322,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements // Update color schemes in landscape to use wallpaperTextColor boolean shouldUseWallpaperTextColor = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE; - mBatteryMeterView.useWallpaperTextColor(shouldUseWallpaperTextColor); mClockView.useWallpaperTextColor(shouldUseWallpaperTextColor); } @@ -415,17 +397,11 @@ public class QuickStatusBarHeader extends RelativeLayout implements .build(); } - private void updateBatteryRemainingText() { - if (!mShowBatteryPercentAndEstimate) { - return; - } - mBatteryRemainingText.setText(mBatteryController.getEstimatedTimeRemainingString()); - } - public void setExpanded(boolean expanded) { if (mExpanded == expanded) return; mExpanded = expanded; mHeaderQsPanel.setExpanded(expanded); + updateEverything(); } /** @@ -518,7 +494,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements } } mSpace.setLayoutParams(lp); - // Decide whether to show BatteryMeterView setChipVisibility(mPrivacyChip.getVisibility() == View.VISIBLE); return super.onApplyWindowInsets(insets); } @@ -545,7 +520,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements mAlarmController.addCallback(this); mContext.registerReceiver(mRingerReceiver, new IntentFilter(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)); - updateBatteryRemainingText(); } else { mZenController.removeCallback(this); mAlarmController.removeCallback(this); @@ -558,9 +532,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements if (v == mClockView) { mActivityStarter.postStartActivityDismissingKeyguard(new Intent( AlarmClock.ACTION_SHOW_ALARMS),0); - } else if (v == mBatteryMeterView) { - mActivityStarter.postStartActivityDismissingKeyguard(new Intent( - Intent.ACTION_POWER_USAGE_SUMMARY),0); } else if (v == mPrivacyChip) { Handler mUiHandler = new Handler(Looper.getMainLooper()); mUiHandler.post(() -> { @@ -697,6 +668,10 @@ public class QuickStatusBarHeader extends RelativeLayout implements .start(); } + public void updateEverything() { + post(() -> setClickable(!mExpanded)); + } + public void setQSPanel(final QSPanel qsPanel) { mQsPanel = qsPanel; setupHost(qsPanel.getHost()); @@ -708,9 +683,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements mHeaderQsPanel.setQSPanelAndHeader(mQsPanel, this); mHeaderQsPanel.setHost(host, null /* No customization in header */); - // Use SystemUI context to get battery meter colors, and let it use the default tint (white) - mBatteryMeterView.setColorsFromContext(mHost.getContext()); - mBatteryMeterView.onDarkChanged(new Rect(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT); Rect tintArea = new Rect(0, 0, 0, 0); int colorForeground = Utils.getColorAttrDefaultColor(getContext(), @@ -758,22 +730,8 @@ public class QuickStatusBarHeader extends RelativeLayout implements .getIntForUser(getContext().getContentResolver(), SHOW_BATTERY_PERCENT, 0, ActivityManager.getCurrentUser()); - mShowBatteryPercentAndEstimate = systemSetting; - - updateBatteryViews(); - } - - private void updateBatteryViews() { - if (mShowBatteryPercentAndEstimate) { - mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON); - mBatteryRemainingIcon.setVisibility(View.VISIBLE); - mBatteryRemainingText.setVisibility(View.VISIBLE); - updateBatteryRemainingText(); - } else { - mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_OFF); - mBatteryRemainingIcon.setVisibility(View.GONE); - mBatteryRemainingText.setVisibility(View.GONE); - } + mBatteryRemainingIcon.setPercentShowMode(systemSetting + ? BatteryMeterView.MODE_ESTIMATE : BatteryMeterView.MODE_ON); } private final class PercentSettingObserver extends ContentObserver { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyTile.java index 5230cea88e8e..7ee37d567a55 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyTile.java @@ -84,7 +84,7 @@ public class SensorPrivacyTile extends QSTileImpl<BooleanState> implements @Override public Intent getLongClickIntent() { - return null; + return new Intent(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 81757d0aadd4..c474faf6b1e0 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -101,6 +101,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private float mBackButtonAlpha; private MotionEvent mStatusBarGestureDownEvent; private float mWindowCornerRadius; + private boolean mSupportsRoundedCornersOnWindows; private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() { @@ -244,6 +245,18 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } + public boolean supportsRoundedCornersOnWindows() { + if (!verifyCaller("supportsRoundedCornersOnWindows")) { + return false; + } + long token = Binder.clearCallingIdentity(); + try { + return mSupportsRoundedCornersOnWindows; + } finally { + Binder.restoreCallingIdentity(token); + } + } + private boolean verifyCaller(String reason) { final int callerId = Binder.getCallingUserHandle().getIdentifier(); if (callerId != mCurrentBoundedUserId) { @@ -353,6 +366,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis mInteractionFlags = Prefs.getInt(mContext, Prefs.Key.QUICK_STEP_INTERACTION_FLAGS, getDefaultInteractionFlags()); mWindowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext.getResources()); + mSupportsRoundedCornersOnWindows = ScreenDecorationsUtils + .supportsRoundedCornersOnWindows(mContext.getResources()); // Listen for the package update changes. if (mDeviceProvisionedController.getCurrentUser() == UserHandle.USER_SYSTEM) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java index bc381699494a..a776d0fbb45e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar; -import static com.android.systemui.statusbar.notification.NotificationData.Entry; - import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Handler; @@ -29,6 +27,7 @@ import android.util.Log; import android.view.accessibility.AccessibilityEvent; import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag; import java.util.stream.Stream; @@ -48,7 +47,7 @@ public abstract class AlertingNotificationManager implements NotificationLifetim * NotificationManagerService side, but we keep it to prevent the UI from looking weird and * will remove when possible. See {@link NotificationLifetimeExtender} */ - protected final ArraySet<Entry> mExtendedLifetimeAlertEntries = new ArraySet<>(); + protected final ArraySet<NotificationEntry> mExtendedLifetimeAlertEntries = new ArraySet<>(); protected NotificationSafeToRemoveCallback mNotificationLifetimeFinishedCallback; protected int mMinimumDisplayTime; @@ -61,7 +60,7 @@ public abstract class AlertingNotificationManager implements NotificationLifetim * Adds the notification to be managed. * @param entry entry to show */ - public void showNotification(@NonNull Entry entry) { + public void showNotification(@NonNull NotificationEntry entry) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "showNotification"); } @@ -139,7 +138,7 @@ public abstract class AlertingNotificationManager implements NotificationLifetim * @return the entry */ @Nullable - public Entry getEntry(@NonNull String key) { + public NotificationEntry getEntry(@NonNull String key) { AlertEntry entry = mAlertEntries.get(key); return entry != null ? entry.mEntry : null; } @@ -149,7 +148,7 @@ public abstract class AlertingNotificationManager implements NotificationLifetim * @return all entries */ @NonNull - public Stream<Entry> getAllEntries() { + public Stream<NotificationEntry> getAllEntries() { return mAlertEntries.values().stream().map(headsUpEntry -> headsUpEntry.mEntry); } @@ -180,7 +179,7 @@ public abstract class AlertingNotificationManager implements NotificationLifetim * Add a new entry and begin managing it. * @param entry the entry to add */ - protected final void addAlertEntry(@NonNull Entry entry) { + protected final void addAlertEntry(@NonNull NotificationEntry entry) { AlertEntry alertEntry = createAlertEntry(); alertEntry.setEntry(entry); mAlertEntries.put(entry.key, alertEntry); @@ -203,7 +202,7 @@ public abstract class AlertingNotificationManager implements NotificationLifetim if (alertEntry == null) { return; } - Entry entry = alertEntry.mEntry; + NotificationEntry entry = alertEntry.mEntry; mAlertEntries.remove(key); onAlertEntryRemoved(alertEntry); entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); @@ -250,12 +249,12 @@ public abstract class AlertingNotificationManager implements NotificationLifetim } @Override - public boolean shouldExtendLifetime(Entry entry) { + public boolean shouldExtendLifetime(NotificationEntry entry) { return !canRemoveImmediately(entry.key); } @Override - public void setShouldManageLifetime(Entry entry, boolean shouldExtend) { + public void setShouldManageLifetime(NotificationEntry entry, boolean shouldExtend) { if (shouldExtend) { mExtendedLifetimeAlertEntries.add(entry); } else { @@ -265,17 +264,17 @@ public abstract class AlertingNotificationManager implements NotificationLifetim /////////////////////////////////////////////////////////////////////////////////////////////// protected class AlertEntry implements Comparable<AlertEntry> { - @Nullable public Entry mEntry; + @Nullable public NotificationEntry mEntry; public long mPostTime; public long mEarliestRemovaltime; @Nullable protected Runnable mRemoveAlertRunnable; - public void setEntry(@NonNull final Entry entry) { + public void setEntry(@NonNull final NotificationEntry entry) { setEntry(entry, () -> removeAlertEntry(entry.key)); } - public void setEntry(@NonNull final Entry entry, + public void setEntry(@NonNull final NotificationEntry entry, @Nullable Runnable removeAlertRunnable) { mEntry = entry; mRemoveAlertRunnable = removeAlertRunnable; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java index 9bfd4ee24ff0..a3beb96780d3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java @@ -25,7 +25,7 @@ import android.util.ArraySet; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.R; -import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag; import javax.inject.Inject; @@ -83,7 +83,7 @@ public class AmbientPulseManager extends AlertingNotificationManager { @Override protected void onAlertEntryAdded(AlertEntry alertEntry) { - NotificationData.Entry entry = alertEntry.mEntry; + NotificationEntry entry = alertEntry.mEntry; entry.setAmbientPulsing(true); for (OnAmbientChangedListener listener : mListeners) { listener.onAmbientStateChanged(entry, true); @@ -92,7 +92,7 @@ public class AmbientPulseManager extends AlertingNotificationManager { @Override protected void onAlertEntryRemoved(AlertEntry alertEntry) { - NotificationData.Entry entry = alertEntry.mEntry; + NotificationEntry entry = alertEntry.mEntry; entry.setAmbientPulsing(false); for (OnAmbientChangedListener listener : mListeners) { listener.onAmbientStateChanged(entry, false); @@ -131,7 +131,7 @@ public class AmbientPulseManager extends AlertingNotificationManager { * @param entry the entry that changed * @param isPulsing true if the entry is now pulsing, false otherwise */ - void onAmbientStateChanged(NotificationData.Entry entry, boolean isPulsing); + void onAmbientStateChanged(NotificationEntry entry, boolean isPulsing); } private final class AmbientEntry extends AlertEntry { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 6a015630a076..904478efb568 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -111,7 +111,6 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< private static final int MSG_SHOW_CHARGING_ANIMATION = 44 << MSG_SHIFT; private static final int MSG_SHOW_PINNING_TOAST_ENTER_EXIT = 45 << MSG_SHIFT; private static final int MSG_SHOW_PINNING_TOAST_ESCAPE = 46 << MSG_SHIFT; - private static final int MSG_BIOMETRIC_TRY_AGAIN = 47 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; @@ -271,11 +270,10 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< default void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, int type, boolean requireConfirmation, int userId) { } - default void onBiometricAuthenticated() { } + default void onBiometricAuthenticated(boolean authenticated) { } default void onBiometricHelp(String message) { } default void onBiometricError(String error) { } default void hideBiometricDialog() { } - default void showBiometricTryAgain() { } } @VisibleForTesting @@ -736,9 +734,9 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< } @Override - public void onBiometricAuthenticated() { + public void onBiometricAuthenticated(boolean authenticated) { synchronized (mLock) { - mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED).sendToTarget(); + mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, authenticated).sendToTarget(); } } @@ -763,13 +761,6 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< } } - @Override - public void showBiometricTryAgain() { - synchronized (mLock) { - mHandler.obtainMessage(MSG_BIOMETRIC_TRY_AGAIN).sendToTarget(); - } - } - private final class H extends Handler { private H(Looper l) { super(l); @@ -991,7 +982,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< break; case MSG_BIOMETRIC_AUTHENTICATED: for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).onBiometricAuthenticated(); + mCallbacks.get(i).onBiometricAuthenticated((boolean) msg.obj); } break; case MSG_BIOMETRIC_HELP: @@ -1024,11 +1015,6 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< mCallbacks.get(i).showPinningEscapeToast(); } break; - case MSG_BIOMETRIC_TRY_AGAIN: - for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).showBiometricTryAgain(); - } - break; } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java index 758fb7a9971e..22d1d5b233ce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java @@ -18,12 +18,14 @@ package com.android.systemui.statusbar; import android.animation.Animator; import android.content.Context; +import android.util.Log; import android.view.ViewPropertyAnimator; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; import com.android.systemui.Interpolators; import com.android.systemui.statusbar.notification.NotificationUtils; +import com.android.systemui.statusbar.phone.StatusBar; /** * Utility class to calculate general fling animation when the finger is released. @@ -196,9 +198,16 @@ public class FlingAnimationUtils { if (startGradient != mCachedStartGradient || velocityFactor != mCachedVelocityFactor) { float speedup = mSpeedUpFactor * (1.0f - velocityFactor); - mInterpolator = new PathInterpolator(speedup, - speedup * startGradient, - mLinearOutSlowInX2, mY2); + float x1 = speedup; + float y1 = speedup * startGradient; + float x2 = mLinearOutSlowInX2; + float y2 = mY2; + try { + mInterpolator = new PathInterpolator(x1, y1, x2, y2); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Illegal path with " + + "x1=" + x1 + " y1=" + y1 + " x2=" + x2 + " y2=" + y2, e); + } mCachedStartGradient = startGradient; mCachedVelocityFactor = velocityFactor; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java index e2177774a75d..3f1ff33437b9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java @@ -32,7 +32,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.AlphaOptimizedLinearLayout; import com.android.systemui.R; import com.android.systemui.plugins.DarkIconDispatcher; -import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.util.List; @@ -50,7 +50,7 @@ public class HeadsUpStatusBarView extends AlphaOptimizedLinearLayout { private int mEndMargin; private View mIconPlaceholder; private TextView mTextView; - private NotificationData.Entry mShowingEntry; + private NotificationEntry mShowingEntry; private Rect mLayoutedIconRect = new Rect(); private int[] mTmpPosition = new int[2]; private boolean mFirstLayout = true; @@ -162,7 +162,7 @@ public class HeadsUpStatusBarView extends AlphaOptimizedLinearLayout { mTextView = findViewById(R.id.text); } - public void setEntry(NotificationData.Entry entry) { + public void setEntry(NotificationEntry entry) { if (entry != null) { mShowingEntry = entry; CharSequence text = entry.headsUpStatusBarText; @@ -261,7 +261,7 @@ public class HeadsUpStatusBarView extends AlphaOptimizedLinearLayout { return super.fitSystemWindows(insets); } - public NotificationData.Entry getShowingEntry() { + public NotificationEntry getShowingEntry() { return mShowingEntry; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java index ecd9814c3073..0f295ba75fe4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java @@ -2,7 +2,7 @@ package com.android.systemui.statusbar; import androidx.annotation.NonNull; -import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; /** * Interface for anything that may need to keep notifications managed even after @@ -24,7 +24,7 @@ public interface NotificationLifetimeExtender { * @param entry the entry containing the notification to check * @return true if the notification lifetime should be extended */ - boolean shouldExtendLifetime(@NonNull NotificationData.Entry entry); + boolean shouldExtendLifetime(@NonNull NotificationEntry entry); /** * Sets whether or not the lifetime should be managed by the extender. In practice, if @@ -37,7 +37,7 @@ public interface NotificationLifetimeExtender { * @param entry the entry that needs an extended lifetime * @param shouldManage true if the extender should manage the entry now, false otherwise */ - void setShouldManageLifetime(@NonNull NotificationData.Entry entry, boolean shouldManage); + void setShouldManageLifetime(@NonNull NotificationEntry entry, boolean shouldManage); /** * The callback for when the notification is now safe to remove (i.e. its lifetime has ended). diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java index bc662e3d8855..f46ded4d61d8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java @@ -18,7 +18,7 @@ import android.content.pm.UserInfo; import android.service.notification.StatusBarNotification; import android.util.SparseArray; -import com.android.systemui.statusbar.notification.NotificationData.Entry; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; public interface NotificationLockscreenUserManager { String PERMISSION_SELF = "com.android.systemui.permission.SELF"; @@ -55,7 +55,7 @@ public interface NotificationLockscreenUserManager { void updatePublicMode(); - boolean needsRedaction(Entry entry); + boolean needsRedaction(NotificationEntry entry); boolean userAllowsPrivateNotificationsInPublic(int currentUserId); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index bba4369b5e01..d5f4d0461ba4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -46,9 +46,9 @@ import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.statusbar.StatusBarStateController.StateListener; -import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationUtils; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.KeyguardMonitor; @@ -407,7 +407,7 @@ public class NotificationLockscreenUserManagerImpl implements } /** @return true if the entry needs redaction when on the lockscreen. */ - public boolean needsRedaction(NotificationData.Entry ent) { + public boolean needsRedaction(NotificationEntry ent) { int userId = ent.notification.getUserId(); boolean currentUserWantsRedaction = !userAllowsPrivateNotificationsInPublic(mCurrentUserId); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index 1bf101c00711..7412702abfea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -37,7 +37,6 @@ import android.media.session.PlaybackState; import android.os.Handler; import android.os.Trace; import android.os.UserHandle; -import android.service.notification.StatusBarNotification; import android.util.Log; import android.view.View; import android.widget.ImageView; @@ -47,9 +46,9 @@ import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.Interpolators; import com.android.systemui.colorextraction.SysuiColorExtractor; -import com.android.systemui.statusbar.notification.NotificationData.Entry; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.phone.BiometricUnlockController; import com.android.systemui.statusbar.phone.LockscreenWallpaper; import com.android.systemui.statusbar.phone.ScrimController; @@ -157,15 +156,10 @@ public class NotificationMediaManager implements Dumpable { notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() { @Override public void onEntryRemoved( - @Nullable Entry entry, - String key, - StatusBarNotification old, + NotificationEntry entry, NotificationVisibility visibility, - boolean lifetimeExtended, boolean removedByUser) { - if (!lifetimeExtended) { - onNotificationRemoved(key); - } + onNotificationRemoved(entry.key); } }); } @@ -194,7 +188,7 @@ public class NotificationMediaManager implements Dumpable { return null; } synchronized (mEntryManager.getNotificationData()) { - Entry entry = mEntryManager.getNotificationData().get(mMediaNotificationKey); + NotificationEntry entry = mEntryManager.getNotificationData().get(mMediaNotificationKey); if (entry == null || entry.expandedIcon == null) { return null; } @@ -216,15 +210,15 @@ public class NotificationMediaManager implements Dumpable { boolean metaDataChanged = false; synchronized (mEntryManager.getNotificationData()) { - ArrayList<Entry> activeNotifications = + ArrayList<NotificationEntry> activeNotifications = mEntryManager.getNotificationData().getActiveNotifications(); final int N = activeNotifications.size(); // Promote the media notification with a controller in 'playing' state, if any. - Entry mediaNotification = null; + NotificationEntry mediaNotification = null; MediaController controller = null; for (int i = 0; i < N; i++) { - final Entry entry = activeNotifications.get(i); + final NotificationEntry entry = activeNotifications.get(i); if (entry.isMediaNotification()) { final MediaSession.Token token = @@ -264,7 +258,7 @@ public class NotificationMediaManager implements Dumpable { final String pkg = aController.getPackageName(); for (int i = 0; i < N; i++) { - final Entry entry = activeNotifications.get(i); + final NotificationEntry entry = activeNotifications.get(i); if (entry.notification.getPackageName().equals(pkg)) { if (DEBUG_MEDIA) { Log.v(TAG, "DEBUG_MEDIA: found controller matching " diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index 886d99eeff17..7d6231f27885 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -51,9 +51,9 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.Dumpable; -import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.RemoteInputView; @@ -103,7 +103,7 @@ public class NotificationRemoteInputManager implements Dumpable { * Notifications that are already removed but are kept around because the remote input is * actively being used (i.e. user is typing in it). See {@link RemoteInputActiveExtender}. */ - protected final ArraySet<NotificationData.Entry> mEntriesKeptForRemoteInputActive = + protected final ArraySet<NotificationEntry> mEntriesKeptForRemoteInputActive = new ArraySet<>(); // Dependencies: @@ -253,14 +253,11 @@ public class NotificationRemoteInputManager implements Dumpable { notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() { @Override public void onEntryRemoved( - @Nullable NotificationData.Entry entry, - String key, - StatusBarNotification old, + @Nullable NotificationEntry entry, NotificationVisibility visibility, - boolean lifetimeExtended, boolean removedByUser) { if (removedByUser && entry != null) { - onPerformRemoveNotification(entry, key); + onPerformRemoveNotification(entry, entry.key); } } }); @@ -272,7 +269,7 @@ public class NotificationRemoteInputManager implements Dumpable { mRemoteInputController = new RemoteInputController(delegate); mRemoteInputController.addCallback(new RemoteInputController.Callback() { @Override - public void onRemoteInputSent(NotificationData.Entry entry) { + public void onRemoteInputSent(NotificationEntry entry) { if (FORCE_REMOTE_INPUT_HISTORY && isNotificationKeptForRemoteInputHistory(entry.key)) { mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.key); @@ -416,7 +413,7 @@ public class NotificationRemoteInputManager implements Dumpable { } @VisibleForTesting - void onPerformRemoveNotification(NotificationData.Entry entry, final String key) { + void onPerformRemoveNotification(NotificationEntry entry, final String key) { if (mKeysKeptForRemoteInputHistory.contains(key)) { mKeysKeptForRemoteInputHistory.remove(key); } @@ -427,7 +424,7 @@ public class NotificationRemoteInputManager implements Dumpable { public void onPanelCollapsed() { for (int i = 0; i < mEntriesKeptForRemoteInputActive.size(); i++) { - NotificationData.Entry entry = mEntriesKeptForRemoteInputActive.valueAt(i); + NotificationEntry entry = mEntriesKeptForRemoteInputActive.valueAt(i); mRemoteInputController.removeRemoteInput(entry, null); if (mNotificationLifetimeFinishedCallback != null) { mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.key); @@ -440,7 +437,7 @@ public class NotificationRemoteInputManager implements Dumpable { return mKeysKeptForRemoteInputHistory.contains(key); } - public boolean shouldKeepForRemoteInputHistory(NotificationData.Entry entry) { + public boolean shouldKeepForRemoteInputHistory(NotificationEntry entry) { if (entry.isDismissed()) { return false; } @@ -450,7 +447,7 @@ public class NotificationRemoteInputManager implements Dumpable { return (mRemoteInputController.isSpinning(entry.key) || entry.hasJustSentRemoteInput()); } - public boolean shouldKeepForSmartReplyHistory(NotificationData.Entry entry) { + public boolean shouldKeepForSmartReplyHistory(NotificationEntry entry) { if (entry.isDismissed()) { return false; } @@ -470,13 +467,13 @@ public class NotificationRemoteInputManager implements Dumpable { @VisibleForTesting StatusBarNotification rebuildNotificationForCanceledSmartReplies( - NotificationData.Entry entry) { + NotificationEntry entry) { return rebuildNotificationWithRemoteInput(entry, null /* remoteInputTest */, false /* showSpinner */); } @VisibleForTesting - StatusBarNotification rebuildNotificationWithRemoteInput(NotificationData.Entry entry, + StatusBarNotification rebuildNotificationWithRemoteInput(NotificationEntry entry, CharSequence remoteInputText, boolean showSpinner) { StatusBarNotification sbn = entry.notification; @@ -533,7 +530,7 @@ public class NotificationRemoteInputManager implements Dumpable { } @VisibleForTesting - public Set<NotificationData.Entry> getEntriesKeptForRemoteInputActive() { + public Set<NotificationEntry> getEntriesKeptForRemoteInputActive() { return mEntriesKeptForRemoteInputActive; } @@ -556,12 +553,12 @@ public class NotificationRemoteInputManager implements Dumpable { */ protected class RemoteInputHistoryExtender extends RemoteInputExtender { @Override - public boolean shouldExtendLifetime(@NonNull NotificationData.Entry entry) { + public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) { return shouldKeepForRemoteInputHistory(entry); } @Override - public void setShouldManageLifetime(NotificationData.Entry entry, + public void setShouldManageLifetime(NotificationEntry entry, boolean shouldExtend) { if (shouldExtend) { CharSequence remoteInputText = entry.remoteInputText; @@ -602,12 +599,12 @@ public class NotificationRemoteInputManager implements Dumpable { */ protected class SmartReplyHistoryExtender extends RemoteInputExtender { @Override - public boolean shouldExtendLifetime(@NonNull NotificationData.Entry entry) { + public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) { return shouldKeepForSmartReplyHistory(entry); } @Override - public void setShouldManageLifetime(NotificationData.Entry entry, + public void setShouldManageLifetime(NotificationEntry entry, boolean shouldExtend) { if (shouldExtend) { StatusBarNotification newSbn = rebuildNotificationForCanceledSmartReplies(entry); @@ -640,7 +637,7 @@ public class NotificationRemoteInputManager implements Dumpable { */ protected class RemoteInputActiveExtender extends RemoteInputExtender { @Override - public boolean shouldExtendLifetime(@NonNull NotificationData.Entry entry) { + public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) { if (entry.isDismissed()) { return false; } @@ -648,7 +645,7 @@ public class NotificationRemoteInputManager implements Dumpable { } @Override - public void setShouldManageLifetime(NotificationData.Entry entry, + public void setShouldManageLifetime(NotificationEntry entry, boolean shouldExtend) { if (shouldExtend) { if (Log.isLoggable(TAG, Log.DEBUG)) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 91b34fc875bf..9ef9c94d7cdc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -55,7 +55,7 @@ import com.android.systemui.statusbar.phone.NotificationIconContainer; * overflow icons that don't fit into the regular list anymore. */ public class NotificationShelf extends ActivatableNotificationView implements - View.OnLayoutChangeListener { + View.OnLayoutChangeListener, StateListener { private static final boolean USE_ANIMATIONS_WHEN_OPENING = SystemProperties.getBoolean("debug.icon_opening_animations", true); @@ -65,12 +65,12 @@ public class NotificationShelf extends ActivatableNotificationView implements private static final String TAG = "NotificationShelf"; private static final long SHELF_IN_TRANSLATION_DURATION = 200; - private boolean mDark; private NotificationIconContainer mShelfIcons; private int[] mTmp = new int[2]; private boolean mHideBackground; private int mIconAppearTopPadding; private int mShelfAppearTranslation; + private float mDarkShelfPadding; private int mStatusBarHeight; private int mStatusBarPaddingStart; private AmbientState mAmbientState; @@ -95,8 +95,6 @@ public class NotificationShelf extends ActivatableNotificationView implements private int mCutoutHeight; private int mGapHeight; - private final StateListener mStateListener = this::setStatusBarState; - public NotificationShelf(Context context, AttributeSet attrs) { super(context, attrs); } @@ -121,13 +119,13 @@ public class NotificationShelf extends ActivatableNotificationView implements protected void onAttachedToWindow() { super.onAttachedToWindow(); Dependency.get(StatusBarStateController.class) - .addCallback(mStateListener, StatusBarStateController.RANK_SHELF); + .addCallback(this, StatusBarStateController.RANK_SHELF); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - Dependency.get(StatusBarStateController.class).removeCallback(mStateListener); + Dependency.get(StatusBarStateController.class).removeCallback(this); } public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout) { @@ -142,6 +140,7 @@ public class NotificationShelf extends ActivatableNotificationView implements mStatusBarPaddingStart = res.getDimensionPixelOffset(R.dimen.status_bar_padding_start); mPaddingBetweenElements = res.getDimensionPixelSize(R.dimen.notification_divider_height); mShelfAppearTranslation = res.getDimensionPixelSize(R.dimen.shelf_appear_translation); + mDarkShelfPadding = res.getDimensionPixelSize(R.dimen.widget_bottom_separator_padding); ViewGroup.LayoutParams layoutParams = getLayoutParams(); layoutParams.height = res.getDimensionPixelOffset(R.dimen.notification_shelf_height); @@ -167,11 +166,29 @@ public class NotificationShelf extends ActivatableNotificationView implements @Override public void setDark(boolean dark, boolean fade, long delay) { - super.setDark(dark, fade, delay); if (mDark == dark) return; - mDark = dark; + super.setDark(dark, fade, delay); mShelfIcons.setDark(dark, fade, delay); updateInteractiveness(); + updateOutline(); + } + + /** + * Alpha animation with translation played when this view is visible on AOD. + */ + public void fadeInTranslating() { + mShelfIcons.setTranslationY(-mShelfAppearTranslation); + mShelfIcons.setAlpha(0); + mShelfIcons.animate() + .setInterpolator(Interpolators.DECELERATE_QUINT) + .translationY(0) + .setDuration(SHELF_IN_TRANSLATION_DURATION) + .start(); + mShelfIcons.animate() + .alpha(1) + .setInterpolator(Interpolators.LINEAR) + .setDuration(SHELF_IN_TRANSLATION_DURATION) + .start(); } @Override @@ -202,10 +219,9 @@ public class NotificationShelf extends ActivatableNotificationView implements float awakenTranslation = Math.max(Math.min(viewEnd, maxShelfEnd) - viewState.height, getFullyClosedTranslation()); - float darkTranslation = mAmbientState.getDarkTopPadding(); float yRatio = mAmbientState.hasPulsingNotifications() ? 0 : mAmbientState.getDarkAmount(); - viewState.yTranslation = MathUtils.lerp(awakenTranslation, darkTranslation, yRatio); + viewState.yTranslation = awakenTranslation + mDarkShelfPadding * yRatio; viewState.zTranslation = ambientState.getBaseZHeight(); // For the small display size, it's not enough to make the icon not covered by // the top cutout so the denominator add the height of cutout. @@ -225,7 +241,7 @@ public class NotificationShelf extends ActivatableNotificationView implements } viewState.hasItemsInStableShelf = lastViewState.inShelf; viewState.hidden = !mAmbientState.isShadeExpanded() - || mAmbientState.isQsCustomizerShowing() || mAmbientState.isFullyDark(); + || mAmbientState.isQsCustomizerShowing(); viewState.maxShelfEnd = maxShelfEnd; } else { viewState.hidden = true; @@ -420,7 +436,7 @@ public class NotificationShelf extends ActivatableNotificationView implements float maxTop = row.getTranslationY(); StatusBarIconView icon = row.getEntry().expandedIcon; float shelfIconPosition = getTranslationY() + icon.getTop() + icon.getTranslationY(); - if (shelfIconPosition < maxTop) { + if (shelfIconPosition < maxTop && !mAmbientState.isDark()) { int top = (int) (maxTop - shelfIconPosition); Rect clipRect = new Rect(0, top, icon.getWidth(), Math.max(top, icon.getHeight())); icon.setClipBounds(clipRect); @@ -431,7 +447,7 @@ public class NotificationShelf extends ActivatableNotificationView implements private void updateContinuousClipping(final ExpandableNotificationRow row) { StatusBarIconView icon = row.getEntry().expandedIcon; - boolean needsContinuousClipping = ViewState.isAnimatingY(icon); + boolean needsContinuousClipping = ViewState.isAnimatingY(icon) && !mAmbientState.isDark(); boolean isContinuousClipping = icon.getTag(TAG_CONTINUOUS_CLIPPING) != null; if (needsContinuousClipping && !isContinuousClipping) { final ViewTreeObserver observer = icon.getViewTreeObserver(); @@ -622,7 +638,9 @@ public class NotificationShelf extends ActivatableNotificationView implements iconState.translateContent = false; } float transitionAmount; - if (isLastChild || !USE_ANIMATIONS_WHEN_OPENING || iconState.useFullTransitionAmount + if (mAmbientState.isDarkAtAll() && !row.isInShelf()) { + transitionAmount = mAmbientState.isFullyDark() ? 1 : 0; + } else if (isLastChild || !USE_ANIMATIONS_WHEN_OPENING || iconState.useFullTransitionAmount || iconState.useLinearTransitionAmount) { transitionAmount = iconTransitionAmount; } else { @@ -745,18 +763,14 @@ public class NotificationShelf extends ActivatableNotificationView implements } } - public boolean hidesBackground() { - return mHideBackground; - } - @Override protected boolean needsOutline() { - return !mHideBackground && super.needsOutline(); + return !mHideBackground && !mDark && super.needsOutline(); } @Override protected boolean shouldHideBackground() { - return super.shouldHideBackground() || mHideBackground; + return super.shouldHideBackground() || mHideBackground || mDark; } @Override @@ -860,8 +874,9 @@ public class NotificationShelf extends ActivatableNotificationView implements mCollapsedIcons.addOnLayoutChangeListener(this); } - private void setStatusBarState(int statusBarState) { - mStatusBarState = statusBarState; + @Override + public void onStateChanged(int newState) { + mStatusBarState = newState; updateInteractiveness(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java index f23ae3f6bfb7..f0d804dbc7a3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java @@ -24,7 +24,7 @@ import android.text.TextUtils; import androidx.annotation.VisibleForTesting; -import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.util.ArrayList; import java.util.Arrays; @@ -52,7 +52,7 @@ public class NotificationUiAdjustment { } public static NotificationUiAdjustment extractFromNotificationEntry( - NotificationData.Entry entry) { + NotificationEntry entry) { return new NotificationUiAdjustment( entry.key, entry.systemGeneratedSmartActions, entry.smartReplies); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java index 017a9c368fdf..f2ff85bb226c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar; -import static com.android.systemui.statusbar.StatusBarState.SHADE; - import android.content.Context; import android.content.res.Resources; import android.os.Trace; @@ -26,10 +24,9 @@ import android.view.View; import android.view.ViewGroup; import com.android.systemui.R; -import com.android.systemui.bubbles.BubbleController; -import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.NotificationGroupManager; @@ -66,7 +63,6 @@ public class NotificationViewHierarchyManager { protected final VisualStabilityManager mVisualStabilityManager; private final StatusBarStateController mStatusBarStateController; private final NotificationEntryManager mEntryManager; - private final BubbleController mBubbleController; // Lazy private final Lazy<ShadeController> mShadeController; @@ -80,41 +76,6 @@ public class NotificationViewHierarchyManager { private NotificationPresenter mPresenter; private NotificationListContainer mListContainer; - private StatusBarStateListener mStatusBarStateListener; - - /** - * Listens for the current state of the status bar and updates the visibility state - * of bubbles as needed. - */ - public class StatusBarStateListener implements StatusBarStateController.StateListener { - private int mState; - private BubbleController mController; - - public StatusBarStateListener(BubbleController controller) { - mController = controller; - } - - /** - * Returns the current status bar state. - */ - public int getCurrentState() { - return mState; - } - - @Override - public void onStateChanged(int newState) { - mState = newState; - // Order here matters because we need to remove the expandable notification row - // from it's current parent (NSSL or bubble) before it can be added to the new parent - if (mState == SHADE) { - updateNotificationViews(); - mController.updateVisibility(true); - } else { - mController.updateVisibility(false); - updateNotificationViews(); - } - } - } @Inject public NotificationViewHierarchyManager(Context context, @@ -123,20 +84,16 @@ public class NotificationViewHierarchyManager { VisualStabilityManager visualStabilityManager, StatusBarStateController statusBarStateController, NotificationEntryManager notificationEntryManager, - BubbleController bubbleController, Lazy<ShadeController> shadeController) { mLockscreenUserManager = notificationLockscreenUserManager; mGroupManager = groupManager; mVisualStabilityManager = visualStabilityManager; mStatusBarStateController = statusBarStateController; mEntryManager = notificationEntryManager; - mBubbleController = bubbleController; mShadeController = shadeController; Resources res = context.getResources(); mAlwaysExpandNonGroupedNotification = res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications); - mStatusBarStateListener = new StatusBarStateListener(mBubbleController); - mStatusBarStateController.addCallback(mStatusBarStateListener); } public void setUpWithPresenter(NotificationPresenter presenter, @@ -150,25 +107,17 @@ public class NotificationViewHierarchyManager { */ //TODO: Rewrite this to focus on Entries, or some other data object instead of views public void updateNotificationViews() { - ArrayList<NotificationData.Entry> activeNotifications = mEntryManager.getNotificationData() + ArrayList<NotificationEntry> activeNotifications = mEntryManager.getNotificationData() .getActiveNotifications(); ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size()); - ArrayList<NotificationData.Entry> toBubble = new ArrayList<>(); final int N = activeNotifications.size(); for (int i = 0; i < N; i++) { - NotificationData.Entry ent = activeNotifications.get(i); + NotificationEntry ent = activeNotifications.get(i); if (ent.isRowDismissed() || ent.isRowRemoved()) { // we don't want to update removed notifications because they could // temporarily become children if they were isolated before. continue; } - ent.getRow().setStatusBarState(mStatusBarStateListener.getCurrentState()); - boolean showAsBubble = ent.isBubble() && !ent.isBubbleDismissed() - && mStatusBarStateListener.getCurrentState() == SHADE; - if (showAsBubble) { - toBubble.add(ent); - continue; - } int userId = ent.notification.getUserId(); @@ -187,7 +136,7 @@ public class NotificationViewHierarchyManager { ent.getRow().setSensitive(sensitive, deviceSensitive); ent.getRow().setNeedsRedaction(needsRedaction); if (mGroupManager.isChildInGroupWithSummary(ent.notification)) { - NotificationData.Entry summary = mGroupManager.getGroupSummary(ent.notification); + NotificationEntry summary = mGroupManager.getGroupSummary(ent.notification); List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(summary.getRow()); if (orderedChildren == null) { @@ -269,12 +218,6 @@ public class NotificationViewHierarchyManager { } - for (int i = 0; i < toBubble.size(); i++) { - // TODO: might make sense to leave them in the shade and just reposition them - NotificationData.Entry ent = toBubble.get(i); - mBubbleController.addBubble(ent); - } - mVisualStabilityManager.onReorderingFinished(); // clear the map again for the next usage mTmpChildOrderMap.clear(); @@ -385,7 +328,7 @@ public class NotificationViewHierarchyManager { } while(!stack.isEmpty()) { ExpandableNotificationRow row = stack.pop(); - NotificationData.Entry entry = row.getEntry(); + NotificationEntry entry = row.getEntry(); boolean isChildNotification = mGroupManager.isChildInGroupWithSummary(entry.notification); @@ -408,7 +351,7 @@ public class NotificationViewHierarchyManager { if (!showOnKeyguard) { // min priority notifications should show if their summary is showing if (mGroupManager.isChildInGroupWithSummary(entry.notification)) { - NotificationData.Entry summary = mGroupManager.getLogicalGroupSummary( + NotificationEntry summary = mGroupManager.getLogicalGroupSummary( entry.notification); if (summary != null && mLockscreenUserManager.shouldShowOnKeyguard( summary.notification)) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java index e8abcc2bd80e..998cf523d3df 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java @@ -24,7 +24,7 @@ import android.util.ArrayMap; import android.util.Pair; import com.android.internal.util.Preconditions; -import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.policy.RemoteInputView; import java.lang.ref.WeakReference; @@ -38,7 +38,7 @@ public class RemoteInputController { private static final boolean ENABLE_REMOTE_INPUT = SystemProperties.getBoolean("debug.enable_remote_input", true); - private final ArrayList<Pair<WeakReference<NotificationData.Entry>, Object>> mOpen + private final ArrayList<Pair<WeakReference<NotificationEntry>, Object>> mOpen = new ArrayList<>(); private final ArrayMap<String, Object> mSpinning = new ArrayMap<>(); private final ArrayList<Callback> mCallbacks = new ArrayList<>(3); @@ -101,7 +101,7 @@ public class RemoteInputController { * @param entry the entry for which a remote input is now active. * @param token a token identifying the view that is managing the remote input */ - public void addRemoteInput(NotificationData.Entry entry, Object token) { + public void addRemoteInput(NotificationEntry entry, Object token) { Preconditions.checkNotNull(entry); Preconditions.checkNotNull(token); @@ -122,7 +122,7 @@ public class RemoteInputController { * the entry is only removed if the token matches the last added token for this * entry. If null, the entry is removed regardless. */ - public void removeRemoteInput(NotificationData.Entry entry, Object token) { + public void removeRemoteInput(NotificationEntry entry, Object token) { Preconditions.checkNotNull(entry); pruneWeakThenRemoveAndContains(null /* contains */, entry /* remove */, token); @@ -173,7 +173,7 @@ public class RemoteInputController { return mSpinning.get(key) == token; } - private void apply(NotificationData.Entry entry) { + private void apply(NotificationEntry entry) { mDelegate.setRemoteInputActive(entry, isRemoteInputActive(entry)); boolean remoteInputActive = isRemoteInputActive(); int N = mCallbacks.size(); @@ -185,7 +185,7 @@ public class RemoteInputController { /** * @return true if {@param entry} has an active RemoteInput */ - public boolean isRemoteInputActive(NotificationData.Entry entry) { + public boolean isRemoteInputActive(NotificationEntry entry) { return pruneWeakThenRemoveAndContains(entry /* contains */, null /* remove */, null /* removeToken */); } @@ -208,10 +208,10 @@ public class RemoteInputController { * @return true if {@param contains} is in the set of active remote inputs */ private boolean pruneWeakThenRemoveAndContains( - NotificationData.Entry contains, NotificationData.Entry remove, Object removeToken) { + NotificationEntry contains, NotificationEntry remove, Object removeToken) { boolean found = false; for (int i = mOpen.size() - 1; i >= 0; i--) { - NotificationData.Entry item = mOpen.get(i).first.get(); + NotificationEntry item = mOpen.get(i).first.get(); Object itemToken = mOpen.get(i).second; boolean removeTokenMatches = (removeToken == null || itemToken == removeToken); @@ -235,7 +235,7 @@ public class RemoteInputController { mCallbacks.add(callback); } - public void remoteInputSent(NotificationData.Entry entry) { + public void remoteInputSent(NotificationEntry entry) { int N = mCallbacks.size(); for (int i = 0; i < N; i++) { mCallbacks.get(i).onRemoteInputSent(entry); @@ -248,16 +248,16 @@ public class RemoteInputController { } // Make a copy because closing the remote inputs will modify mOpen. - ArrayList<NotificationData.Entry> list = new ArrayList<>(mOpen.size()); + ArrayList<NotificationEntry> list = new ArrayList<>(mOpen.size()); for (int i = mOpen.size() - 1; i >= 0; i--) { - NotificationData.Entry entry = mOpen.get(i).first.get(); + NotificationEntry entry = mOpen.get(i).first.get(); if (entry != null && entry.rowExists()) { list.add(entry); } } for (int i = list.size() - 1; i >= 0; i--) { - NotificationData.Entry entry = list.get(i); + NotificationEntry entry = list.get(i); if (entry.rowExists()) { entry.closeRemoteInput(); } @@ -268,31 +268,31 @@ public class RemoteInputController { mDelegate.requestDisallowLongPressAndDismiss(); } - public void lockScrollTo(NotificationData.Entry entry) { + public void lockScrollTo(NotificationEntry entry) { mDelegate.lockScrollTo(entry); } public interface Callback { default void onRemoteInputActive(boolean active) {} - default void onRemoteInputSent(NotificationData.Entry entry) {} + default void onRemoteInputSent(NotificationEntry entry) {} } public interface Delegate { /** * Activate remote input if necessary. */ - void setRemoteInputActive(NotificationData.Entry entry, boolean remoteInputActive); - - /** - * Request that the view does not dismiss nor perform long press for the current touch. - */ - void requestDisallowLongPressAndDismiss(); - - /** - * Request that the view is made visible by scrolling to it, and keep the scroll locked until - * the user scrolls, or {@param v} loses focus or is detached. - */ - void lockScrollTo(NotificationData.Entry entry); + void setRemoteInputActive(NotificationEntry entry, boolean remoteInputActive); + + /** + * Request that the view does not dismiss nor perform long press for the current touch. + */ + void requestDisallowLongPressAndDismiss(); + + /** + * Request that the view is made visible by scrolling to it, and keep the scroll locked until + * the user scrolls, or {@param entry} loses focus or is detached. + */ + void lockScrollTo(NotificationEntry entry); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java deleted file mode 100644 index a846059a7cce..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java +++ /dev/null @@ -1,694 +0,0 @@ -/* - * Copyright (C) 2011 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; - -import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS; -import static android.app.StatusBarManager.DISABLE_NONE; - -import android.annotation.DrawableRes; -import android.content.Context; -import android.content.res.ColorStateList; -import android.content.res.Resources; -import android.graphics.Color; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.telephony.SubscriptionInfo; -import android.util.ArraySet; -import android.util.AttributeSet; -import android.util.Log; -import android.util.TypedValue; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.accessibility.AccessibilityEvent; -import android.widget.ImageView; -import android.widget.LinearLayout; - -import com.android.settingslib.graph.SignalDrawable; -import com.android.systemui.Dependency; -import com.android.systemui.R; -import com.android.systemui.plugins.DarkIconDispatcher; -import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; -import com.android.systemui.statusbar.phone.StatusBarIconController; -import com.android.systemui.statusbar.policy.IconLogger; -import com.android.systemui.statusbar.policy.NetworkController; -import com.android.systemui.statusbar.policy.NetworkController.IconState; -import com.android.systemui.statusbar.policy.NetworkControllerImpl; -import com.android.systemui.statusbar.policy.SecurityController; -import com.android.systemui.tuner.TunerService; -import com.android.systemui.tuner.TunerService.Tunable; -import com.android.systemui.util.Utils.DisableStateTracker; - -import java.util.ArrayList; -import java.util.List; - -// Intimately tied to the design of res/layout/signal_cluster_view.xml -public class SignalClusterView extends LinearLayout implements NetworkControllerImpl.SignalCallback, - SecurityController.SecurityControllerCallback, Tunable, DarkReceiver { - - static final String TAG = "SignalClusterView"; - static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - - private static final String SLOT_AIRPLANE = "airplane"; - private static final String SLOT_MOBILE = "mobile"; - private static final String SLOT_WIFI = "wifi"; - private static final String SLOT_ETHERNET = "ethernet"; - private static final String SLOT_VPN = "vpn"; - - private final NetworkController mNetworkController; - private final SecurityController mSecurityController; - - private boolean mVpnVisible = false; - private int mVpnIconId = 0; - private int mLastVpnIconId = -1; - private boolean mEthernetVisible = false; - private int mEthernetIconId = 0; - private int mLastEthernetIconId = -1; - private boolean mWifiVisible = false; - private int mWifiStrengthId = 0; - private int mLastWifiStrengthId = -1; - private boolean mWifiIn; - private boolean mWifiOut; - private boolean mIsAirplaneMode = false; - private int mAirplaneIconId = 0; - private int mLastAirplaneIconId = -1; - private String mAirplaneContentDescription; - private String mWifiDescription; - private String mEthernetDescription; - private ArrayList<PhoneState> mPhoneStates = new ArrayList<PhoneState>(); - private int mIconTint = Color.WHITE; - private float mDarkIntensity; - private final Rect mTintArea = new Rect(); - private int mNoSimsIcon; - - ViewGroup mEthernetGroup, mWifiGroup; - ImageView mVpn, mEthernet, mWifi, mAirplane, mEthernetDark, mWifiDark; - ImageView mWifiActivityIn; - ImageView mWifiActivityOut; - View mWifiAirplaneSpacer; - View mWifiSignalSpacer; - LinearLayout mMobileSignalGroup; - - private final int mMobileSignalGroupEndPadding; - private final int mMobileDataIconStartPadding; - private final int mSecondaryTelephonyPadding; - private final int mEndPadding; - private final int mEndPaddingNothingVisible; - private final float mIconScaleFactor; - - private boolean mBlockAirplane; - private boolean mBlockMobile; - private boolean mBlockWifi; - private boolean mBlockEthernet; - private boolean mActivityEnabled; - private boolean mForceBlockWifi; - - private final IconLogger mIconLogger = Dependency.get(IconLogger.class); - - public SignalClusterView(Context context) { - this(context, null); - } - - public SignalClusterView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public SignalClusterView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - Resources res = getResources(); - mMobileSignalGroupEndPadding = - res.getDimensionPixelSize(R.dimen.mobile_signal_group_end_padding); - mMobileDataIconStartPadding = - res.getDimensionPixelSize(R.dimen.mobile_data_icon_start_padding); - mSecondaryTelephonyPadding = res.getDimensionPixelSize(R.dimen.secondary_telephony_padding); - mEndPadding = res.getDimensionPixelSize(R.dimen.signal_cluster_battery_padding); - mEndPaddingNothingVisible = res.getDimensionPixelSize( - R.dimen.no_signal_cluster_battery_padding); - - TypedValue typedValue = new TypedValue(); - res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true); - mIconScaleFactor = typedValue.getFloat(); - mNetworkController = Dependency.get(NetworkController.class); - mSecurityController = Dependency.get(SecurityController.class); - addOnAttachStateChangeListener( - new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS)); - updateActivityEnabled(); - } - - public void setForceBlockWifi() { - mForceBlockWifi = true; - mBlockWifi = true; - if (isAttachedToWindow()) { - // Re-register to get new callbacks. - mNetworkController.removeCallback(this); - mNetworkController.addCallback(this); - } - } - - @Override - public void onTuningChanged(String key, String newValue) { - if (!StatusBarIconController.ICON_BLACKLIST.equals(key)) { - return; - } - ArraySet<String> blockList = StatusBarIconController.getIconBlacklist(newValue); - boolean blockAirplane = blockList.contains(SLOT_AIRPLANE); - boolean blockMobile = blockList.contains(SLOT_MOBILE); - boolean blockWifi = blockList.contains(SLOT_WIFI); - boolean blockEthernet = blockList.contains(SLOT_ETHERNET); - - if (blockAirplane != mBlockAirplane || blockMobile != mBlockMobile - || blockEthernet != mBlockEthernet || blockWifi != mBlockWifi) { - mBlockAirplane = blockAirplane; - mBlockMobile = blockMobile; - mBlockEthernet = blockEthernet; - mBlockWifi = blockWifi || mForceBlockWifi; - // Re-register to get new callbacks. - mNetworkController.removeCallback(this); - mNetworkController.addCallback(this); - } - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - mVpn = findViewById(R.id.vpn); - mEthernetGroup = findViewById(R.id.ethernet_combo); - mEthernet = findViewById(R.id.ethernet); - mEthernetDark = findViewById(R.id.ethernet_dark); - mWifiGroup = findViewById(R.id.wifi_combo); - mWifi = findViewById(R.id.wifi_signal); - mWifiDark = findViewById(R.id.wifi_signal_dark); - mWifiActivityIn = findViewById(R.id.wifi_in); - mWifiActivityOut= findViewById(R.id.wifi_out); - mAirplane = findViewById(R.id.airplane); - mWifiAirplaneSpacer = findViewById(R.id.wifi_airplane_spacer); - mWifiSignalSpacer = findViewById(R.id.wifi_signal_spacer); - mMobileSignalGroup = findViewById(R.id.mobile_signal_group); - - maybeScaleVpnAndNoSimsIcons(); - } - - /** - * Extracts the icon off of the VPN and no sims views and maybe scale them by - * {@link #mIconScaleFactor}. Note that the other icons are not scaled here because they are - * dynamic. As such, they need to be scaled each time the icon changes in {@link #apply()}. - */ - private void maybeScaleVpnAndNoSimsIcons() { - if (mIconScaleFactor == 1.f) { - return; - } - - mVpn.setImageDrawable(new ScalingDrawableWrapper(mVpn.getDrawable(), mIconScaleFactor)); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mVpnVisible = mSecurityController.isVpnEnabled(); - mVpnIconId = currentVpnIconId(mSecurityController.isVpnBranded()); - - for (PhoneState state : mPhoneStates) { - if (state.mMobileGroup.getParent() == null) { - mMobileSignalGroup.addView(state.mMobileGroup); - } - } - - int endPadding = mMobileSignalGroup.getChildCount() > 0 ? mMobileSignalGroupEndPadding : 0; - mMobileSignalGroup.setPaddingRelative(0, 0, endPadding, 0); - - Dependency.get(TunerService.class).addTunable(this, StatusBarIconController.ICON_BLACKLIST); - - apply(); - applyIconTint(); - mNetworkController.addCallback(this); - mSecurityController.addCallback(this); - } - - @Override - protected void onDetachedFromWindow() { - mMobileSignalGroup.removeAllViews(); - Dependency.get(TunerService.class).removeTunable(this); - mSecurityController.removeCallback(this); - mNetworkController.removeCallback(this); - - super.onDetachedFromWindow(); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); - - // Re-run all checks against the tint area for all icons - applyIconTint(); - } - - // From SecurityController. - @Override - public void onStateChanged() { - post(new Runnable() { - @Override - public void run() { - mVpnVisible = mSecurityController.isVpnEnabled(); - mVpnIconId = currentVpnIconId(mSecurityController.isVpnBranded()); - apply(); - } - }); - } - - private void updateActivityEnabled() { - mActivityEnabled = mContext.getResources().getBoolean(R.bool.config_showActivity); - } - - @Override - public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon, - boolean activityIn, boolean activityOut, String description, boolean isTransient, - String secondaryLabel) { - mWifiVisible = statusIcon.visible && !mBlockWifi; - mWifiStrengthId = statusIcon.icon; - mWifiDescription = statusIcon.contentDescription; - mWifiIn = activityIn && mActivityEnabled && mWifiVisible; - mWifiOut = activityOut && mActivityEnabled && mWifiVisible; - - apply(); - } - - @Override - public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType, - int qsType, boolean activityIn, boolean activityOut, int volteIcon, - String typeContentDescription, String description, boolean isWide, - int subId, boolean roaming) { - PhoneState state = getState(subId); - if (state == null) { - return; - } - state.mMobileVisible = statusIcon.visible && !mBlockMobile; - state.mMobileStrengthId = statusIcon.icon; - state.mMobileTypeId = statusType; - state.mMobileDescription = statusIcon.contentDescription; - state.mMobileTypeDescription = typeContentDescription; - state.mRoaming = roaming; - state.mActivityIn = activityIn && mActivityEnabled; - state.mActivityOut = activityOut && mActivityEnabled; - - apply(); - } - - @Override - public void setEthernetIndicators(IconState state) { - mEthernetVisible = state.visible && !mBlockEthernet; - mEthernetIconId = state.icon; - mEthernetDescription = state.contentDescription; - - apply(); - } - - @Override - public void setNoSims(boolean show, boolean simDetected) { - // Noop. Status bar no longer shows no sim icon. - } - - @Override - public void setSubs(List<SubscriptionInfo> subs) { - if (hasCorrectSubs(subs)) { - return; - } - mPhoneStates.clear(); - if (mMobileSignalGroup != null) { - mMobileSignalGroup.removeAllViews(); - } - final int n = subs.size(); - for (int i = 0; i < n; i++) { - inflatePhoneState(subs.get(i).getSubscriptionId()); - } - if (isAttachedToWindow()) { - applyIconTint(); - } - } - - private boolean hasCorrectSubs(List<SubscriptionInfo> subs) { - final int N = subs.size(); - if (N != mPhoneStates.size()) { - return false; - } - for (int i = 0; i < N; i++) { - if (mPhoneStates.get(i).mSubId != subs.get(i).getSubscriptionId()) { - return false; - } - } - return true; - } - - private PhoneState getState(int subId) { - for (PhoneState state : mPhoneStates) { - if (state.mSubId == subId) { - return state; - } - } - Log.e(TAG, "Unexpected subscription " + subId); - return null; - } - - private PhoneState inflatePhoneState(int subId) { - PhoneState state = new PhoneState(subId, mContext); - if (mMobileSignalGroup != null) { - mMobileSignalGroup.addView(state.mMobileGroup); - } - mPhoneStates.add(state); - return state; - } - - @Override - public void setIsAirplaneMode(IconState icon) { - mIsAirplaneMode = icon.visible && !mBlockAirplane; - mAirplaneIconId = icon.icon; - mAirplaneContentDescription = icon.contentDescription; - - apply(); - } - - @Override - public void setMobileDataEnabled(boolean enabled) { - // Don't care. - } - - @Override - public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { - // Standard group layout onPopulateAccessibilityEvent() implementations - // ignore content description, so populate manually - if (mEthernetVisible && mEthernetGroup != null && - mEthernetGroup.getContentDescription() != null) - event.getText().add(mEthernetGroup.getContentDescription()); - if (mWifiVisible && mWifiGroup != null && mWifiGroup.getContentDescription() != null) - event.getText().add(mWifiGroup.getContentDescription()); - for (PhoneState state : mPhoneStates) { - state.populateAccessibilityEvent(event); - } - return super.dispatchPopulateAccessibilityEventInternal(event); - } - - @Override - public void onRtlPropertiesChanged(int layoutDirection) { - super.onRtlPropertiesChanged(layoutDirection); - - if (mEthernet != null) { - mEthernet.setImageDrawable(null); - mEthernetDark.setImageDrawable(null); - mLastEthernetIconId = -1; - } - - if (mWifi != null) { - mWifi.setImageDrawable(null); - mWifiDark.setImageDrawable(null); - mLastWifiStrengthId = -1; - } - - for (PhoneState state : mPhoneStates) { - if (state.mMobileType != null) { - state.mMobileType.setImageDrawable(null); - state.mLastMobileTypeId = -1; - } - } - - if (mAirplane != null) { - mAirplane.setImageDrawable(null); - mLastAirplaneIconId = -1; - } - - apply(); - } - - @Override - public boolean hasOverlappingRendering() { - return false; - } - - // Run after each indicator change. - private void apply() { - if (mWifiGroup == null) return; - - if (mVpnVisible) { - if (mLastVpnIconId != mVpnIconId) { - setIconForView(mVpn, mVpnIconId); - mLastVpnIconId = mVpnIconId; - } - mIconLogger.onIconShown(SLOT_VPN); - mVpn.setVisibility(View.VISIBLE); - } else { - mIconLogger.onIconHidden(SLOT_VPN); - mVpn.setVisibility(View.GONE); - } - if (DEBUG) Log.d(TAG, String.format("vpn: %s", mVpnVisible ? "VISIBLE" : "GONE")); - - if (mEthernetVisible) { - if (mLastEthernetIconId != mEthernetIconId) { - setIconForView(mEthernet, mEthernetIconId); - setIconForView(mEthernetDark, mEthernetIconId); - mLastEthernetIconId = mEthernetIconId; - } - mEthernetGroup.setContentDescription(mEthernetDescription); - mIconLogger.onIconShown(SLOT_ETHERNET); - mEthernetGroup.setVisibility(View.VISIBLE); - } else { - mIconLogger.onIconHidden(SLOT_ETHERNET); - mEthernetGroup.setVisibility(View.GONE); - } - - if (DEBUG) Log.d(TAG, - String.format("ethernet: %s", - (mEthernetVisible ? "VISIBLE" : "GONE"))); - - if (mWifiVisible) { - if (mWifiStrengthId != mLastWifiStrengthId) { - setIconForView(mWifi, mWifiStrengthId); - setIconForView(mWifiDark, mWifiStrengthId); - mLastWifiStrengthId = mWifiStrengthId; - } - mIconLogger.onIconShown(SLOT_WIFI); - mWifiGroup.setContentDescription(mWifiDescription); - mWifiGroup.setVisibility(View.VISIBLE); - } else { - mIconLogger.onIconHidden(SLOT_WIFI); - mWifiGroup.setVisibility(View.GONE); - } - - if (DEBUG) Log.d(TAG, - String.format("wifi: %s sig=%d", - (mWifiVisible ? "VISIBLE" : "GONE"), - mWifiStrengthId)); - - mWifiActivityIn.setVisibility(mWifiIn ? View.VISIBLE : View.GONE); - mWifiActivityOut.setVisibility(mWifiOut ? View.VISIBLE : View.GONE); - - boolean anyMobileVisible = false; - int firstMobileTypeId = 0; - for (PhoneState state : mPhoneStates) { - if (state.apply(anyMobileVisible)) { - if (!anyMobileVisible) { - firstMobileTypeId = state.mMobileTypeId; - anyMobileVisible = true; - } - } - } - if (anyMobileVisible) { - mIconLogger.onIconShown(SLOT_MOBILE); - } else { - mIconLogger.onIconHidden(SLOT_MOBILE); - } - - if (mIsAirplaneMode) { - if (mLastAirplaneIconId != mAirplaneIconId) { - setIconForView(mAirplane, mAirplaneIconId); - mLastAirplaneIconId = mAirplaneIconId; - } - mAirplane.setContentDescription(mAirplaneContentDescription); - mIconLogger.onIconShown(SLOT_AIRPLANE); - mAirplane.setVisibility(View.VISIBLE); - } else { - mIconLogger.onIconHidden(SLOT_AIRPLANE); - mAirplane.setVisibility(View.GONE); - } - - if (mIsAirplaneMode && mWifiVisible) { - mWifiAirplaneSpacer.setVisibility(View.VISIBLE); - } else { - mWifiAirplaneSpacer.setVisibility(View.GONE); - } - - if ((anyMobileVisible && firstMobileTypeId != 0) && mWifiVisible) { - mWifiSignalSpacer.setVisibility(View.VISIBLE); - } else { - mWifiSignalSpacer.setVisibility(View.GONE); - } - - boolean anythingVisible = mWifiVisible || mIsAirplaneMode - || anyMobileVisible || mVpnVisible || mEthernetVisible; - setPaddingRelative(0, 0, anythingVisible ? mEndPadding : mEndPaddingNothingVisible, 0); - } - - /** - * Sets the given drawable id on the view. This method will also scale the icon by - * {@link #mIconScaleFactor} if appropriate. - */ - private void setIconForView(ImageView imageView, @DrawableRes int iconId) { - // Using the imageView's context to retrieve the Drawable so that theme is preserved. - Drawable icon = imageView.getContext().getDrawable(iconId); - - if (mIconScaleFactor == 1.f) { - imageView.setImageDrawable(icon); - } else { - imageView.setImageDrawable(new ScalingDrawableWrapper(icon, mIconScaleFactor)); - } - } - - - @Override - public void onDarkChanged(Rect tintArea, float darkIntensity, int tint) { - boolean changed = tint != mIconTint || darkIntensity != mDarkIntensity - || !mTintArea.equals(tintArea); - mIconTint = tint; - mDarkIntensity = darkIntensity; - mTintArea.set(tintArea); - if (changed && isAttachedToWindow()) { - applyIconTint(); - } - } - - private void applyIconTint() { - setTint(mVpn, DarkIconDispatcher.getTint(mTintArea, mVpn, mIconTint)); - setTint(mAirplane, DarkIconDispatcher.getTint(mTintArea, mAirplane, mIconTint)); - applyDarkIntensity( - DarkIconDispatcher.getDarkIntensity(mTintArea, mWifi, mDarkIntensity), - mWifi, mWifiDark); - setTint(mWifiActivityIn, - DarkIconDispatcher.getTint(mTintArea, mWifiActivityIn, mIconTint)); - setTint(mWifiActivityOut, - DarkIconDispatcher.getTint(mTintArea, mWifiActivityOut, mIconTint)); - applyDarkIntensity( - DarkIconDispatcher.getDarkIntensity(mTintArea, mEthernet, mDarkIntensity), - mEthernet, mEthernetDark); - for (int i = 0; i < mPhoneStates.size(); i++) { - mPhoneStates.get(i).setIconTint(mIconTint, mDarkIntensity, mTintArea); - } - } - - private void applyDarkIntensity(float darkIntensity, View lightIcon, View darkIcon) { - lightIcon.setAlpha(1 - darkIntensity); - darkIcon.setAlpha(darkIntensity); - } - - private void setTint(ImageView v, int tint) { - v.setImageTintList(ColorStateList.valueOf(tint)); - } - - private int currentVpnIconId(boolean isBranded) { - return isBranded ? R.drawable.stat_sys_branded_vpn : R.drawable.stat_sys_vpn_ic; - } - - private class PhoneState { - private final int mSubId; - private boolean mMobileVisible = false; - private int mMobileStrengthId = 0, mMobileTypeId = 0; - private int mLastMobileStrengthId = -1; - private int mLastMobileTypeId = -1; - private String mMobileDescription, mMobileTypeDescription; - - private ViewGroup mMobileGroup; - private ImageView mMobile, mMobileType, mMobileRoaming; - private View mMobileRoamingSpace; - public boolean mRoaming; - private ImageView mMobileActivityIn; - private ImageView mMobileActivityOut; - public boolean mActivityIn; - public boolean mActivityOut; - private SignalDrawable mMobileSignalDrawable; - - public PhoneState(int subId, Context context) { - ViewGroup root = (ViewGroup) LayoutInflater.from(context) - .inflate(R.layout.mobile_signal_group, null); - setViews(root); - mSubId = subId; - } - - public void setViews(ViewGroup root) { - mMobileGroup = root; - mMobile = root.findViewById(R.id.mobile_signal); - mMobileType = root.findViewById(R.id.mobile_type); - mMobileRoaming = root.findViewById(R.id.mobile_roaming); - mMobileRoamingSpace = root.findViewById(R.id.mobile_roaming_space); - mMobileActivityIn = root.findViewById(R.id.mobile_in); - mMobileActivityOut = root.findViewById(R.id.mobile_out); - mMobileSignalDrawable = new SignalDrawable(mMobile.getContext()); - mMobile.setImageDrawable(mMobileSignalDrawable); - } - - public boolean apply(boolean isSecondaryIcon) { - if (mMobileVisible && !mIsAirplaneMode) { - if (mLastMobileStrengthId != mMobileStrengthId) { - mMobile.getDrawable().setLevel(mMobileStrengthId); - mLastMobileStrengthId = mMobileStrengthId; - } - - if (mLastMobileTypeId != mMobileTypeId) { - mMobileType.setImageResource(mMobileTypeId); - mLastMobileTypeId = mMobileTypeId; - } - - mMobileGroup.setContentDescription(mMobileTypeDescription - + " " + mMobileDescription); - mMobileGroup.setVisibility(View.VISIBLE); - } else { - mMobileGroup.setVisibility(View.GONE); - } - - // When this isn't next to wifi, give it some extra padding between the signals. - mMobileGroup.setPaddingRelative(isSecondaryIcon ? mSecondaryTelephonyPadding : 0, - 0, 0, 0); - mMobile.setPaddingRelative(mMobileDataIconStartPadding, 0, 0, 0); - - if (DEBUG) Log.d(TAG, String.format("mobile: %s sig=%d typ=%d", - (mMobileVisible ? "VISIBLE" : "GONE"), mMobileStrengthId, mMobileTypeId)); - - mMobileType.setVisibility(mMobileTypeId != 0 ? View.VISIBLE : View.GONE); - mMobileRoaming.setVisibility((mRoaming)? View.VISIBLE : View.GONE); - mMobileRoamingSpace.setVisibility((mRoaming) ? View.VISIBLE : View.GONE); - mMobileActivityIn.setVisibility(mActivityIn ? View.VISIBLE : View.GONE); - mMobileActivityOut.setVisibility(mActivityOut ? View.VISIBLE : View.GONE); - - return mMobileVisible; - } - - public void populateAccessibilityEvent(AccessibilityEvent event) { - if (mMobileVisible && mMobileGroup != null - && mMobileGroup.getContentDescription() != null) { - event.getText().add(mMobileGroup.getContentDescription()); - } - } - - public void setIconTint(int tint, float darkIntensity, Rect tintArea) { - mMobileSignalDrawable.setDarkIntensity(darkIntensity); - setTint(mMobileType, DarkIconDispatcher.getTint(tintArea, mMobileType, tint)); - setTint(mMobileRoaming, DarkIconDispatcher.getTint(tintArea, mMobileRoaming, - tint)); - setTint(mMobileActivityIn, - DarkIconDispatcher.getTint(tintArea, mMobileActivityIn, tint)); - setTint(mMobileActivityOut, - DarkIconDispatcher.getTint(tintArea, mMobileActivityOut, tint)); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java index 9e91133aff09..573c1f8ee509 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java @@ -21,8 +21,8 @@ import android.util.ArraySet; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; -import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.util.Set; @@ -54,7 +54,7 @@ public class SmartReplyController { /** * Notifies StatusBarService a smart reply is sent. */ - public void smartReplySent(NotificationData.Entry entry, int replyIndex, CharSequence reply, + public void smartReplySent(NotificationEntry entry, int replyIndex, CharSequence reply, boolean generatedByAssistant) { mCallback.onSmartReplySent(entry, reply); mSendingKeys.add(entry.key); @@ -70,7 +70,7 @@ public class SmartReplyController { * Notifies StatusBarService a smart action is clicked. */ public void smartActionClicked( - NotificationData.Entry entry, int actionIndex, Notification.Action action, + NotificationEntry entry, int actionIndex, Notification.Action action, boolean generatedByAssistant) { final int count = mEntryManager.getNotificationData().getActiveNotifications().size(); final int rank = mEntryManager.getNotificationData().getRank(entry.key); @@ -95,7 +95,7 @@ public class SmartReplyController { /** * Smart Replies and Actions have been added to the UI. */ - public void smartSuggestionsAdded(final NotificationData.Entry entry, int replyCount, + public void smartSuggestionsAdded(final NotificationEntry entry, int replyCount, int actionCount, boolean generatedByAssistant) { try { mBarService.onNotificationSmartSuggestionsAdded( @@ -105,7 +105,7 @@ public class SmartReplyController { } } - public void stopSending(final NotificationData.Entry entry) { + public void stopSending(final NotificationEntry entry) { if (entry != null) { mSendingKeys.remove(entry.notification.getKey()); } @@ -121,6 +121,6 @@ public class SmartReplyController { * @param entry the entry for the notification * @param reply the reply that was sent */ - void onSmartReplySent(NotificationData.Entry entry, CharSequence reply); + void onSmartReplySent(NotificationEntry entry, CharSequence reply); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java deleted file mode 100644 index 065222762b33..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2017 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; - -import com.android.internal.statusbar.StatusBarIcon; - -import java.util.ArrayList; -import java.util.List; - -/** - * Holds an array of {@link com.android.internal.statusbar.StatusBarIcon}s and draws them - * in a linear layout - */ -public class StatusBarIconContainer { - private final List<StatusBarIcon> mIcons = new ArrayList<>(); - - public StatusBarIconContainer(List<StatusBarIcon> icons) { - mIcons.addAll(icons); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index 3c1335456d04..19ed13e7a95e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -206,6 +206,10 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi mIconScale = SYSTEM_ICON_SCALE; } + public float getIconScaleFullyDark() { + return (float) mStatusBarIconDrawingSizeDark / mStatusBarIconDrawingSize; + } + public float getIconScale() { return mIconScale; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateController.java index 087b65592b7a..54bce1c00354 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateController.java @@ -331,7 +331,8 @@ public class StatusBarStateController implements CallbackController<StateListene * * @param newState the new {@link StatusBarState} */ - public void onStateChanged(int newState); + default void onStateChanged(int newState) { + } /** * Callback to be notified when Dozing changes. Dozing is stored separately from state. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java index 7b42dd901d72..5605f3db90fb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java @@ -20,7 +20,6 @@ import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORC import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_AMBIENT; import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_HEADS_UP; -import android.annotation.Nullable; import android.app.Notification; import android.service.notification.StatusBarNotification; import android.util.Log; @@ -30,6 +29,7 @@ import com.android.systemui.statusbar.AlertingNotificationManager; import com.android.systemui.statusbar.AmbientPulseManager; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationInflater; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -72,24 +72,21 @@ public class NotificationAlertingManager { notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() { @Override - public void onEntryInflated(NotificationData.Entry entry, int inflatedFlags) { + public void onEntryInflated(NotificationEntry entry, int inflatedFlags) { showAlertingView(entry, inflatedFlags); } @Override - public void onEntryUpdated(NotificationData.Entry entry) { + public void onPostEntryUpdated(NotificationEntry entry) { updateAlertState(entry); } @Override public void onEntryRemoved( - @Nullable NotificationData.Entry entry, - String key, - StatusBarNotification old, + NotificationEntry entry, NotificationVisibility visibility, - boolean lifetimeExtended, boolean removedByUser) { - stopAlerting(key); + stopAlerting(entry.key); } }); } @@ -105,7 +102,7 @@ public class NotificationAlertingManager { * @param entry entry to add * @param inflatedFlags flags representing content views that were inflated */ - private void showAlertingView(NotificationData.Entry entry, + private void showAlertingView(NotificationEntry entry, @NotificationInflater.InflationFlag int inflatedFlags) { if ((inflatedFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) { // Possible for shouldHeadsUp to change between the inflation starting and ending. @@ -127,7 +124,7 @@ public class NotificationAlertingManager { } } - private void updateAlertState(NotificationData.Entry entry) { + private void updateAlertState(NotificationEntry entry) { boolean alertAgain = alertAgain(entry, entry.notification.getNotification()); AlertingNotificationManager alertManager; boolean shouldAlert; @@ -153,8 +150,15 @@ public class NotificationAlertingManager { } } - private static boolean alertAgain( - NotificationData.Entry oldEntry, Notification newNotification) { + /** + * Checks whether an update for a notification warrants an alert for the user. + * + * @param oldEntry the entry for this notification. + * @param newNotification the new notification for this entry. + * @return whether this notification should alert the user. + */ + public static boolean alertAgain( + NotificationEntry oldEntry, Notification newNotification) { return oldEntry == null || !oldEntry.hasInterrupted() || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java deleted file mode 100644 index a51896ee69fc..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java +++ /dev/null @@ -1,1072 +0,0 @@ -/* - * Copyright (C) 2008 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.notification; - -import static android.app.Notification.CATEGORY_ALARM; -import static android.app.Notification.CATEGORY_CALL; -import static android.app.Notification.CATEGORY_EVENT; -import static android.app.Notification.CATEGORY_MESSAGE; -import static android.app.Notification.CATEGORY_REMINDER; -import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; -import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT; -import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST; -import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; -import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; - -import android.annotation.NonNull; -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.Person; -import android.content.Context; -import android.graphics.drawable.Icon; -import android.os.Bundle; -import android.os.Parcelable; -import android.os.SystemClock; -import android.service.notification.NotificationListenerService.Ranking; -import android.service.notification.NotificationListenerService.RankingMap; -import android.service.notification.SnoozeCriterion; -import android.service.notification.StatusBarNotification; -import android.util.ArrayMap; -import android.util.ArraySet; -import android.view.View; -import android.widget.ImageView; - -import androidx.annotation.Nullable; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.statusbar.StatusBarIcon; -import com.android.internal.util.ArrayUtils; -import com.android.internal.util.ContrastColorUtil; -import com.android.systemui.Dependency; -import com.android.systemui.statusbar.InflationTask; -import com.android.systemui.statusbar.NotificationMediaManager; -import com.android.systemui.statusbar.StatusBarIconView; -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; -import com.android.systemui.statusbar.notification.row.NotificationGuts; -import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag; -import com.android.systemui.statusbar.phone.NotificationGroupManager; -import com.android.systemui.statusbar.policy.HeadsUpManager; - -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; - -/** - * The list of currently displaying notifications. - */ -public class NotificationData { - - private final NotificationFilter mNotificationFilter = Dependency.get(NotificationFilter.class); - - /** - * These dependencies are late init-ed - */ - private KeyguardEnvironment mEnvironment; - private NotificationMediaManager mMediaManager; - - private HeadsUpManager mHeadsUpManager; - - public static final class Entry { - private static final long LAUNCH_COOLDOWN = 2000; - private static final long REMOTE_INPUT_COOLDOWN = 500; - private static final long INITIALIZATION_DELAY = 400; - private static final long NOT_LAUNCHED_YET = -LAUNCH_COOLDOWN; - private static final int COLOR_INVALID = 1; - public final String key; - public StatusBarNotification notification; - public NotificationChannel channel; - public long lastAudiblyAlertedMs; - public boolean noisy; - public boolean ambient; - public int importance; - public StatusBarIconView icon; - public StatusBarIconView expandedIcon; - private boolean interruption; - public boolean autoRedacted; // whether the redacted notification was generated by us - public int targetSdk; - private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET; - public CharSequence remoteInputText; - public List<SnoozeCriterion> snoozeCriteria; - public int userSentiment = Ranking.USER_SENTIMENT_NEUTRAL; - /** Smart Actions provided by the NotificationAssistantService. */ - @NonNull - public List<Notification.Action> systemGeneratedSmartActions = Collections.emptyList(); - public CharSequence[] smartReplies = new CharSequence[0]; - @VisibleForTesting - public int suppressedVisualEffects; - public boolean suspended; - - private Entry parent; // our parent (if we're in a group) - private ArrayList<Entry> children = new ArrayList<Entry>(); - private ExpandableNotificationRow row; // the outer expanded view - - private int mCachedContrastColor = COLOR_INVALID; - private int mCachedContrastColorIsFor = COLOR_INVALID; - private InflationTask mRunningTask = null; - private Throwable mDebugThrowable; - public CharSequence remoteInputTextWhenReset; - public long lastRemoteInputSent = NOT_LAUNCHED_YET; - public ArraySet<Integer> mActiveAppOps = new ArraySet<>(3); - public CharSequence headsUpStatusBarText; - public CharSequence headsUpStatusBarTextPublic; - - private long initializationTime = -1; - - /** - * Whether or not this row represents a system notification. Note that if this is - * {@code null}, that means we were either unable to retrieve the info or have yet to - * retrieve the info. - */ - public Boolean mIsSystemNotification; - - /** - * Has the user sent a reply through this Notification. - */ - private boolean hasSentReply; - - /** - * Whether this notification should be displayed as a bubble. - */ - private boolean mIsBubble; - - /** - * Whether the user has dismissed this notification when it was in bubble form. - */ - private boolean mUserDismissedBubble; - - public Entry(StatusBarNotification n) { - this(n, null); - } - - public Entry(StatusBarNotification n, @Nullable Ranking ranking) { - this.key = n.getKey(); - this.notification = n; - if (ranking != null) { - populateFromRanking(ranking); - } - } - - public void populateFromRanking(@NonNull Ranking ranking) { - channel = ranking.getChannel(); - lastAudiblyAlertedMs = ranking.getLastAudiblyAlertedMillis(); - importance = ranking.getImportance(); - ambient = ranking.isAmbient(); - snoozeCriteria = ranking.getSnoozeCriteria(); - userSentiment = ranking.getUserSentiment(); - systemGeneratedSmartActions = ranking.getSmartActions() == null - ? Collections.emptyList() : ranking.getSmartActions(); - smartReplies = ranking.getSmartReplies() == null - ? new CharSequence[0] - : ranking.getSmartReplies().toArray(new CharSequence[0]); - suppressedVisualEffects = ranking.getSuppressedVisualEffects(); - suspended = ranking.isSuspended(); - } - - public void setInterruption() { - interruption = true; - } - - public boolean hasInterrupted() { - return interruption; - } - - public void setIsBubble(boolean bubbleable) { - mIsBubble = bubbleable; - } - - public boolean isBubble() { - return mIsBubble; - } - - public void setBubbleDismissed(boolean userDismissed) { - mUserDismissedBubble = userDismissed; - } - - public boolean isBubbleDismissed() { - return mUserDismissedBubble; - } - - /** - * Resets the notification entry to be re-used. - */ - public void reset() { - if (row != null) { - row.reset(); - } - } - - public ExpandableNotificationRow getRow() { - return row; - } - - //TODO: This will go away when we have a way to bind an entry to a row - public void setRow(ExpandableNotificationRow row) { - this.row = row; - } - - @Nullable - public List<Entry> getChildren() { - if (children.size() <= 0) { - return null; - } - - return children; - } - - public void notifyFullScreenIntentLaunched() { - setInterruption(); - lastFullScreenIntentLaunchTime = SystemClock.elapsedRealtime(); - } - - public boolean hasJustLaunchedFullScreenIntent() { - return SystemClock.elapsedRealtime() < lastFullScreenIntentLaunchTime + LAUNCH_COOLDOWN; - } - - public boolean hasJustSentRemoteInput() { - return SystemClock.elapsedRealtime() < lastRemoteInputSent + REMOTE_INPUT_COOLDOWN; - } - - public boolean hasFinishedInitialization() { - return initializationTime == -1 || - SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY; - } - - /** - * Create the icons for a notification - * @param context the context to create the icons with - * @param sbn the notification - * @throws InflationException - */ - public void createIcons(Context context, StatusBarNotification sbn) - throws InflationException { - Notification n = sbn.getNotification(); - final Icon smallIcon = n.getSmallIcon(); - if (smallIcon == null) { - throw new InflationException("No small icon in notification from " - + sbn.getPackageName()); - } - - // Construct the icon. - icon = new StatusBarIconView(context, - sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn); - icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - - // Construct the expanded icon. - expandedIcon = new StatusBarIconView(context, - sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn); - expandedIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - final StatusBarIcon ic = new StatusBarIcon( - sbn.getUser(), - sbn.getPackageName(), - smallIcon, - n.iconLevel, - n.number, - StatusBarIconView.contentDescForNotification(context, n)); - if (!icon.set(ic) || !expandedIcon.set(ic)) { - icon = null; - expandedIcon = null; - throw new InflationException("Couldn't create icon: " + ic); - } - expandedIcon.setVisibility(View.INVISIBLE); - expandedIcon.setOnVisibilityChangedListener( - newVisibility -> { - if (row != null) { - row.setIconsVisible(newVisibility != View.VISIBLE); - } - }); - } - - public void setIconTag(int key, Object tag) { - if (icon != null) { - icon.setTag(key, tag); - expandedIcon.setTag(key, tag); - } - } - - /** - * Update the notification icons. - * - * @param context the context to create the icons with. - * @param sbn the notification to read the icon from. - * @throws InflationException - */ - public void updateIcons(Context context, StatusBarNotification sbn) - throws InflationException { - if (icon != null) { - // Update the icon - Notification n = sbn.getNotification(); - final StatusBarIcon ic = new StatusBarIcon( - notification.getUser(), - notification.getPackageName(), - n.getSmallIcon(), - n.iconLevel, - n.number, - StatusBarIconView.contentDescForNotification(context, n)); - icon.setNotification(sbn); - expandedIcon.setNotification(sbn); - if (!icon.set(ic) || !expandedIcon.set(ic)) { - throw new InflationException("Couldn't update icon: " + ic); - } - } - } - - public int getContrastedColor(Context context, boolean isLowPriority, - int backgroundColor) { - int rawColor = isLowPriority ? Notification.COLOR_DEFAULT : - notification.getNotification().color; - if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) { - return mCachedContrastColor; - } - final int contrasted = ContrastColorUtil.resolveContrastColor(context, rawColor, - backgroundColor); - mCachedContrastColorIsFor = rawColor; - mCachedContrastColor = contrasted; - return mCachedContrastColor; - } - - /** - * Abort all existing inflation tasks - */ - public void abortTask() { - if (mRunningTask != null) { - mRunningTask.abort(); - mRunningTask = null; - } - } - - public void setInflationTask(InflationTask abortableTask) { - // abort any existing inflation - InflationTask existing = mRunningTask; - abortTask(); - mRunningTask = abortableTask; - if (existing != null && mRunningTask != null) { - mRunningTask.supersedeTask(existing); - } - } - - public void onInflationTaskFinished() { - mRunningTask = null; - } - - @VisibleForTesting - public InflationTask getRunningTask() { - return mRunningTask; - } - - /** - * Set a throwable that is used for debugging - * - * @param debugThrowable the throwable to save - */ - public void setDebugThrowable(Throwable debugThrowable) { - mDebugThrowable = debugThrowable; - } - - public Throwable getDebugThrowable() { - return mDebugThrowable; - } - - public void onRemoteInputInserted() { - lastRemoteInputSent = NOT_LAUNCHED_YET; - remoteInputTextWhenReset = null; - } - - public void setHasSentReply() { - hasSentReply = true; - } - - public boolean isLastMessageFromReply() { - if (!hasSentReply) { - return false; - } - Bundle extras = notification.getNotification().extras; - CharSequence[] replyTexts = extras.getCharSequenceArray( - Notification.EXTRA_REMOTE_INPUT_HISTORY); - if (!ArrayUtils.isEmpty(replyTexts)) { - return true; - } - Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES); - if (messages != null && messages.length > 0) { - Parcelable message = messages[messages.length - 1]; - if (message instanceof Bundle) { - Notification.MessagingStyle.Message lastMessage = - Notification.MessagingStyle.Message.getMessageFromBundle( - (Bundle) message); - if (lastMessage != null) { - Person senderPerson = lastMessage.getSenderPerson(); - if (senderPerson == null) { - return true; - } - Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON); - return Objects.equals(user, senderPerson); - } - } - } - return false; - } - - public void setInitializationTime(long time) { - if (initializationTime == -1) { - initializationTime = time; - } - } - - public void sendAccessibilityEvent(int eventType) { - if (row != null) { - row.sendAccessibilityEvent(eventType); - } - } - - /** - * Used by NotificationMediaManager to determine... things - * @return {@code true} if we are a media notification - */ - public boolean isMediaNotification() { - if (row == null) return false; - - return row.isMediaRow(); - } - - /** - * We are a top level child if our parent is the list of notifications duh - * @return {@code true} if we're a top level notification - */ - public boolean isTopLevelChild() { - return row != null && row.isTopLevelChild(); - } - - public void resetUserExpansion() { - if (row != null) row.resetUserExpansion(); - } - - public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) { - if (row != null) row.freeContentViewWhenSafe(inflationFlag); - } - - public void setAmbientPulsing(boolean pulsing) { - if (row != null) row.setAmbientPulsing(pulsing); - } - - public boolean rowExists() { - return row != null; - } - - public boolean isRowDismissed() { - return row != null && row.isDismissed(); - } - - public boolean isRowRemoved() { - return row != null && row.isRemoved(); - } - - /** - * @return {@code true} if the row is null or removed - */ - public boolean isRemoved() { - //TODO: recycling invalidates this - return row == null || row.isRemoved(); - } - - /** - * @return {@code true} if the row is null or dismissed - */ - public boolean isDismissed() { - //TODO: recycling - return row == null || row.isDismissed(); - } - - public boolean isRowPinned() { - return row != null && row.isPinned(); - } - - public void setRowPinned(boolean pinned) { - if (row != null) row.setPinned(pinned); - } - - public boolean isRowAnimatingAway() { - return row != null && row.isHeadsUpAnimatingAway(); - } - - public boolean isRowHeadsUp() { - return row != null && row.isHeadsUp(); - } - - public void setHeadsUp(boolean shouldHeadsUp) { - if (row != null) row.setHeadsUp(shouldHeadsUp); - } - - public boolean mustStayOnScreen() { - return row != null && row.mustStayOnScreen(); - } - - public void setHeadsUpIsVisible() { - if (row != null) row.setHeadsUpIsVisible(); - } - - //TODO: i'm imagining a world where this isn't just the row, but I could be rwong - public ExpandableNotificationRow getHeadsUpAnimationView() { - return row; - } - - public void setUserLocked(boolean userLocked) { - if (row != null) row.setUserLocked(userLocked); - } - - public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) { - if (row != null) row.setUserExpanded(userExpanded, allowChildExpansion); - } - - public void setGroupExpansionChanging(boolean changing) { - if (row != null) row.setGroupExpansionChanging(changing); - } - - public void notifyHeightChanged(boolean needsAnimation) { - if (row != null) row.notifyHeightChanged(needsAnimation); - } - - public void closeRemoteInput() { - if (row != null) row.closeRemoteInput(); - } - - public boolean areChildrenExpanded() { - return row != null && row.areChildrenExpanded(); - } - - public boolean keepInParent() { - return row != null && row.keepInParent(); - } - - //TODO: probably less confusing to say "is group fully visible" - public boolean isGroupNotFullyVisible() { - return row == null || row.isGroupNotFullyVisible(); - } - - public NotificationGuts getGuts() { - if (row != null) return row.getGuts(); - return null; - } - - public boolean hasLowPriorityStateUpdated() { - return row != null && row.hasLowPriorityStateUpdated(); - } - - public void removeRow() { - if (row != null) row.setRemoved(); - } - - public boolean isSummaryWithChildren() { - return row != null && row.isSummaryWithChildren(); - } - - public void setKeepInParent(boolean keep) { - if (row != null) row.setKeepInParent(keep); - } - - public void onDensityOrFontScaleChanged() { - if (row != null) row.onDensityOrFontScaleChanged(); - } - - public boolean areGutsExposed() { - return row != null && row.getGuts() != null && row.getGuts().isExposed(); - } - - public boolean isChildInGroup() { - return parent == null; - } - - public void setLowPriorityStateUpdated(boolean updated) { - if (row != null) row.setLowPriorityStateUpdated(updated); - } - - /** - * @return Can the underlying notification be cleared? This can be different from whether the - * notification can be dismissed in case notifications are sensitive on the lockscreen. - * @see #canViewBeDismissed() - */ - public boolean isClearable() { - if (notification == null || !notification.isClearable()) { - return false; - } - if (children.size() > 0) { - for (int i = 0; i < children.size(); i++) { - Entry child = children.get(i); - if (!child.isClearable()) { - return false; - } - } - } - return true; - } - - public boolean canViewBeDismissed() { - if (row == null) return true; - return row.canViewBeDismissed(); - } - - boolean isExemptFromDndVisualSuppression() { - if (isNotificationBlockedByPolicy(notification.getNotification())) { - return false; - } - - if ((notification.getNotification().flags - & Notification.FLAG_FOREGROUND_SERVICE) != 0) { - return true; - } - if (notification.getNotification().isMediaNotification()) { - return true; - } - if (mIsSystemNotification != null && mIsSystemNotification) { - return true; - } - return false; - } - - private boolean shouldSuppressVisualEffect(int effect) { - if (isExemptFromDndVisualSuppression()) { - return false; - } - return (suppressedVisualEffects & effect) != 0; - } - - /** - * Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_FULL_SCREEN_INTENT} - * is set for this entry. - */ - public boolean shouldSuppressFullScreenIntent() { - return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_FULL_SCREEN_INTENT); - } - - /** - * Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_PEEK} - * is set for this entry. - */ - public boolean shouldSuppressPeek() { - return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_PEEK); - } - - /** - * Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_STATUS_BAR} - * is set for this entry. - */ - public boolean shouldSuppressStatusBar() { - return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_STATUS_BAR); - } - - /** - * Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_AMBIENT} - * is set for this entry. - */ - public boolean shouldSuppressAmbient() { - return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_AMBIENT); - } - - /** - * Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_NOTIFICATION_LIST} - * is set for this entry. - */ - public boolean shouldSuppressNotificationList() { - return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_NOTIFICATION_LIST); - } - } - - private final ArrayMap<String, Entry> mEntries = new ArrayMap<>(); - private final ArrayList<Entry> mSortedAndFiltered = new ArrayList<>(); - private final ArrayList<Entry> mFilteredForUser = new ArrayList<>(); - - private final NotificationGroupManager mGroupManager - = Dependency.get(NotificationGroupManager.class); - - private RankingMap mRankingMap; - private final Ranking mTmpRanking = new Ranking(); - - public void setHeadsUpManager(HeadsUpManager headsUpManager) { - mHeadsUpManager = headsUpManager; - } - - private final Comparator<Entry> mRankingComparator = new Comparator<Entry>() { - private final Ranking mRankingA = new Ranking(); - private final Ranking mRankingB = new Ranking(); - - @Override - public int compare(Entry a, Entry b) { - final StatusBarNotification na = a.notification; - final StatusBarNotification nb = b.notification; - int aImportance = NotificationManager.IMPORTANCE_DEFAULT; - int bImportance = NotificationManager.IMPORTANCE_DEFAULT; - int aRank = 0; - int bRank = 0; - - if (mRankingMap != null) { - // RankingMap as received from NoMan - getRanking(a.key, mRankingA); - getRanking(b.key, mRankingB); - aImportance = mRankingA.getImportance(); - bImportance = mRankingB.getImportance(); - aRank = mRankingA.getRank(); - bRank = mRankingB.getRank(); - } - - String mediaNotification = getMediaManager().getMediaNotificationKey(); - - // IMPORTANCE_MIN media streams are allowed to drift to the bottom - final boolean aMedia = a.key.equals(mediaNotification) - && aImportance > NotificationManager.IMPORTANCE_MIN; - final boolean bMedia = b.key.equals(mediaNotification) - && bImportance > NotificationManager.IMPORTANCE_MIN; - - boolean aSystemMax = aImportance >= NotificationManager.IMPORTANCE_HIGH && - isSystemNotification(na); - boolean bSystemMax = bImportance >= NotificationManager.IMPORTANCE_HIGH && - isSystemNotification(nb); - - boolean isHeadsUp = a.row.isHeadsUp(); - if (isHeadsUp != b.row.isHeadsUp()) { - return isHeadsUp ? -1 : 1; - } else if (isHeadsUp) { - // Provide consistent ranking with headsUpManager - return mHeadsUpManager.compare(a, b); - } else if (a.row.isAmbientPulsing() != b.row.isAmbientPulsing()) { - return a.row.isAmbientPulsing() ? -1 : 1; - } else if (aMedia != bMedia) { - // Upsort current media notification. - return aMedia ? -1 : 1; - } else if (aSystemMax != bSystemMax) { - // Upsort PRIORITY_MAX system notifications - return aSystemMax ? -1 : 1; - } else if (aRank != bRank) { - return aRank - bRank; - } else { - return Long.compare(nb.getNotification().when, na.getNotification().when); - } - } - }; - - private KeyguardEnvironment getEnvironment() { - if (mEnvironment == null) { - mEnvironment = Dependency.get(KeyguardEnvironment.class); - } - return mEnvironment; - } - - private NotificationMediaManager getMediaManager() { - if (mMediaManager == null) { - mMediaManager = Dependency.get(NotificationMediaManager.class); - } - return mMediaManager; - } - - /** - * Returns the sorted list of active notifications (depending on {@link KeyguardEnvironment} - * - * <p> - * This call doesn't update the list of active notifications. Call {@link #filterAndSort()} - * when the environment changes. - * <p> - * Don't hold on to or modify the returned list. - */ - public ArrayList<Entry> getActiveNotifications() { - return mSortedAndFiltered; - } - - public ArrayList<Entry> getNotificationsForCurrentUser() { - mFilteredForUser.clear(); - - synchronized (mEntries) { - final int N = mEntries.size(); - for (int i = 0; i < N; i++) { - Entry entry = mEntries.valueAt(i); - final StatusBarNotification sbn = entry.notification; - if (!getEnvironment().isNotificationForCurrentProfiles(sbn)) { - continue; - } - mFilteredForUser.add(entry); - } - } - return mFilteredForUser; - } - - public Entry get(String key) { - return mEntries.get(key); - } - - public void add(Entry entry) { - synchronized (mEntries) { - mEntries.put(entry.notification.getKey(), entry); - } - mGroupManager.onEntryAdded(entry); - - updateRankingAndSort(mRankingMap); - } - - public Entry remove(String key, RankingMap ranking) { - Entry removed; - synchronized (mEntries) { - removed = mEntries.remove(key); - } - if (removed == null) return null; - mGroupManager.onEntryRemoved(removed); - updateRankingAndSort(ranking); - return removed; - } - - /** Updates the given notification entry with the provided ranking. */ - public void update(Entry entry, RankingMap ranking, StatusBarNotification notification) { - updateRanking(ranking); - final StatusBarNotification oldNotification = entry.notification; - entry.notification = notification; - mGroupManager.onEntryUpdated(entry, oldNotification); - } - - public void updateRanking(RankingMap ranking) { - updateRankingAndSort(ranking); - } - - public void updateAppOp(int appOp, int uid, String pkg, String key, boolean showIcon) { - synchronized (mEntries) { - final int N = mEntries.size(); - for (int i = 0; i < N; i++) { - Entry entry = mEntries.valueAt(i); - if (uid == entry.notification.getUid() - && pkg.equals(entry.notification.getPackageName()) - && key.equals(entry.key)) { - if (showIcon) { - entry.mActiveAppOps.add(appOp); - } else { - entry.mActiveAppOps.remove(appOp); - } - } - } - } - } - - /** - * Returns true if this notification should be displayed in the high-priority notifications - * section (and on the lockscreen and status bar). - */ - public boolean isHighPriority(StatusBarNotification statusBarNotification) { - if (mRankingMap != null) { - getRanking(statusBarNotification.getKey(), mTmpRanking); - if (mTmpRanking.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT - || statusBarNotification.getNotification().isForegroundService() - || statusBarNotification.getNotification().hasMediaSession()) { - return true; - } - if (mGroupManager.isSummaryOfGroup(statusBarNotification)) { - for (Entry child : mGroupManager.getLogicalChildren(statusBarNotification)) { - if (isHighPriority(child.notification)) { - return true; - } - } - } - } - return false; - } - - public boolean isAmbient(String key) { - if (mRankingMap != null) { - getRanking(key, mTmpRanking); - return mTmpRanking.isAmbient(); - } - return false; - } - - public int getVisibilityOverride(String key) { - if (mRankingMap != null) { - getRanking(key, mTmpRanking); - return mTmpRanking.getVisibilityOverride(); - } - return Ranking.VISIBILITY_NO_OVERRIDE; - } - - /** - * Categories that are explicitly called out on DND settings screens are always blocked, if - * DND has flagged them, even if they are foreground or system notifications that might - * otherwise visually bypass DND. - */ - private static boolean isNotificationBlockedByPolicy(Notification n) { - if (isCategory(CATEGORY_CALL, n) - || isCategory(CATEGORY_MESSAGE, n) - || isCategory(CATEGORY_ALARM, n) - || isCategory(CATEGORY_EVENT, n) - || isCategory(CATEGORY_REMINDER, n)) { - return true; - } - return false; - } - - private static boolean isCategory(String category, Notification n) { - return Objects.equals(n.category, category); - } - - public int getImportance(String key) { - if (mRankingMap != null) { - getRanking(key, mTmpRanking); - return mTmpRanking.getImportance(); - } - return NotificationManager.IMPORTANCE_UNSPECIFIED; - } - - public String getOverrideGroupKey(String key) { - if (mRankingMap != null) { - getRanking(key, mTmpRanking); - return mTmpRanking.getOverrideGroupKey(); - } - return null; - } - - public List<SnoozeCriterion> getSnoozeCriteria(String key) { - if (mRankingMap != null) { - getRanking(key, mTmpRanking); - return mTmpRanking.getSnoozeCriteria(); - } - return null; - } - - public NotificationChannel getChannel(String key) { - if (mRankingMap != null) { - getRanking(key, mTmpRanking); - return mTmpRanking.getChannel(); - } - return null; - } - - public int getRank(String key) { - if (mRankingMap != null) { - getRanking(key, mTmpRanking); - return mTmpRanking.getRank(); - } - return 0; - } - - public boolean shouldHide(String key) { - if (mRankingMap != null) { - getRanking(key, mTmpRanking); - return mTmpRanking.isSuspended(); - } - return false; - } - - private void updateRankingAndSort(RankingMap ranking) { - if (ranking != null) { - mRankingMap = ranking; - synchronized (mEntries) { - final int N = mEntries.size(); - for (int i = 0; i < N; i++) { - Entry entry = mEntries.valueAt(i); - if (!getRanking(entry.key, mTmpRanking)) { - continue; - } - final StatusBarNotification oldSbn = entry.notification.cloneLight(); - final String overrideGroupKey = getOverrideGroupKey(entry.key); - if (!Objects.equals(oldSbn.getOverrideGroupKey(), overrideGroupKey)) { - entry.notification.setOverrideGroupKey(overrideGroupKey); - mGroupManager.onEntryUpdated(entry, oldSbn); - } - entry.populateFromRanking(mTmpRanking); - } - } - } - filterAndSort(); - } - - /** - * Get the ranking from the current ranking map. - * - * @param key the key to look up - * @param outRanking the ranking to populate - * - * @return {@code true} if the ranking was properly obtained. - */ - @VisibleForTesting - protected boolean getRanking(String key, Ranking outRanking) { - return mRankingMap.getRanking(key, outRanking); - } - - // TODO: This should not be public. Instead the Environment should notify this class when - // anything changed, and this class should call back the UI so it updates itself. - public void filterAndSort() { - mSortedAndFiltered.clear(); - - synchronized (mEntries) { - final int N = mEntries.size(); - for (int i = 0; i < N; i++) { - Entry entry = mEntries.valueAt(i); - - if (mNotificationFilter.shouldFilterOut(entry)) { - continue; - } - - mSortedAndFiltered.add(entry); - } - } - - Collections.sort(mSortedAndFiltered, mRankingComparator); - } - - public void dump(PrintWriter pw, String indent) { - int N = mSortedAndFiltered.size(); - pw.print(indent); - pw.println("active notifications: " + N); - int active; - for (active = 0; active < N; active++) { - NotificationData.Entry e = mSortedAndFiltered.get(active); - dumpEntry(pw, indent, active, e); - } - synchronized (mEntries) { - int M = mEntries.size(); - pw.print(indent); - pw.println("inactive notifications: " + (M - active)); - int inactiveCount = 0; - for (int i = 0; i < M; i++) { - Entry entry = mEntries.valueAt(i); - if (!mSortedAndFiltered.contains(entry)) { - dumpEntry(pw, indent, inactiveCount, entry); - inactiveCount++; - } - } - } - } - - private void dumpEntry(PrintWriter pw, String indent, int i, Entry e) { - getRanking(e.key, mTmpRanking); - pw.print(indent); - pw.println(" [" + i + "] key=" + e.key + " icon=" + e.icon); - StatusBarNotification n = e.notification; - pw.print(indent); - pw.println(" pkg=" + n.getPackageName() + " id=" + n.getId() + " importance=" + - mTmpRanking.getImportance()); - pw.print(indent); - pw.println(" notification=" + n.getNotification()); - } - - private static boolean isSystemNotification(StatusBarNotification sbn) { - String sbnPackage = sbn.getPackageName(); - return "android".equals(sbnPackage) || "com.android.systemui".equals(sbnPackage); - } - - /** - * Provides access to keyguard state and user settings dependent data. - */ - public interface KeyguardEnvironment { - boolean isDeviceProvisioned(); - boolean isNotificationForCurrentProfiles(StatusBarNotification sbn); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java index 1d06ce031f2b..839b06cec496 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java @@ -19,6 +19,7 @@ import android.annotation.Nullable; import android.service.notification.StatusBarNotification; import com.android.internal.statusbar.NotificationVisibility; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationInflater; /** @@ -29,25 +30,38 @@ public interface NotificationEntryListener { * Called when a new notification is posted. At this point, the notification is "pending": its * views haven't been inflated yet and most of the system pretends like it doesn't exist yet. */ - default void onPendingEntryAdded(NotificationData.Entry entry) { + default void onPendingEntryAdded(NotificationEntry entry) { + } + + // TODO: Combine this with onPreEntryUpdated into "onBeforeEntryFiltered" or similar + /** + * Called when a new entry is created but before it has been filtered or displayed to the user. + */ + default void onBeforeNotificationAdded(NotificationEntry entry) { } /** * Called when a new entry is created. */ - default void onNotificationAdded(NotificationData.Entry entry) { + default void onNotificationAdded(NotificationEntry entry) { + } + + /** + * Called when a notification is updated, before any filtering of notifications have occurred. + */ + default void onPreEntryUpdated(NotificationEntry entry) { } /** - * Called when a notification was updated. + * Called when a notification was updated, after any filtering of notifications have occurred. */ - default void onEntryUpdated(NotificationData.Entry entry) { + default void onPostEntryUpdated(NotificationEntry entry) { } /** * Called when a notification's views are inflated for the first time. */ - default void onEntryInflated(NotificationData.Entry entry, + default void onEntryInflated(NotificationEntry entry, @NotificationInflater.InflationFlag int inflatedFlags) { } @@ -57,7 +71,7 @@ public interface NotificationEntryListener { * * @param entry notification data entry that was reinflated. */ - default void onEntryReinflated(NotificationData.Entry entry) { + default void onEntryReinflated(NotificationEntry entry) { } /** @@ -71,20 +85,14 @@ public interface NotificationEntryListener { * because the developer retracted it). * @param entry notification data entry that was removed. Null if no entry existed for the * removed key at the time of removal. - * @param key key of notification that was removed - * @param old StatusBarNotification of the notification before it was removed * @param visibility logging data related to the visibility of the notification at the time of * removal, if it was removed by a user action. Null if it was not removed by * a user action. - * @param lifetimeExtended true if something is artificially extending how long the notification * @param removedByUser true if the notification was removed by a user action */ default void onEntryRemoved( - @Nullable NotificationData.Entry entry, - String key, - StatusBarNotification old, + NotificationEntry entry, @Nullable NotificationVisibility visibility, - boolean lifetimeExtended, boolean removedByUser) { } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index 2d316508c8cf..989e781ab5ba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -15,17 +15,12 @@ */ package com.android.systemui.statusbar.notification; -import static com.android.systemui.bubbles.BubbleController.DEBUG_DEMOTE_TO_NOTIF; - import android.annotation.Nullable; import android.app.Notification; import android.content.Context; -import android.os.Handler; -import android.os.UserHandle; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; -import android.util.ArraySet; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -33,18 +28,17 @@ import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.ForegroundServiceController; -import com.android.systemui.bubbles.BubbleController; import com.android.systemui.statusbar.NotificationLifetimeExtender; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationUiAdjustment; import com.android.systemui.statusbar.NotificationUpdateHandler; -import com.android.systemui.statusbar.notification.NotificationData.KeyguardEnvironment; -import com.android.systemui.statusbar.notification.row.NotificationGutsManager; +import com.android.systemui.statusbar.notification.collection.NotificationData; +import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationInflater; import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; -import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.util.leak.LeakDetector; @@ -53,7 +47,6 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.concurrent.TimeUnit; /** * NotificationEntryManager is responsible for the adding, removing, and updating of notifications. @@ -64,53 +57,31 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.InflationCallback, NotificationUpdateHandler, - VisualStabilityManager.Callback, - BubbleController.BubbleDismissListener { + VisualStabilityManager.Callback { private static final String TAG = "NotificationEntryMgr"; - protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - - public static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30); + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - protected final Context mContext; - protected final HashMap<String, NotificationData.Entry> mPendingNotifications = new HashMap<>(); + private final Context mContext; + @VisibleForTesting + protected final HashMap<String, NotificationEntry> mPendingNotifications = new HashMap<>(); - private final NotificationGutsManager mGutsManager = - Dependency.get(NotificationGutsManager.class); - private final DeviceProvisionedController mDeviceProvisionedController = - Dependency.get(DeviceProvisionedController.class); private final ForegroundServiceController mForegroundServiceController = Dependency.get(ForegroundServiceController.class); - private final BubbleController mBubbleController = Dependency.get(BubbleController.class); // Lazily retrieved dependencies private NotificationRemoteInputManager mRemoteInputManager; private NotificationRowBinder mNotificationRowBinder; - private final Handler mDeferredNotificationViewUpdateHandler; - private Runnable mUpdateNotificationViewsCallback; - private NotificationPresenter mPresenter; private NotificationListenerService.RankingMap mLatestRankingMap; + @VisibleForTesting protected NotificationData mNotificationData; - protected NotificationListContainer mListContainer; + @VisibleForTesting final ArrayList<NotificationLifetimeExtender> mNotificationLifetimeExtenders = new ArrayList<>(); private final List<NotificationEntryListener> mNotificationEntryListeners = new ArrayList<>(); - private final DeviceProvisionedController.DeviceProvisionedListener - mDeviceProvisionedListener = - new DeviceProvisionedController.DeviceProvisionedListener() { - @Override - public void onDeviceProvisionedChanged() { - updateNotifications(); - } - }; - - public void destroy() { - mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener); - } - @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("NotificationEntryManager state:"); @@ -118,7 +89,7 @@ public class NotificationEntryManager implements if (mPendingNotifications.size() == 0) { pw.println("null"); } else { - for (NotificationData.Entry entry : mPendingNotifications.values()) { + for (NotificationEntry entry : mPendingNotifications.values()) { pw.println(entry.notification); } } @@ -126,9 +97,7 @@ public class NotificationEntryManager implements public NotificationEntryManager(Context context) { mContext = context; - mBubbleController.setDismissListener(this /* bubbleEventListener */); mNotificationData = new NotificationData(); - mDeferredNotificationViewUpdateHandler = new Handler(); } /** Adds a {@link NotificationEntryListener}. */ @@ -153,15 +122,17 @@ public class NotificationEntryManager implements return mNotificationRowBinder; } + // TODO: Remove this once we can always use a mocked row binder in our tests + @VisibleForTesting + void setRowBinder(NotificationRowBinder notificationRowBinder) { + mNotificationRowBinder = notificationRowBinder; + } + public void setUpWithPresenter(NotificationPresenter presenter, NotificationListContainer listContainer, HeadsUpManager headsUpManager) { mPresenter = presenter; - mUpdateNotificationViewsCallback = mPresenter::updateNotificationViews; mNotificationData.setHeadsUpManager(headsUpManager); - mListContainer = listContainer; - - mDeviceProvisionedController.addCallback(mDeviceProvisionedListener); } /** Adds multiple {@link NotificationLifetimeExtender}s. */ @@ -181,14 +152,6 @@ public class NotificationEntryManager implements return mNotificationData; } - protected Context getContext() { - return mContext; - } - - protected NotificationPresenter getPresenter() { - return mPresenter; - } - @Override public void onReorderingAllowed() { updateNotifications(); @@ -203,30 +166,13 @@ public class NotificationEntryManager implements n.getKey(), null, nv, false /* forceRemove */, true /* removedByUser */); } - @Override - public void onStackDismissed() { - updateNotifications(); - } - - @Override - public void onBubbleDismissed(String key) { - NotificationData.Entry entry = mNotificationData.get(key); - if (entry != null) { - entry.setBubbleDismissed(true); - if (!DEBUG_DEMOTE_TO_NOTIF) { - performRemoveNotification(entry.notification); - } - } - updateNotifications(); - } - private void abortExistingInflation(String key) { if (mPendingNotifications.containsKey(key)) { - NotificationData.Entry entry = mPendingNotifications.get(key); + NotificationEntry entry = mPendingNotifications.get(key); entry.abortTask(); mPendingNotifications.remove(key); } - NotificationData.Entry addedEntry = mNotificationData.get(key); + NotificationEntry addedEntry = mNotificationData.get(key); if (addedEntry != null) { addedEntry.abortTask(); } @@ -247,32 +193,8 @@ public class NotificationEntryManager implements } } - private void addEntry(NotificationData.Entry shadeEntry) { - if (shadeEntry == null) { - return; - } - // Add the expanded view and icon. - mNotificationData.add(shadeEntry); - tagForeground(shadeEntry.notification); - updateNotifications(); - for (NotificationEntryListener listener : mNotificationEntryListeners) { - listener.onNotificationAdded(shadeEntry); - } - - maybeScheduleUpdateNotificationViews(shadeEntry); - } - - private void maybeScheduleUpdateNotificationViews(NotificationData.Entry entry) { - long audibleAlertTimeout = RECENTLY_ALERTED_THRESHOLD_MS - - (System.currentTimeMillis() - entry.lastAudiblyAlertedMs); - if (audibleAlertTimeout > 0) { - mDeferredNotificationViewUpdateHandler.postDelayed( - mUpdateNotificationViewsCallback, audibleAlertTimeout); - } - } - @Override - public void onAsyncInflationFinished(NotificationData.Entry entry, + public void onAsyncInflationFinished(NotificationEntry entry, @InflationFlag int inflatedFlags) { mPendingNotifications.remove(entry.key); // If there was an async task started after the removal, we don't want to add it back to @@ -283,7 +205,14 @@ public class NotificationEntryManager implements for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onEntryInflated(entry, inflatedFlags); } - addEntry(entry); + mNotificationData.add(entry); + for (NotificationEntryListener listener : mNotificationEntryListeners) { + listener.onBeforeNotificationAdded(entry); + } + updateNotifications(); + for (NotificationEntryListener listener : mNotificationEntryListeners) { + listener.onNotificationAdded(entry); + } } else { for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onEntryReinflated(entry); @@ -305,11 +234,10 @@ public class NotificationEntryManager implements @Nullable NotificationVisibility visibility, boolean forceRemove, boolean removedByUser) { - final NotificationData.Entry entry = mNotificationData.get(key); + final NotificationEntry entry = mNotificationData.get(key); abortExistingInflation(key); - StatusBarNotification old = null; boolean lifetimeExtended = false; if (entry != null) { @@ -336,31 +264,20 @@ public class NotificationEntryManager implements if (entry.rowExists()) { entry.removeRow(); - mListContainer.cleanUpViewStateForEntry(entry); } // Let's remove the children if this was a summary handleGroupSummaryRemoved(key); - old = removeNotificationViews(key, ranking); - } - } + mNotificationData.remove(key, ranking); + updateNotifications(); + Dependency.get(LeakDetector.class).trackGarbage(entry); - for (NotificationEntryListener listener : mNotificationEntryListeners) { - listener.onEntryRemoved(entry, key, old, visibility, lifetimeExtended, removedByUser); - } - } - - private StatusBarNotification removeNotificationViews(String key, - NotificationListenerService.RankingMap ranking) { - NotificationData.Entry entry = mNotificationData.remove(key, ranking); - if (entry == null) { - Log.w(TAG, "removeNotification for unknown key: " + key); - return null; + for (NotificationEntryListener listener : mNotificationEntryListeners) { + listener.onEntryRemoved(entry, visibility, removedByUser); + } + } } - updateNotifications(); - Dependency.get(LeakDetector.class).trackGarbage(entry); - return entry.notification; } /** @@ -374,19 +291,19 @@ public class NotificationEntryManager implements * */ private void handleGroupSummaryRemoved(String key) { - NotificationData.Entry entry = mNotificationData.get(key); + NotificationEntry entry = mNotificationData.get(key); if (entry != null && entry.rowExists() && entry.isSummaryWithChildren()) { if (entry.notification.getOverrideGroupKey() != null && !entry.isRowDismissed()) { // We don't want to remove children for autobundled notifications as they are not // always cancelled. We only remove them if they were dismissed by the user. return; } - List<NotificationData.Entry> childEntries = entry.getChildren(); + List<NotificationEntry> childEntries = entry.getChildren(); if (childEntries == null) { return; } for (int i = 0; i < childEntries.size(); i++) { - NotificationData.Entry childEntry = childEntries.get(i); + NotificationEntry childEntry = childEntries.get(i); boolean isForeground = (entry.notification.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0; boolean keepForReply = @@ -405,38 +322,6 @@ public class NotificationEntryManager implements } } - public void updateNotificationsOnDensityOrFontScaleChanged() { - ArrayList<NotificationData.Entry> userNotifications = - mNotificationData.getNotificationsForCurrentUser(); - for (int i = 0; i < userNotifications.size(); i++) { - NotificationData.Entry entry = userNotifications.get(i); - entry.onDensityOrFontScaleChanged(); - boolean exposedGuts = entry.areGutsExposed(); - if (exposedGuts) { - mGutsManager.onDensityOrFontScaleChanged(entry); - } - } - } - - private NotificationData.Entry createNotificationEntry( - StatusBarNotification sbn, NotificationListenerService.Ranking ranking) - throws InflationException { - if (DEBUG) { - Log.d(TAG, "createNotificationEntry(notification=" + sbn + " " + ranking); - } - - NotificationData.Entry entry = new NotificationData.Entry(sbn, ranking); - if (BubbleController.shouldAutoBubble(getContext(), entry)) { - entry.setIsBubble(true); - } - - Dependency.get(LeakDetector.class).trackInstance(entry); - // Construct the expanded view. - getRowBinder().inflateViews(entry, () -> performRemoveNotification(sbn), - mNotificationData.get(entry.key) != null); - return entry; - } - private void addNotificationInternal(StatusBarNotification notification, NotificationListenerService.RankingMap rankingMap) throws InflationException { String key = notification.getKey(); @@ -447,7 +332,14 @@ public class NotificationEntryManager implements mNotificationData.updateRanking(rankingMap); NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking(); rankingMap.getRanking(key, ranking); - NotificationData.Entry entry = createNotificationEntry(notification, ranking); + + NotificationEntry entry = new NotificationEntry(notification, ranking); + + Dependency.get(LeakDetector.class).trackInstance(entry); + // Construct the expanded view. + getRowBinder().inflateViews(entry, () -> performRemoveNotification(notification), + mNotificationData.get(entry.key) != null); + abortExistingInflation(key); mPendingNotifications.put(key, entry); @@ -456,19 +348,6 @@ public class NotificationEntryManager implements } } - @VisibleForTesting - void tagForeground(StatusBarNotification notification) { - ArraySet<Integer> activeOps = mForegroundServiceController.getAppOps( - notification.getUserId(), notification.getPackageName()); - if (activeOps != null) { - int N = activeOps.size(); - for (int i = 0; i < N; i++) { - updateNotificationsForAppOp(activeOps.valueAt(i), notification.getUid(), - notification.getPackageName(), true); - } - } - } - @Override public void addNotification(StatusBarNotification notification, NotificationListenerService.RankingMap ranking) { @@ -479,22 +358,13 @@ public class NotificationEntryManager implements } } - public void updateNotificationsForAppOp(int appOp, int uid, String pkg, boolean showIcon) { - String foregroundKey = mForegroundServiceController.getStandardLayoutKey( - UserHandle.getUserId(uid), pkg); - if (foregroundKey != null) { - mNotificationData.updateAppOp(appOp, uid, pkg, foregroundKey, showIcon); - updateNotifications(); - } - } - private void updateNotificationInternal(StatusBarNotification notification, NotificationListenerService.RankingMap ranking) throws InflationException { if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")"); final String key = notification.getKey(); abortExistingInflation(key); - NotificationData.Entry entry = mNotificationData.get(key); + NotificationEntry entry = mNotificationData.get(key); if (entry == null) { return; } @@ -510,14 +380,12 @@ public class NotificationEntryManager implements getRowBinder().inflateViews(entry, () -> performRemoveNotification(notification), mNotificationData.get(entry.key) != null); - updateNotifications(); - - if (!notification.isClearable()) { - // The user may have performed a dismiss action on the notification, since it's - // not clearable we should snap it back. - mListContainer.snapViewIfNeeded(entry); + for (NotificationEntryListener listener : mNotificationEntryListeners) { + listener.onPreEntryUpdated(entry); } + updateNotifications(); + if (DEBUG) { // Is this for you? boolean isForCurrentUser = Dependency.get(KeyguardEnvironment.class) @@ -526,10 +394,8 @@ public class NotificationEntryManager implements } for (NotificationEntryListener listener : mNotificationEntryListeners) { - listener.onEntryUpdated(entry); + listener.onPostEntryUpdated(entry); } - - maybeScheduleUpdateNotificationViews(entry); } @Override @@ -544,19 +410,20 @@ public class NotificationEntryManager implements public void updateNotifications() { mNotificationData.filterAndSort(); - - mPresenter.updateNotificationViews(); + if (mPresenter != null) { + mPresenter.updateNotificationViews(); + } } public void updateNotificationRanking(NotificationListenerService.RankingMap rankingMap) { - List<NotificationData.Entry> entries = new ArrayList<>(); + List<NotificationEntry> entries = new ArrayList<>(); entries.addAll(mNotificationData.getActiveNotifications()); entries.addAll(mPendingNotifications.values()); // Has a copy of the current UI adjustments. ArrayMap<String, NotificationUiAdjustment> oldAdjustments = new ArrayMap<>(); ArrayMap<String, Integer> oldImportances = new ArrayMap<>(); - for (NotificationData.Entry entry : entries) { + for (NotificationEntry entry : entries) { NotificationUiAdjustment adjustment = NotificationUiAdjustment.extractFromNotificationEntry(entry); oldAdjustments.put(entry.key, adjustment); @@ -568,7 +435,7 @@ public class NotificationEntryManager implements updateRankingOfPendingNotifications(rankingMap); // By comparing the old and new UI adjustments, reinflate the view accordingly. - for (NotificationData.Entry entry : entries) { + for (NotificationEntry entry : entries) { getRowBinder().onNotificationRankingUpdated( entry, oldImportances.get(entry.key), @@ -586,7 +453,7 @@ public class NotificationEntryManager implements return; } NotificationListenerService.Ranking tmpRanking = new NotificationListenerService.Ranking(); - for (NotificationData.Entry pendingNotification : mPendingNotifications.values()) { + for (NotificationEntry pendingNotification : mPendingNotifications.values()) { rankingMap.getRanking(pendingNotification.key, tmpRanking); pendingNotification.populateFromRanking(tmpRanking); } @@ -597,7 +464,7 @@ public class NotificationEntryManager implements * notifications whose views have not yet been inflated. In general, the system pretends like * these don't exist, although there are a couple exceptions. */ - public Iterable<NotificationData.Entry> getPendingNotificationsIterator() { + public Iterable<NotificationEntry> getPendingNotificationsIterator() { return mPendingNotifications.values(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java index 700382a103b2..154d7b356cd1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java @@ -28,6 +28,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dependency; import com.android.systemui.ForegroundServiceController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.notification.collection.NotificationData; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; @@ -82,7 +84,7 @@ public class NotificationFilter { /** * @return true if the provided notification should NOT be shown right now. */ - public boolean shouldFilterOut(NotificationData.Entry entry) { + public boolean shouldFilterOut(NotificationEntry entry) { final StatusBarNotification sbn = entry.notification; if (!(getEnvironment().isDeviceProvisioned() || showNotificationEvenIfUnprovisioned(sbn))) { @@ -132,6 +134,10 @@ public class NotificationFilter { } } + if (entry.isBubble() && !entry.showInShadeWhenBubble()) { + return true; + } + return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java index 8bd0e9ad3c8b..c50f10b55a71 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java @@ -37,6 +37,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dependency; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -134,12 +135,35 @@ public class NotificationInterruptionStateProvider { } /** + * Whether the notification should appear as a bubble with a fly-out on top of the screen. + * + * @param entry the entry to check + * @return true if the entry should bubble up, false otherwise + */ + public boolean shouldBubbleUp(NotificationEntry entry) { + StatusBarNotification sbn = entry.notification; + if (!entry.isBubble()) { + if (DEBUG) { + Log.d(TAG, "No bubble up: notification " + sbn.getKey() + + " is bubble? " + entry.isBubble()); + } + return false; + } + + if (!canHeadsUpCommon(entry)) { + return false; + } + + return true; + } + + /** * Whether the notification should peek in from the top and alert the user. * * @param entry the entry to check * @return true if the entry should heads up, false otherwise */ - public boolean shouldHeadsUp(NotificationData.Entry entry) { + public boolean shouldHeadsUp(NotificationEntry entry) { StatusBarNotification sbn = entry.notification; if (getShadeController().isDozing()) { @@ -149,10 +173,12 @@ public class NotificationInterruptionStateProvider { return false; } - // TODO: need to changes this, e.g. should still heads up in expanded shade, might want - // message bubble from the bubble to go through heads up path boolean inShade = mStatusBarStateController.getState() == SHADE; - if (entry.isBubble() && !entry.isBubbleDismissed() && inShade) { + if (entry.isBubble() && inShade) { + if (DEBUG) { + Log.d(TAG, "No heads up: in unlocked shade where notification is shown as a " + + "bubble: " + sbn.getKey()); + } return false; } @@ -163,9 +189,13 @@ public class NotificationInterruptionStateProvider { return false; } - if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) { + if (!canHeadsUpCommon(entry)) { + return false; + } + + if (entry.importance < NotificationManager.IMPORTANCE_HIGH) { if (DEBUG) { - Log.d(TAG, "No heads up: no huns or vr mode"); + Log.d(TAG, "No heads up: unimportant notification: " + sbn.getKey()); } return false; } @@ -185,34 +215,6 @@ public class NotificationInterruptionStateProvider { return false; } - if (entry.shouldSuppressPeek()) { - if (DEBUG) { - Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey()); - } - return false; - } - - if (isSnoozedPackage(sbn)) { - if (DEBUG) { - Log.d(TAG, "No heads up: snoozed package: " + sbn.getKey()); - } - return false; - } - - if (entry.hasJustLaunchedFullScreenIntent()) { - if (DEBUG) { - Log.d(TAG, "No heads up: recent fullscreen: " + sbn.getKey()); - } - return false; - } - - if (entry.importance < NotificationManager.IMPORTANCE_HIGH) { - if (DEBUG) { - Log.d(TAG, "No heads up: unimportant notification: " + sbn.getKey()); - } - return false; - } - if (!mHeadsUpSuppressor.canHeadsUp(entry, sbn)) { return false; } @@ -227,7 +229,7 @@ public class NotificationInterruptionStateProvider { * @param entry the entry to check * @return true if the entry should ambient pulse, false otherwise */ - public boolean shouldPulse(NotificationData.Entry entry) { + public boolean shouldPulse(NotificationEntry entry) { StatusBarNotification sbn = entry.notification; if (!getShadeController().isDozing()) { @@ -273,14 +275,14 @@ public class NotificationInterruptionStateProvider { /** * Common checks between heads up alerting and ambient pulse alerting. See - * {@link #shouldHeadsUp(NotificationData.Entry)} and - * {@link #shouldPulse(NotificationData.Entry)}. Notifications that fail any of these checks + * {@link #shouldHeadsUp(NotificationEntry)} and + * {@link #shouldPulse(NotificationEntry)}. Notifications that fail any of these checks * should not alert at all. * * @param entry the entry to check * @return true if these checks pass, false if the notification should not alert */ - protected boolean canAlertCommon(NotificationData.Entry entry) { + protected boolean canAlertCommon(NotificationEntry entry) { StatusBarNotification sbn = entry.notification; if (mNotificationFilter.shouldFilterOut(entry)) { @@ -301,6 +303,49 @@ public class NotificationInterruptionStateProvider { return true; } + /** + * Common checks between heads up alerting and bubble fly out alerting. See + * {@link #shouldHeadsUp(NotificationEntry)} and + * {@link #shouldBubbleUp(NotificationEntry)}. Notifications that fail any of these + * checks should not interrupt the user on screen. + * + * @param entry the entry to check + * @return true if these checks pass, false if the notification should not interrupt on screen + */ + public boolean canHeadsUpCommon(NotificationEntry entry) { + StatusBarNotification sbn = entry.notification; + + if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) { + if (DEBUG) { + Log.d(TAG, "No heads up: no huns or vr mode"); + } + return false; + } + + if (entry.shouldSuppressPeek()) { + if (DEBUG) { + Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey()); + } + return false; + } + + if (isSnoozedPackage(sbn)) { + if (DEBUG) { + Log.d(TAG, "No heads up: snoozed package: " + sbn.getKey()); + } + return false; + } + + if (entry.hasJustLaunchedFullScreenIntent()) { + if (DEBUG) { + Log.d(TAG, "No heads up: recent fullscreen: " + sbn.getKey()); + } + return false; + } + + return true; + } + private boolean isSnoozedPackage(StatusBarNotification sbn) { return mHeadsUpManager.isSnoozed(sbn.getPackageName()); } @@ -325,7 +370,7 @@ public class NotificationInterruptionStateProvider { * @param sbn notification that might be heads upped * @return false if the notification can not be heads upped */ - boolean canHeadsUp(NotificationData.Entry entry, StatusBarNotification sbn); + boolean canHeadsUp(NotificationEntry entry, StatusBarNotification sbn); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java new file mode 100644 index 000000000000..88f4ca239af4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2019 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.notification; + +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.os.UserHandle; +import android.service.notification.StatusBarNotification; +import android.util.ArraySet; + +import com.android.internal.statusbar.NotificationVisibility; +import com.android.systemui.ForegroundServiceController; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.stack.NotificationListContainer; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; + +/** + * Root controller for the list of notifications in the shade. + * + * TODO: Much of the code in NotificationPresenter should eventually move in here. It will proxy + * domain-specific behavior (ARC, etc) to subcontrollers. + */ +public class NotificationListController { + private final NotificationEntryManager mEntryManager; + private final NotificationListContainer mListContainer; + private final ForegroundServiceController mForegroundServiceController; + private final DeviceProvisionedController mDeviceProvisionedController; + + public NotificationListController( + NotificationEntryManager entryManager, + NotificationListContainer listContainer, + ForegroundServiceController foregroundServiceController, + DeviceProvisionedController deviceProvisionedController) { + mEntryManager = checkNotNull(entryManager); + mListContainer = checkNotNull(listContainer); + mForegroundServiceController = checkNotNull(foregroundServiceController); + mDeviceProvisionedController = checkNotNull(deviceProvisionedController); + } + + /** + * Causes the controller to register listeners on its dependencies. This method must be called + * before the controller is ready to perform its duties. + */ + public void bind() { + mEntryManager.addNotificationEntryListener(mEntryListener); + mDeviceProvisionedController.addCallback(mDeviceProvisionedListener); + } + + /** Should be called when the list controller is being destroyed. */ + public void destroy() { + mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener); + } + + @SuppressWarnings("FieldCanBeLocal") + private final NotificationEntryListener mEntryListener = new NotificationEntryListener() { + @Override + public void onEntryRemoved( + NotificationEntry entry, + NotificationVisibility visibility, + boolean removedByUser) { + mListContainer.cleanUpViewStateForEntry(entry); + } + + @Override + public void onBeforeNotificationAdded(NotificationEntry entry) { + tagForeground(entry.notification); + } + }; + + private final DeviceProvisionedListener mDeviceProvisionedListener = + new DeviceProvisionedListener() { + @Override + public void onDeviceProvisionedChanged() { + mEntryManager.updateNotifications(); + } + }; + + // TODO: This method is horrifically inefficient + private void tagForeground(StatusBarNotification notification) { + ArraySet<Integer> activeOps = + mForegroundServiceController.getAppOps( + notification.getUserId(), notification.getPackageName()); + if (activeOps != null) { + int len = activeOps.size(); + for (int i = 0; i < len; i++) { + updateNotificationsForAppOp(activeOps.valueAt(i), notification.getUid(), + notification.getPackageName(), true); + } + } + } + + /** When an app op changes, propagate that change to notifications. */ + public void updateNotificationsForAppOp(int appOp, int uid, String pkg, boolean showIcon) { + String foregroundKey = + mForegroundServiceController.getStandardLayoutKey(UserHandle.getUserId(uid), pkg); + if (foregroundKey != null) { + mEntryManager + .getNotificationData().updateAppOp(appOp, uid, pkg, foregroundKey, showIcon); + mEntryManager.updateNotifications(); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java index 058efca51b21..0b8596f9cba7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java @@ -27,13 +27,10 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.Build; -import android.os.RemoteException; -import android.os.ServiceManager; import android.service.notification.StatusBarNotification; import android.util.Log; import android.view.ViewGroup; -import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.NotificationMessagingUtil; import com.android.systemui.Dependency; import com.android.systemui.R; @@ -42,6 +39,8 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationUiAdjustment; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.NotificationInflater; @@ -70,7 +69,6 @@ public class NotificationRowBinder { Dependency.get(NotificationInterruptionStateProvider.class); private final Context mContext; - private final IStatusBarService mBarService; private final NotificationMessagingUtil mMessagingUtil; private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger = this::logNotificationExpansion; @@ -84,6 +82,7 @@ public class NotificationRowBinder { private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener; private BindRowCallback mBindRowCallback; private NotificationClicker mNotificationClicker; + private final NotificationLogger mNotificationLogger = Dependency.get(NotificationLogger.class); @Inject public NotificationRowBinder(Context context, @@ -91,8 +90,6 @@ public class NotificationRowBinder { mContext = context; mMessagingUtil = new NotificationMessagingUtil(context); mAllowLongPress = allowLongPress; - mBarService = IStatusBarService.Stub.asInterface( - ServiceManager.getService(Context.STATUS_BAR_SERVICE)); } private NotificationRemoteInputManager getRemoteInputManager() { @@ -126,7 +123,7 @@ public class NotificationRowBinder { * Inflates the views for the given entry (possibly asynchronously). */ public void inflateViews( - NotificationData.Entry entry, + NotificationEntry entry, Runnable onDismissRunnable, boolean isUpdate) throws InflationException { @@ -149,7 +146,7 @@ public class NotificationRowBinder { } } - private void bindRow(NotificationData.Entry entry, PackageManager pmUser, + private void bindRow(NotificationEntry entry, PackageManager pmUser, StatusBarNotification sbn, ExpandableNotificationRow row, Runnable onDismissRunnable) { row.setExpansionLogger(mExpansionLogger, entry.notification.getKey()); @@ -197,7 +194,7 @@ public class NotificationRowBinder { * reinflating them. */ public void onNotificationRankingUpdated( - NotificationData.Entry entry, + NotificationEntry entry, @Nullable Integer oldImportance, NotificationUiAdjustment oldAdjustment, NotificationUiAdjustment newAdjustment, @@ -224,7 +221,7 @@ public class NotificationRowBinder { //TODO: This method associates a row with an entry, but eventually needs to not do that private void updateNotification( - NotificationData.Entry entry, + NotificationEntry entry, PackageManager pmUser, StatusBarNotification sbn, ExpandableNotificationRow row, @@ -273,13 +270,7 @@ public class NotificationRowBinder { } private void logNotificationExpansion(String key, boolean userAction, boolean expanded) { - mUiOffloadThread.submit(() -> { - try { - mBarService.onNotificationExpansionChanged(key, userAction, expanded); - } catch (RemoteException e) { - // Ignore. - } - }); + mNotificationLogger.onExpansionChanged(key, userAction, expanded); } /** Callback for when a row is bound to an entry. */ @@ -292,7 +283,7 @@ public class NotificationRowBinder { * @param sbn notification * @param row row for the notification */ - void onBindRow(NotificationData.Entry entry, PackageManager pmUser, + void onBindRow(NotificationEntry entry, PackageManager pmUser, StatusBarNotification sbn, ExpandableNotificationRow row); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisibilityLocationProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisibilityLocationProvider.java index a194eef39b6d..f09c57d786ef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisibilityLocationProvider.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisibilityLocationProvider.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; + /** * An object that can determine the visibility of a Notification. */ @@ -27,5 +29,5 @@ public interface VisibilityLocationProvider { * @param entry * @return true if row is in a visible location */ - boolean isInVisibleLocation(NotificationData.Entry entry); + boolean isInVisibleLocation(NotificationEntry entry); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java index 8e6a93deec69..c886685424f7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java @@ -21,6 +21,7 @@ import android.view.View; import androidx.collection.ArraySet; import com.android.systemui.statusbar.NotificationPresenter; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; @@ -52,7 +53,7 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener { public VisualStabilityManager(NotificationEntryManager notificationEntryManager) { notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() { @Override - public void onEntryReinflated(NotificationData.Entry entry) { + public void onEntryReinflated(NotificationEntry entry) { if (entry.hasLowPriorityStateUpdated()) { onLowPriorityUpdated(entry); if (mPresenter != null) { @@ -163,7 +164,7 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener { } @Override - public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) { + public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) { if (isHeadsUp) { // Heads up notifications should in general be allowed to reorder if they are out of // view and stay at the current location if they aren't. @@ -171,7 +172,7 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener { } } - private void onLowPriorityUpdated(NotificationData.Entry entry) { + private void onLowPriorityUpdated(NotificationEntry entry) { mLowPriorityReorderingViews.add(entry.getRow()); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java new file mode 100644 index 000000000000..8c29fb50f00a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java @@ -0,0 +1,424 @@ +/* + * Copyright (C) 2019 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.notification.collection; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.service.notification.NotificationListenerService.Ranking; +import android.service.notification.NotificationListenerService.RankingMap; +import android.service.notification.SnoozeCriterion; +import android.service.notification.StatusBarNotification; +import android.util.ArrayMap; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.Dependency; +import com.android.systemui.statusbar.NotificationMediaManager; +import com.android.systemui.statusbar.notification.NotificationFilter; +import com.android.systemui.statusbar.phone.NotificationGroupManager; +import com.android.systemui.statusbar.policy.HeadsUpManager; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; + +/** + * The list of currently displaying notifications. + */ +public class NotificationData { + + private final NotificationFilter mNotificationFilter = Dependency.get(NotificationFilter.class); + + /** + * These dependencies are late init-ed + */ + private KeyguardEnvironment mEnvironment; + private NotificationMediaManager mMediaManager; + + private HeadsUpManager mHeadsUpManager; + + private final ArrayMap<String, NotificationEntry> mEntries = new ArrayMap<>(); + private final ArrayList<NotificationEntry> mSortedAndFiltered = new ArrayList<>(); + private final ArrayList<NotificationEntry> mFilteredForUser = new ArrayList<>(); + + private final NotificationGroupManager mGroupManager = + Dependency.get(NotificationGroupManager.class); + + private RankingMap mRankingMap; + private final Ranking mTmpRanking = new Ranking(); + + public void setHeadsUpManager(HeadsUpManager headsUpManager) { + mHeadsUpManager = headsUpManager; + } + + private final Comparator<NotificationEntry> mRankingComparator = + new Comparator<NotificationEntry>() { + private final Ranking mRankingA = new Ranking(); + private final Ranking mRankingB = new Ranking(); + + @Override + public int compare(NotificationEntry a, NotificationEntry b) { + final StatusBarNotification na = a.notification; + final StatusBarNotification nb = b.notification; + int aImportance = NotificationManager.IMPORTANCE_DEFAULT; + int bImportance = NotificationManager.IMPORTANCE_DEFAULT; + int aRank = 0; + int bRank = 0; + + if (mRankingMap != null) { + // RankingMap as received from NoMan + getRanking(a.key, mRankingA); + getRanking(b.key, mRankingB); + aImportance = mRankingA.getImportance(); + bImportance = mRankingB.getImportance(); + aRank = mRankingA.getRank(); + bRank = mRankingB.getRank(); + } + + String mediaNotification = getMediaManager().getMediaNotificationKey(); + + // IMPORTANCE_MIN media streams are allowed to drift to the bottom + final boolean aMedia = a.key.equals(mediaNotification) + && aImportance > NotificationManager.IMPORTANCE_MIN; + final boolean bMedia = b.key.equals(mediaNotification) + && bImportance > NotificationManager.IMPORTANCE_MIN; + + boolean aSystemMax = aImportance >= NotificationManager.IMPORTANCE_HIGH + && isSystemNotification(na); + boolean bSystemMax = bImportance >= NotificationManager.IMPORTANCE_HIGH + && isSystemNotification(nb); + + boolean isHeadsUp = a.getRow().isHeadsUp(); + if (isHeadsUp != b.getRow().isHeadsUp()) { + return isHeadsUp ? -1 : 1; + } else if (isHeadsUp) { + // Provide consistent ranking with headsUpManager + return mHeadsUpManager.compare(a, b); + } else if (a.getRow().isAmbientPulsing() != b.getRow().isAmbientPulsing()) { + return a.getRow().isAmbientPulsing() ? -1 : 1; + } else if (aMedia != bMedia) { + // Upsort current media notification. + return aMedia ? -1 : 1; + } else if (aSystemMax != bSystemMax) { + // Upsort PRIORITY_MAX system notifications + return aSystemMax ? -1 : 1; + } else if (aRank != bRank) { + return aRank - bRank; + } else { + return Long.compare(nb.getNotification().when, na.getNotification().when); + } + } + }; + + private KeyguardEnvironment getEnvironment() { + if (mEnvironment == null) { + mEnvironment = Dependency.get(KeyguardEnvironment.class); + } + return mEnvironment; + } + + private NotificationMediaManager getMediaManager() { + if (mMediaManager == null) { + mMediaManager = Dependency.get(NotificationMediaManager.class); + } + return mMediaManager; + } + + /** + * Returns the sorted list of active notifications (depending on {@link KeyguardEnvironment} + * + * <p> + * This call doesn't update the list of active notifications. Call {@link #filterAndSort()} + * when the environment changes. + * <p> + * Don't hold on to or modify the returned list. + */ + public ArrayList<NotificationEntry> getActiveNotifications() { + return mSortedAndFiltered; + } + + public ArrayList<NotificationEntry> getNotificationsForCurrentUser() { + mFilteredForUser.clear(); + + synchronized (mEntries) { + final int len = mEntries.size(); + for (int i = 0; i < len; i++) { + NotificationEntry entry = mEntries.valueAt(i); + final StatusBarNotification sbn = entry.notification; + if (!getEnvironment().isNotificationForCurrentProfiles(sbn)) { + continue; + } + mFilteredForUser.add(entry); + } + } + return mFilteredForUser; + } + + public NotificationEntry get(String key) { + return mEntries.get(key); + } + + public void add(NotificationEntry entry) { + synchronized (mEntries) { + mEntries.put(entry.notification.getKey(), entry); + } + mGroupManager.onEntryAdded(entry); + + updateRankingAndSort(mRankingMap); + } + + public NotificationEntry remove(String key, RankingMap ranking) { + NotificationEntry removed; + synchronized (mEntries) { + removed = mEntries.remove(key); + } + if (removed == null) return null; + mGroupManager.onEntryRemoved(removed); + updateRankingAndSort(ranking); + return removed; + } + + /** Updates the given notification entry with the provided ranking. */ + public void update( + NotificationEntry entry, + RankingMap ranking, + StatusBarNotification notification) { + updateRanking(ranking); + final StatusBarNotification oldNotification = entry.notification; + entry.notification = notification; + mGroupManager.onEntryUpdated(entry, oldNotification); + } + + public void updateRanking(RankingMap ranking) { + updateRankingAndSort(ranking); + } + + public void updateAppOp(int appOp, int uid, String pkg, String key, boolean showIcon) { + synchronized (mEntries) { + final int len = mEntries.size(); + for (int i = 0; i < len; i++) { + NotificationEntry entry = mEntries.valueAt(i); + if (uid == entry.notification.getUid() + && pkg.equals(entry.notification.getPackageName()) + && key.equals(entry.key)) { + if (showIcon) { + entry.mActiveAppOps.add(appOp); + } else { + entry.mActiveAppOps.remove(appOp); + } + } + } + } + } + + /** + * Returns true if this notification should be displayed in the high-priority notifications + * section (and on the lockscreen and status bar). + */ + public boolean isHighPriority(StatusBarNotification statusBarNotification) { + if (mRankingMap != null) { + getRanking(statusBarNotification.getKey(), mTmpRanking); + if (mTmpRanking.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT + || statusBarNotification.getNotification().isForegroundService() + || statusBarNotification.getNotification().hasMediaSession()) { + return true; + } + if (mGroupManager.isSummaryOfGroup(statusBarNotification)) { + final ArrayList<NotificationEntry> logicalChildren = + mGroupManager.getLogicalChildren(statusBarNotification); + for (NotificationEntry child : logicalChildren) { + if (isHighPriority(child.notification)) { + return true; + } + } + } + } + return false; + } + + public boolean isAmbient(String key) { + if (mRankingMap != null) { + getRanking(key, mTmpRanking); + return mTmpRanking.isAmbient(); + } + return false; + } + + public int getVisibilityOverride(String key) { + if (mRankingMap != null) { + getRanking(key, mTmpRanking); + return mTmpRanking.getVisibilityOverride(); + } + return Ranking.VISIBILITY_NO_OVERRIDE; + } + + public int getImportance(String key) { + if (mRankingMap != null) { + getRanking(key, mTmpRanking); + return mTmpRanking.getImportance(); + } + return NotificationManager.IMPORTANCE_UNSPECIFIED; + } + + public String getOverrideGroupKey(String key) { + if (mRankingMap != null) { + getRanking(key, mTmpRanking); + return mTmpRanking.getOverrideGroupKey(); + } + return null; + } + + public List<SnoozeCriterion> getSnoozeCriteria(String key) { + if (mRankingMap != null) { + getRanking(key, mTmpRanking); + return mTmpRanking.getSnoozeCriteria(); + } + return null; + } + + public NotificationChannel getChannel(String key) { + if (mRankingMap != null) { + getRanking(key, mTmpRanking); + return mTmpRanking.getChannel(); + } + return null; + } + + public int getRank(String key) { + if (mRankingMap != null) { + getRanking(key, mTmpRanking); + return mTmpRanking.getRank(); + } + return 0; + } + + public boolean shouldHide(String key) { + if (mRankingMap != null) { + getRanking(key, mTmpRanking); + return mTmpRanking.isSuspended(); + } + return false; + } + + private void updateRankingAndSort(RankingMap ranking) { + if (ranking != null) { + mRankingMap = ranking; + synchronized (mEntries) { + final int len = mEntries.size(); + for (int i = 0; i < len; i++) { + NotificationEntry entry = mEntries.valueAt(i); + if (!getRanking(entry.key, mTmpRanking)) { + continue; + } + final StatusBarNotification oldSbn = entry.notification.cloneLight(); + final String overrideGroupKey = getOverrideGroupKey(entry.key); + if (!Objects.equals(oldSbn.getOverrideGroupKey(), overrideGroupKey)) { + entry.notification.setOverrideGroupKey(overrideGroupKey); + mGroupManager.onEntryUpdated(entry, oldSbn); + } + entry.populateFromRanking(mTmpRanking); + } + } + } + filterAndSort(); + } + + /** + * Get the ranking from the current ranking map. + * + * @param key the key to look up + * @param outRanking the ranking to populate + * + * @return {@code true} if the ranking was properly obtained. + */ + @VisibleForTesting + protected boolean getRanking(String key, Ranking outRanking) { + return mRankingMap.getRanking(key, outRanking); + } + + // TODO: This should not be public. Instead the Environment should notify this class when + // anything changed, and this class should call back the UI so it updates itself. + public void filterAndSort() { + mSortedAndFiltered.clear(); + + synchronized (mEntries) { + final int len = mEntries.size(); + for (int i = 0; i < len; i++) { + NotificationEntry entry = mEntries.valueAt(i); + + if (mNotificationFilter.shouldFilterOut(entry)) { + continue; + } + + mSortedAndFiltered.add(entry); + } + } + + Collections.sort(mSortedAndFiltered, mRankingComparator); + } + + public void dump(PrintWriter pw, String indent) { + int filteredLen = mSortedAndFiltered.size(); + pw.print(indent); + pw.println("active notifications: " + filteredLen); + int active; + for (active = 0; active < filteredLen; active++) { + NotificationEntry e = mSortedAndFiltered.get(active); + dumpEntry(pw, indent, active, e); + } + synchronized (mEntries) { + int totalLen = mEntries.size(); + pw.print(indent); + pw.println("inactive notifications: " + (totalLen - active)); + int inactiveCount = 0; + for (int i = 0; i < totalLen; i++) { + NotificationEntry entry = mEntries.valueAt(i); + if (!mSortedAndFiltered.contains(entry)) { + dumpEntry(pw, indent, inactiveCount, entry); + inactiveCount++; + } + } + } + } + + private void dumpEntry(PrintWriter pw, String indent, int i, NotificationEntry e) { + getRanking(e.key, mTmpRanking); + pw.print(indent); + pw.println(" [" + i + "] key=" + e.key + " icon=" + e.icon); + StatusBarNotification n = e.notification; + pw.print(indent); + pw.println(" pkg=" + n.getPackageName() + " id=" + n.getId() + " importance=" + + mTmpRanking.getImportance()); + pw.print(indent); + pw.println(" notification=" + n.getNotification()); + } + + private static boolean isSystemNotification(StatusBarNotification sbn) { + String sbnPackage = sbn.getPackageName(); + return "android".equals(sbnPackage) || "com.android.systemui".equals(sbnPackage); + } + + /** + * Provides access to keyguard state and user settings dependent data. + */ + public interface KeyguardEnvironment { + boolean isDeviceProvisioned(); + boolean isNotificationForCurrentProfiles(StatusBarNotification sbn); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java new file mode 100644 index 000000000000..ee551ee96e7b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -0,0 +1,726 @@ +/* + * Copyright (C) 2019 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.notification.collection; + +import static android.app.Notification.CATEGORY_ALARM; +import static android.app.Notification.CATEGORY_CALL; +import static android.app.Notification.CATEGORY_EVENT; +import static android.app.Notification.CATEGORY_MESSAGE; +import static android.app.Notification.CATEGORY_REMINDER; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; + +import android.annotation.NonNull; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager.Policy; +import android.app.Person; +import android.content.Context; +import android.graphics.drawable.Icon; +import android.os.Bundle; +import android.os.Parcelable; +import android.os.SystemClock; +import android.service.notification.NotificationListenerService; +import android.service.notification.SnoozeCriterion; +import android.service.notification.StatusBarNotification; +import android.util.ArraySet; +import android.view.View; +import android.widget.ImageView; + +import androidx.annotation.Nullable; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.statusbar.StatusBarIcon; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.ContrastColorUtil; +import com.android.systemui.statusbar.InflationTask; +import com.android.systemui.statusbar.StatusBarIconView; +import com.android.systemui.statusbar.notification.InflationException; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationGuts; +import com.android.systemui.statusbar.notification.row.NotificationInflater; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * Represents a notification that the system UI knows about + * + * Whenever the NotificationManager tells us about the existence of a new notification, we wrap it + * in a NotificationEntry. Thus, every notification has an associated NotificationEntry, even if + * that notification is never displayed to the user (for example, if it's filtered out for some + * reason). + * + * Entries store information about the current state of the notification. Essentially: + * anything that needs to persist or be modifiable even when the notification's views don't + * exist. Any other state should be stored on the views/view controllers themselves. + * + * At the moment, there are many things here that shouldn't be and vice-versa. Hopefully we can + * clean this up in the future. + */ +public final class NotificationEntry { + private static final long LAUNCH_COOLDOWN = 2000; + private static final long REMOTE_INPUT_COOLDOWN = 500; + private static final long INITIALIZATION_DELAY = 400; + private static final long NOT_LAUNCHED_YET = -LAUNCH_COOLDOWN; + private static final int COLOR_INVALID = 1; + public final String key; + public StatusBarNotification notification; + public NotificationChannel channel; + public long lastAudiblyAlertedMs; + public boolean noisy; + public boolean ambient; + public int importance; + public StatusBarIconView icon; + public StatusBarIconView expandedIcon; + private boolean interruption; + public boolean autoRedacted; // whether the redacted notification was generated by us + public int targetSdk; + private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET; + public CharSequence remoteInputText; + public List<SnoozeCriterion> snoozeCriteria; + public int userSentiment = NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; + /** Smart Actions provided by the NotificationAssistantService. */ + @NonNull + public List<Notification.Action> systemGeneratedSmartActions = Collections.emptyList(); + public CharSequence[] smartReplies = new CharSequence[0]; + @VisibleForTesting + public int suppressedVisualEffects; + public boolean suspended; + + private NotificationEntry parent; // our parent (if we're in a group) + private ArrayList<NotificationEntry> children = new ArrayList<NotificationEntry>(); + private ExpandableNotificationRow row; // the outer expanded view + + private int mCachedContrastColor = COLOR_INVALID; + private int mCachedContrastColorIsFor = COLOR_INVALID; + private InflationTask mRunningTask = null; + private Throwable mDebugThrowable; + public CharSequence remoteInputTextWhenReset; + public long lastRemoteInputSent = NOT_LAUNCHED_YET; + public ArraySet<Integer> mActiveAppOps = new ArraySet<>(3); + public CharSequence headsUpStatusBarText; + public CharSequence headsUpStatusBarTextPublic; + + private long initializationTime = -1; + + /** + * Whether or not this row represents a system notification. Note that if this is + * {@code null}, that means we were either unable to retrieve the info or have yet to + * retrieve the info. + */ + public Boolean mIsSystemNotification; + + /** + * Has the user sent a reply through this Notification. + */ + private boolean hasSentReply; + + /** + * Whether this notification should be displayed as a bubble. + */ + private boolean mIsBubble; + + /** + * Whether this notification should be shown in the shade when it is also displayed as a bubble. + * + * <p>When a notification is a bubble we don't show it in the shade once the bubble has been + * expanded</p> + */ + private boolean mShowInShadeWhenBubble; + + /** + * Whether the user has dismissed this notification when it was in bubble form. + */ + private boolean mUserDismissedBubble; + + public NotificationEntry(StatusBarNotification n) { + this(n, null); + } + + public NotificationEntry( + StatusBarNotification n, + @Nullable NotificationListenerService.Ranking ranking) { + this.key = n.getKey(); + this.notification = n; + if (ranking != null) { + populateFromRanking(ranking); + } + } + + public void populateFromRanking(@NonNull NotificationListenerService.Ranking ranking) { + channel = ranking.getChannel(); + lastAudiblyAlertedMs = ranking.getLastAudiblyAlertedMillis(); + importance = ranking.getImportance(); + ambient = ranking.isAmbient(); + snoozeCriteria = ranking.getSnoozeCriteria(); + userSentiment = ranking.getUserSentiment(); + systemGeneratedSmartActions = ranking.getSmartActions() == null + ? Collections.emptyList() : ranking.getSmartActions(); + smartReplies = ranking.getSmartReplies() == null + ? new CharSequence[0] + : ranking.getSmartReplies().toArray(new CharSequence[0]); + suppressedVisualEffects = ranking.getSuppressedVisualEffects(); + suspended = ranking.isSuspended(); + } + + public void setInterruption() { + interruption = true; + } + + public boolean hasInterrupted() { + return interruption; + } + + public void setIsBubble(boolean bubbleable) { + mIsBubble = bubbleable; + } + + public boolean isBubble() { + return mIsBubble; + } + + public void setBubbleDismissed(boolean userDismissed) { + mUserDismissedBubble = userDismissed; + } + + public boolean isBubbleDismissed() { + return mUserDismissedBubble; + } + + /** + * Sets whether this notification should be shown in the shade when it is also displayed as a + * bubble. + */ + public void setShowInShadeWhenBubble(boolean showInShade) { + mShowInShadeWhenBubble = showInShade; + } + + /** + * Whether this notification should be shown in the shade when it is also displayed as a + * bubble. + */ + public boolean showInShadeWhenBubble() { + // We always show it in the shade if non-clearable + return !isClearable() || mShowInShadeWhenBubble; + } + + /** + * Resets the notification entry to be re-used. + */ + public void reset() { + if (row != null) { + row.reset(); + } + } + + public ExpandableNotificationRow getRow() { + return row; + } + + //TODO: This will go away when we have a way to bind an entry to a row + public void setRow(ExpandableNotificationRow row) { + this.row = row; + } + + @Nullable + public List<NotificationEntry> getChildren() { + if (children.size() <= 0) { + return null; + } + + return children; + } + + public void notifyFullScreenIntentLaunched() { + setInterruption(); + lastFullScreenIntentLaunchTime = SystemClock.elapsedRealtime(); + } + + public boolean hasJustLaunchedFullScreenIntent() { + return SystemClock.elapsedRealtime() < lastFullScreenIntentLaunchTime + LAUNCH_COOLDOWN; + } + + public boolean hasJustSentRemoteInput() { + return SystemClock.elapsedRealtime() < lastRemoteInputSent + REMOTE_INPUT_COOLDOWN; + } + + public boolean hasFinishedInitialization() { + return initializationTime == -1 + || SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY; + } + + /** + * Create the icons for a notification + * @param context the context to create the icons with + * @param sbn the notification + * @throws InflationException Exception if required icons are not valid or specified + */ + public void createIcons(Context context, StatusBarNotification sbn) + throws InflationException { + Notification n = sbn.getNotification(); + final Icon smallIcon = n.getSmallIcon(); + if (smallIcon == null) { + throw new InflationException("No small icon in notification from " + + sbn.getPackageName()); + } + + // Construct the icon. + icon = new StatusBarIconView(context, + sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn); + icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE); + + // Construct the expanded icon. + expandedIcon = new StatusBarIconView(context, + sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn); + expandedIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE); + final StatusBarIcon ic = new StatusBarIcon( + sbn.getUser(), + sbn.getPackageName(), + smallIcon, + n.iconLevel, + n.number, + StatusBarIconView.contentDescForNotification(context, n)); + if (!icon.set(ic) || !expandedIcon.set(ic)) { + icon = null; + expandedIcon = null; + throw new InflationException("Couldn't create icon: " + ic); + } + expandedIcon.setVisibility(View.INVISIBLE); + expandedIcon.setOnVisibilityChangedListener( + newVisibility -> { + if (row != null) { + row.setIconsVisible(newVisibility != View.VISIBLE); + } + }); + } + + public void setIconTag(int key, Object tag) { + if (icon != null) { + icon.setTag(key, tag); + expandedIcon.setTag(key, tag); + } + } + + /** + * Update the notification icons. + * + * @param context the context to create the icons with. + * @param sbn the notification to read the icon from. + * @throws InflationException Exception if required icons are not valid or specified + */ + public void updateIcons(Context context, StatusBarNotification sbn) + throws InflationException { + if (icon != null) { + // Update the icon + Notification n = sbn.getNotification(); + final StatusBarIcon ic = new StatusBarIcon( + notification.getUser(), + notification.getPackageName(), + n.getSmallIcon(), + n.iconLevel, + n.number, + StatusBarIconView.contentDescForNotification(context, n)); + icon.setNotification(sbn); + expandedIcon.setNotification(sbn); + if (!icon.set(ic) || !expandedIcon.set(ic)) { + throw new InflationException("Couldn't update icon: " + ic); + } + } + } + + public int getContrastedColor(Context context, boolean isLowPriority, + int backgroundColor) { + int rawColor = isLowPriority ? Notification.COLOR_DEFAULT : + notification.getNotification().color; + if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) { + return mCachedContrastColor; + } + final int contrasted = ContrastColorUtil.resolveContrastColor(context, rawColor, + backgroundColor); + mCachedContrastColorIsFor = rawColor; + mCachedContrastColor = contrasted; + return mCachedContrastColor; + } + + /** + * Abort all existing inflation tasks + */ + public void abortTask() { + if (mRunningTask != null) { + mRunningTask.abort(); + mRunningTask = null; + } + } + + public void setInflationTask(InflationTask abortableTask) { + // abort any existing inflation + InflationTask existing = mRunningTask; + abortTask(); + mRunningTask = abortableTask; + if (existing != null && mRunningTask != null) { + mRunningTask.supersedeTask(existing); + } + } + + public void onInflationTaskFinished() { + mRunningTask = null; + } + + @VisibleForTesting + public InflationTask getRunningTask() { + return mRunningTask; + } + + /** + * Set a throwable that is used for debugging + * + * @param debugThrowable the throwable to save + */ + public void setDebugThrowable(Throwable debugThrowable) { + mDebugThrowable = debugThrowable; + } + + public Throwable getDebugThrowable() { + return mDebugThrowable; + } + + public void onRemoteInputInserted() { + lastRemoteInputSent = NOT_LAUNCHED_YET; + remoteInputTextWhenReset = null; + } + + public void setHasSentReply() { + hasSentReply = true; + } + + public boolean isLastMessageFromReply() { + if (!hasSentReply) { + return false; + } + Bundle extras = notification.getNotification().extras; + CharSequence[] replyTexts = extras.getCharSequenceArray( + Notification.EXTRA_REMOTE_INPUT_HISTORY); + if (!ArrayUtils.isEmpty(replyTexts)) { + return true; + } + Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES); + if (messages != null && messages.length > 0) { + Parcelable message = messages[messages.length - 1]; + if (message instanceof Bundle) { + Notification.MessagingStyle.Message lastMessage = + Notification.MessagingStyle.Message.getMessageFromBundle( + (Bundle) message); + if (lastMessage != null) { + Person senderPerson = lastMessage.getSenderPerson(); + if (senderPerson == null) { + return true; + } + Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON); + return Objects.equals(user, senderPerson); + } + } + } + return false; + } + + public void setInitializationTime(long time) { + if (initializationTime == -1) { + initializationTime = time; + } + } + + public void sendAccessibilityEvent(int eventType) { + if (row != null) { + row.sendAccessibilityEvent(eventType); + } + } + + /** + * Used by NotificationMediaManager to determine... things + * @return {@code true} if we are a media notification + */ + public boolean isMediaNotification() { + if (row == null) return false; + + return row.isMediaRow(); + } + + /** + * We are a top level child if our parent is the list of notifications duh + * @return {@code true} if we're a top level notification + */ + public boolean isTopLevelChild() { + return row != null && row.isTopLevelChild(); + } + + public void resetUserExpansion() { + if (row != null) row.resetUserExpansion(); + } + + public void freeContentViewWhenSafe(@NotificationInflater.InflationFlag int inflationFlag) { + if (row != null) row.freeContentViewWhenSafe(inflationFlag); + } + + public void setAmbientPulsing(boolean pulsing) { + if (row != null) row.setAmbientPulsing(pulsing); + } + + public boolean rowExists() { + return row != null; + } + + public boolean isRowDismissed() { + return row != null && row.isDismissed(); + } + + public boolean isRowRemoved() { + return row != null && row.isRemoved(); + } + + /** + * @return {@code true} if the row is null or removed + */ + public boolean isRemoved() { + //TODO: recycling invalidates this + return row == null || row.isRemoved(); + } + + /** + * @return {@code true} if the row is null or dismissed + */ + public boolean isDismissed() { + //TODO: recycling + return row == null || row.isDismissed(); + } + + public boolean isRowPinned() { + return row != null && row.isPinned(); + } + + public void setRowPinned(boolean pinned) { + if (row != null) row.setPinned(pinned); + } + + public boolean isRowAnimatingAway() { + return row != null && row.isHeadsUpAnimatingAway(); + } + + public boolean isRowHeadsUp() { + return row != null && row.isHeadsUp(); + } + + public void setHeadsUp(boolean shouldHeadsUp) { + if (row != null) row.setHeadsUp(shouldHeadsUp); + } + + public boolean mustStayOnScreen() { + return row != null && row.mustStayOnScreen(); + } + + public void setHeadsUpIsVisible() { + if (row != null) row.setHeadsUpIsVisible(); + } + + //TODO: i'm imagining a world where this isn't just the row, but I could be rwong + public ExpandableNotificationRow getHeadsUpAnimationView() { + return row; + } + + public void setUserLocked(boolean userLocked) { + if (row != null) row.setUserLocked(userLocked); + } + + public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) { + if (row != null) row.setUserExpanded(userExpanded, allowChildExpansion); + } + + public void setGroupExpansionChanging(boolean changing) { + if (row != null) row.setGroupExpansionChanging(changing); + } + + public void notifyHeightChanged(boolean needsAnimation) { + if (row != null) row.notifyHeightChanged(needsAnimation); + } + + public void closeRemoteInput() { + if (row != null) row.closeRemoteInput(); + } + + public boolean areChildrenExpanded() { + return row != null && row.areChildrenExpanded(); + } + + public boolean keepInParent() { + return row != null && row.keepInParent(); + } + + //TODO: probably less confusing to say "is group fully visible" + public boolean isGroupNotFullyVisible() { + return row == null || row.isGroupNotFullyVisible(); + } + + public NotificationGuts getGuts() { + if (row != null) return row.getGuts(); + return null; + } + + public boolean hasLowPriorityStateUpdated() { + return row != null && row.hasLowPriorityStateUpdated(); + } + + public void removeRow() { + if (row != null) row.setRemoved(); + } + + public boolean isSummaryWithChildren() { + return row != null && row.isSummaryWithChildren(); + } + + public void setKeepInParent(boolean keep) { + if (row != null) row.setKeepInParent(keep); + } + + public void onDensityOrFontScaleChanged() { + if (row != null) row.onDensityOrFontScaleChanged(); + } + + public boolean areGutsExposed() { + return row != null && row.getGuts() != null && row.getGuts().isExposed(); + } + + public boolean isChildInGroup() { + return parent == null; + } + + public void setLowPriorityStateUpdated(boolean updated) { + if (row != null) row.setLowPriorityStateUpdated(updated); + } + + /** + * @return Can the underlying notification be cleared? This can be different from whether the + * notification can be dismissed in case notifications are sensitive on the lockscreen. + * @see #canViewBeDismissed() + */ + public boolean isClearable() { + if (notification == null || !notification.isClearable()) { + return false; + } + if (children.size() > 0) { + for (int i = 0; i < children.size(); i++) { + NotificationEntry child = children.get(i); + if (!child.isClearable()) { + return false; + } + } + } + return true; + } + + public boolean canViewBeDismissed() { + if (row == null) return true; + return row.canViewBeDismissed(); + } + + @VisibleForTesting + boolean isExemptFromDndVisualSuppression() { + if (isNotificationBlockedByPolicy(notification.getNotification())) { + return false; + } + + if ((notification.getNotification().flags + & Notification.FLAG_FOREGROUND_SERVICE) != 0) { + return true; + } + if (notification.getNotification().isMediaNotification()) { + return true; + } + if (mIsSystemNotification != null && mIsSystemNotification) { + return true; + } + return false; + } + + private boolean shouldSuppressVisualEffect(int effect) { + if (isExemptFromDndVisualSuppression()) { + return false; + } + return (suppressedVisualEffects & effect) != 0; + } + + /** + * Returns whether {@link Policy#SUPPRESSED_EFFECT_FULL_SCREEN_INTENT} + * is set for this entry. + */ + public boolean shouldSuppressFullScreenIntent() { + return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_FULL_SCREEN_INTENT); + } + + /** + * Returns whether {@link Policy#SUPPRESSED_EFFECT_PEEK} + * is set for this entry. + */ + public boolean shouldSuppressPeek() { + return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_PEEK); + } + + /** + * Returns whether {@link Policy#SUPPRESSED_EFFECT_STATUS_BAR} + * is set for this entry. + */ + public boolean shouldSuppressStatusBar() { + return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_STATUS_BAR); + } + + /** + * Returns whether {@link Policy#SUPPRESSED_EFFECT_AMBIENT} + * is set for this entry. + */ + public boolean shouldSuppressAmbient() { + return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_AMBIENT); + } + + /** + * Returns whether {@link Policy#SUPPRESSED_EFFECT_NOTIFICATION_LIST} + * is set for this entry. + */ + public boolean shouldSuppressNotificationList() { + return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_NOTIFICATION_LIST); + } + + /** + * Categories that are explicitly called out on DND settings screens are always blocked, if + * DND has flagged them, even if they are foreground or system notifications that might + * otherwise visually bypass DND. + */ + private static boolean isNotificationBlockedByPolicy(Notification n) { + return isCategory(CATEGORY_CALL, n) + || isCategory(CATEGORY_MESSAGE, n) + || isCategory(CATEGORY_ALARM, n) + || isCategory(CATEGORY_EVENT, n) + || isCategory(CATEGORY_REMINDER, n); + } + + private static boolean isCategory(String category, Notification n) { + return Objects.equals(n.category, category); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java index 610d3003f0e9..5ba9b4b34042 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java @@ -15,7 +15,6 @@ */ package com.android.systemui.statusbar.notification.logging; -import android.annotation.Nullable; import android.content.Context; import android.os.Handler; import android.os.RemoteException; @@ -24,9 +23,12 @@ import android.os.SystemClock; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import androidx.annotation.Nullable; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; @@ -34,15 +36,16 @@ import com.android.systemui.UiOffloadThread; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarStateController.StateListener; -import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.policy.HeadsUpManager; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Map; import javax.inject.Inject; import javax.inject.Singleton; @@ -67,6 +70,7 @@ public class NotificationLogger implements StateListener { private final UiOffloadThread mUiOffloadThread; private final NotificationEntryManager mEntryManager; private HeadsUpManager mHeadsUpManager; + private final ExpansionStateLogger mExpansionStateLogger; protected Handler mHandler = new Handler(); protected IStatusBarService mBarService; @@ -116,11 +120,11 @@ public class NotificationLogger implements StateListener { // notifications. // 3. Report newly visible and no-longer visible notifications. // 4. Keep currently visible notifications for next report. - ArrayList<NotificationData.Entry> activeNotifications = mEntryManager + ArrayList<NotificationEntry> activeNotifications = mEntryManager .getNotificationData().getActiveNotifications(); int N = activeNotifications.size(); for (int i = 0; i < N; i++) { - NotificationData.Entry entry = activeNotifications.get(i); + NotificationEntry entry = activeNotifications.get(i); String key = entry.notification.getKey(); boolean isVisible = mListContainer.isInVisibleLocation(entry); NotificationVisibility visObj = NotificationVisibility.obtain(key, i, N, isVisible); @@ -145,6 +149,9 @@ public class NotificationLogger implements StateListener { recycleAllVisibilityObjects(mCurrentlyVisibleNotifications); mCurrentlyVisibleNotifications.addAll(mTmpCurrentlyVisibleNotifications); + mExpansionStateLogger.onVisibilityChanged( + mTmpCurrentlyVisibleNotifications, mTmpCurrentlyVisibleNotifications); + recycleAllVisibilityObjects(mTmpNoLongerVisibleNotifications); mTmpCurrentlyVisibleNotifications.clear(); mTmpNewlyVisibleNotifications.clear(); @@ -156,27 +163,27 @@ public class NotificationLogger implements StateListener { public NotificationLogger(NotificationListener notificationListener, UiOffloadThread uiOffloadThread, NotificationEntryManager entryManager, - StatusBarStateController statusBarStateController) { + StatusBarStateController statusBarStateController, + ExpansionStateLogger expansionStateLogger) { mNotificationListener = notificationListener; mUiOffloadThread = uiOffloadThread; mEntryManager = entryManager; mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + mExpansionStateLogger = expansionStateLogger; // Not expected to be destroyed, don't need to unsubscribe statusBarStateController.addCallback(this); entryManager.addNotificationEntryListener(new NotificationEntryListener() { @Override public void onEntryRemoved( - @Nullable NotificationData.Entry entry, - String key, - StatusBarNotification old, + NotificationEntry entry, NotificationVisibility visibility, - boolean lifetimeExtended, boolean removedByUser) { - if (removedByUser && visibility != null && entry != null) { - logNotificationClear(key, entry.notification, visibility); + if (removedByUser && visibility != null) { + logNotificationClear(entry.key, entry.notification, visibility); } + mExpansionStateLogger.onEntryRemoved(entry.key); } @Override @@ -323,8 +330,8 @@ public class NotificationLogger implements StateListener { } } - private NotificationVisibility[] cloneVisibilitiesAsArr(Collection<NotificationVisibility> c) { - + private static NotificationVisibility[] cloneVisibilitiesAsArr( + Collection<NotificationVisibility> c) { final NotificationVisibility[] array = new NotificationVisibility[c.size()]; int i = 0; for(NotificationVisibility nv: c) { @@ -352,9 +359,133 @@ public class NotificationLogger implements StateListener { } /** + * Called when the notification is expanded / collapsed. + */ + public void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded) { + mExpansionStateLogger.onExpansionChanged(key, isUserAction, isExpanded); + } + + /** * A listener that is notified when some child locations might have changed. */ public interface OnChildLocationsChangedListener { void onChildLocationsChanged(); } + + /** + * Logs the expansion state change when the notification is visible. + */ + public static class ExpansionStateLogger { + /** Notification key -> state, should be accessed in UI offload thread only. */ + private final Map<String, State> mExpansionStates = new ArrayMap<>(); + + /** + * Notification key -> last logged expansion state, should be accessed in UI thread only. + */ + private final Map<String, Boolean> mLoggedExpansionState = new ArrayMap<>(); + private final UiOffloadThread mUiOffloadThread; + @VisibleForTesting + IStatusBarService mBarService; + + @Inject + public ExpansionStateLogger(UiOffloadThread uiOffloadThread) { + mUiOffloadThread = uiOffloadThread; + mBarService = + IStatusBarService.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + } + + @VisibleForTesting + void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded) { + State state = getState(key); + state.mIsUserAction = isUserAction; + state.mIsExpanded = isExpanded; + maybeNotifyOnNotificationExpansionChanged(key, state); + } + + @VisibleForTesting + void onVisibilityChanged( + Collection<NotificationVisibility> newlyVisible, + Collection<NotificationVisibility> noLongerVisible) { + final NotificationVisibility[] newlyVisibleAr = + cloneVisibilitiesAsArr(newlyVisible); + final NotificationVisibility[] noLongerVisibleAr = + cloneVisibilitiesAsArr(noLongerVisible); + + for (NotificationVisibility nv : newlyVisibleAr) { + State state = getState(nv.key); + state.mIsVisible = true; + maybeNotifyOnNotificationExpansionChanged(nv.key, state); + } + for (NotificationVisibility nv : noLongerVisibleAr) { + State state = getState(nv.key); + state.mIsVisible = false; + } + } + + @VisibleForTesting + void onEntryRemoved(String key) { + mExpansionStates.remove(key); + mLoggedExpansionState.remove(key); + } + + private State getState(String key) { + State state = mExpansionStates.get(key); + if (state == null) { + state = new State(); + mExpansionStates.put(key, state); + } + return state; + } + + private void maybeNotifyOnNotificationExpansionChanged(final String key, State state) { + if (!state.isFullySet()) { + return; + } + if (!state.mIsVisible) { + return; + } + Boolean loggedExpansionState = mLoggedExpansionState.get(key); + // Consider notification is initially collapsed, so only expanded is logged in the + // first time. + if (loggedExpansionState == null && !state.mIsExpanded) { + return; + } + if (loggedExpansionState != null + && state.mIsExpanded == loggedExpansionState) { + return; + } + mLoggedExpansionState.put(key, state.mIsExpanded); + final State stateToBeLogged = new State(state); + mUiOffloadThread.submit(() -> { + try { + mBarService.onNotificationExpansionChanged( + key, stateToBeLogged.mIsUserAction, stateToBeLogged.mIsExpanded); + } catch (RemoteException e) { + Log.e(TAG, "Failed to call onNotificationExpansionChanged: ", e); + } + }); + } + + private static class State { + @Nullable + Boolean mIsUserAction; + @Nullable + Boolean mIsExpanded; + @Nullable + Boolean mIsVisible; + + private State() {} + + private State(State state) { + this.mIsUserAction = state.mIsUserAction; + this.mIsExpanded = state.mIsExpanded; + this.mIsVisible = state.mIsVisible; + } + + private boolean isFullySet() { + return mIsUserAction != null && mIsExpanded != null && mIsVisible != null; + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 8b0a6828205d..c34d567da0be 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -105,7 +105,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private final DoubleTapHelper mDoubleTapHelper; private boolean mDimmed; - private boolean mDark; + protected boolean mDark; protected int mBgTint = NO_COLOR; private float mBgAlpha = 1f; @@ -140,7 +140,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private FalsingManager mFalsingManager; private float mNormalBackgroundVisibilityAmount; - private ValueAnimator mFadeInFromDarkAnimator; private float mDimmedBackgroundFadeInAmount = -1; private ValueAnimator.AnimatorUpdateListener mBackgroundVisibilityUpdater = new ValueAnimator.AnimatorUpdateListener() { @@ -150,22 +149,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mDimmedBackgroundFadeInAmount = mBackgroundDimmed.getAlpha(); } }; - private AnimatorListenerAdapter mFadeInEndListener = new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - mFadeInFromDarkAnimator = null; - mDimmedBackgroundFadeInAmount = -1; - updateBackground(); - } - }; - private ValueAnimator.AnimatorUpdateListener mUpdateOutlineListener - = new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - updateOutlineAlpha(); - } - }; private FakeShadowView mFakeShadow; private int mCurrentBackgroundTint; private int mTargetTint; @@ -465,22 +448,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mDark = dark; updateBackground(); updateBackgroundTint(false); - if (!dark && fade && !shouldHideBackground()) { - fadeInFromDark(delay); - } - updateOutlineAlpha(); } private void updateOutlineAlpha() { - if (mDark) { - setOutlineAlpha(0f); - return; - } float alpha = NotificationStackScrollLayout.BACKGROUND_ALPHA_DIMMED; alpha = (alpha + (1.0f - alpha) * mNormalBackgroundVisibilityAmount); - if (mFadeInFromDarkAnimator != null) { - alpha *= mFadeInFromDarkAnimator.getAnimatedFraction(); - } setOutlineAlpha(alpha); } @@ -638,36 +610,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } /** - * Fades in the background when exiting dark mode. - */ - private void fadeInFromDark(long delay) { - final View background = mDimmed ? mBackgroundDimmed : mBackgroundNormal; - background.setAlpha(0f); - mBackgroundVisibilityUpdater.onAnimationUpdate(null); - background.animate() - .alpha(1f) - .setDuration(DARK_ANIMATION_LENGTH) - .setStartDelay(delay) - .setInterpolator(Interpolators.ALPHA_IN) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationCancel(Animator animation) { - // Jump state if we are cancelled - background.setAlpha(1f); - } - }) - .setUpdateListener(mBackgroundVisibilityUpdater) - .start(); - mFadeInFromDarkAnimator = TimeAnimator.ofFloat(0.0f, 1.0f); - mFadeInFromDarkAnimator.setDuration(DARK_ANIMATION_LENGTH); - mFadeInFromDarkAnimator.setStartDelay(delay); - mFadeInFromDarkAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); - mFadeInFromDarkAnimator.addListener(mFadeInEndListener); - mFadeInFromDarkAnimator.addUpdateListener(mUpdateOutlineListener); - mFadeInFromDarkAnimator.start(); - } - - /** * Fades the background when the dimmed state changes. */ private void fadeDimmedBackground() { @@ -708,9 +650,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView public void onAnimationEnd(Animator animation) { updateBackground(); mBackgroundAnimator = null; - if (mFadeInFromDarkAnimator == null) { - mDimmedBackgroundFadeInAmount = -1; - } + mDimmedBackgroundFadeInAmount = -1; } }); mBackgroundAnimator.addUpdateListener(mBackgroundVisibilityUpdater); @@ -736,7 +676,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mBackgroundNormal.setVisibility(mActivated ? VISIBLE : INVISIBLE); } else if (mDimmed) { // When groups are animating to the expanded state from the lockscreen, show the - // normal background instead of the dimmed background + // normal background instead of the dimmed background. final boolean dontShowDimmed = isGroupExpansionChanging() && isChildInGroup(); mBackgroundDimmed.setVisibility(dontShowDimmed ? View.INVISIBLE : View.VISIBLE); mBackgroundNormal.setVisibility((mActivated || dontShowDimmed) @@ -760,7 +700,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } protected boolean shouldHideBackground() { - return mDark; + return false; } private void cancelFadeAnimations() { @@ -1023,14 +963,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView /** * @param withTint should a possible tint be factored in? - * @param withOverRide should the value be interpolated with {@link #mOverrideTint} + * @param withOverride should the value be interpolated with {@link #mOverrideTint} * @return the calculated background color */ - private int calculateBgColor(boolean withTint, boolean withOverRide) { - if (withTint && mDark) { - return getContext().getColor(R.color.notification_material_background_dark_color); - } - if (withOverRide && mOverrideTint != NO_COLOR) { + private int calculateBgColor(boolean withTint, boolean withOverride) { + if (withOverride && mOverrideTint != NO_COLOR) { int defaultTint = calculateBgColor(withTint, false); return NotificationUtils.interpolateColors(defaultTint, mOverrideTint, mOverrideAmount); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index a58c7cde32a7..296c061459bc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.notification.row; -import static com.android.systemui.statusbar.StatusBarState.SHADE; import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_AMBIENT; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED; @@ -86,10 +85,9 @@ import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.notification.AboveShelfChangedListener; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; -import com.android.systemui.statusbar.notification.NotificationData; -import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.logging.NotificationCounters; import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag; import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; @@ -106,6 +104,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.function.BooleanSupplier; import java.util.function.Consumer; @@ -122,6 +121,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private static final int MENU_VIEW_INDEX = 0; private static final String TAG = "ExpandableNotifRow"; public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f; + private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30); private boolean mUpdateBackgroundOnUpdate; /** @@ -132,7 +132,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } private LayoutListener mLayoutListener; - private boolean mDark; private boolean mLowPriorityStateUpdated; private final NotificationInflater mNotificationInflater; private int mIconTransformContentShift; @@ -146,7 +145,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private int mNotificationMinHeight; private int mNotificationMinHeightLarge; private int mNotificationMaxHeight; - private int mNotificationAmbientHeight; private int mIncreasedPaddingBetweenElements; private int mNotificationLaunchHeight; private boolean mMustStayOnScreen; @@ -199,7 +197,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private ExpansionLogger mLogger; private String mLoggingKey; private NotificationGuts mGuts; - private NotificationData.Entry mEntry; + private NotificationEntry mEntry; private StatusBarNotification mStatusBarNotification; private String mAppName; @@ -451,7 +449,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * * @param entry the entry this row is tied to */ - public void setEntry(@NonNull NotificationData.Entry entry) { + public void setEntry(@NonNull NotificationEntry entry) { mEntry = entry; mStatusBarNotification = entry.notification; cacheIsSystemNotification(); @@ -677,15 +675,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (headsUpWrapper != null) { headsUpHeight = Math.max(headsUpHeight, headsUpWrapper.getMinLayoutHeight()); } - layout.setHeights(minHeight, headsUpHeight, mNotificationMaxHeight, - mNotificationAmbientHeight); + layout.setHeights(minHeight, headsUpHeight, mNotificationMaxHeight, headsUpHeight); } public StatusBarNotification getStatusBarNotification() { return mStatusBarNotification; } - public NotificationData.Entry getEntry() { + public NotificationEntry getEntry() { return mEntry; } @@ -1422,7 +1419,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView public void performDismiss(boolean fromAccessibility) { if (isOnlyChildInGroup()) { - NotificationData.Entry groupSummary = + NotificationEntry groupSummary = mGroupManager.getLogicalGroupSummary(getStatusBarNotification()); if (groupSummary.isClearable()) { // If this is the only child in the group, dismiss the group, but don't try to show @@ -1647,8 +1644,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView R.dimen.notification_min_height_increased); mNotificationMaxHeight = NotificationUtils.getFontScaledHeight(mContext, R.dimen.notification_max_height); - mNotificationAmbientHeight = NotificationUtils.getFontScaledHeight(mContext, - R.dimen.notification_ambient_height); mMaxHeadsUpHeightBeforeN = NotificationUtils.getFontScaledHeight(mContext, R.dimen.notification_max_heads_up_height_legacy); mMaxHeadsUpHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext, @@ -1693,17 +1688,31 @@ public class ExpandableNotificationRow extends ActivatableNotificationView /** Sets the last time the notification being displayed audibly alerted the user. */ public void setLastAudiblyAlertedMs(long lastAudiblyAlertedMs) { if (NotificationUtils.useNewInterruptionModel(mContext)) { - boolean recentlyAudiblyAlerted = System.currentTimeMillis() - lastAudiblyAlertedMs - < NotificationEntryManager.RECENTLY_ALERTED_THRESHOLD_MS; - if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() != null) { - mChildrenContainer.getHeaderView().setRecentlyAudiblyAlerted( - recentlyAudiblyAlerted); + long timeSinceAlertedAudibly = System.currentTimeMillis() - lastAudiblyAlertedMs; + boolean alertedRecently = + timeSinceAlertedAudibly < RECENTLY_ALERTED_THRESHOLD_MS; + + applyAudiblyAlertedRecently(alertedRecently); + + removeCallbacks(mExpireRecentlyAlertedFlag); + if (alertedRecently) { + long timeUntilNoLongerRecent = + RECENTLY_ALERTED_THRESHOLD_MS - timeSinceAlertedAudibly; + postDelayed(mExpireRecentlyAlertedFlag, timeUntilNoLongerRecent); } - mPrivateLayout.setRecentlyAudiblyAlerted(recentlyAudiblyAlerted); - mPublicLayout.setRecentlyAudiblyAlerted(recentlyAudiblyAlerted); } } + private final Runnable mExpireRecentlyAlertedFlag = () -> applyAudiblyAlertedRecently(false); + + private void applyAudiblyAlertedRecently(boolean audiblyAlertedRecently) { + if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() != null) { + mChildrenContainer.getHeaderView().setRecentlyAudiblyAlerted(audiblyAlertedRecently); + } + mPrivateLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently); + mPublicLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently); + } + public View.OnClickListener getAppOpsOnClickListener() { return mOnAppOpsClickListener; } @@ -1994,8 +2003,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public void setDark(boolean dark, boolean fade, long delay) { + if (mDark == dark) { + return; + } super.setDark(dark, fade, delay); - mDark = dark; if (!mIsAmbientPulsing) { // Only fade the showing view of the pulsing notification. fade = false; @@ -2004,9 +2015,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (showing != null) { showing.setDark(dark, fade, delay); } - if (mIsSummaryWithChildren) { - mChildrenContainer.setDark(dark, fade, delay); - } updateShelfIconColor(); } @@ -2307,7 +2315,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } private boolean isShownAsBubble() { - return mEntry.isBubble() && (mStatusBarState == SHADE || mStatusBarState == -1); + return mEntry.isBubble() && !mEntry.showInShadeWhenBubble() && !mEntry.isBubbleDismissed(); } /** @@ -2538,7 +2546,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView /** * @return Whether this view is allowed to be dismissed. Only valid for visible notifications as * otherwise some state might not be updated. To request about the general clearability - * see {@link NotificationData.Entry#isClearable()}. + * see {@link NotificationEntry#isClearable()}. */ public boolean canViewBeDismissed() { return mEntry.isClearable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral); @@ -2969,7 +2977,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } public interface OnExpandClickListener { - void onExpandClicked(NotificationData.Entry clickedEntry, boolean nowExpanded); + void onExpandClicked(NotificationEntry clickedEntry, boolean nowExpanded); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java index 33badafd5488..90ff4a728915 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java @@ -39,14 +39,10 @@ public class HybridGroupManager { private final NotificationDozeHelper mDozer; private final ViewGroup mParent; - private float mOverflowNumberSizeDark; - private int mOverflowNumberPaddingDark; private float mOverflowNumberSize; private int mOverflowNumberPadding; private int mOverflowNumberColor; - private int mOverflowNumberColorDark; - private float mDarkAmount = 0f; public HybridGroupManager(Context ctx, ViewGroup parent) { mContext = ctx; @@ -59,12 +55,8 @@ public class HybridGroupManager { Resources res = mContext.getResources(); mOverflowNumberSize = res.getDimensionPixelSize( R.dimen.group_overflow_number_size); - mOverflowNumberSizeDark = res.getDimensionPixelSize( - R.dimen.group_overflow_number_size_dark); mOverflowNumberPadding = res.getDimensionPixelSize( R.dimen.group_overflow_number_padding); - mOverflowNumberPaddingDark = mOverflowNumberPadding + res.getDimensionPixelSize( - R.dimen.group_overflow_number_extra_padding_dark); } private HybridNotificationView inflateHybridViewWithStyle(int style) { @@ -86,13 +78,11 @@ public class HybridGroupManager { } private void updateOverFlowNumberColor(TextView numberView) { - numberView.setTextColor(NotificationUtils.interpolateColors( - mOverflowNumberColor, mOverflowNumberColorDark, mDarkAmount)); + numberView.setTextColor(mOverflowNumberColor); } - public void setOverflowNumberColor(TextView numberView, int colorRegular, int colorDark) { + public void setOverflowNumberColor(TextView numberView, int colorRegular) { mOverflowNumberColor = colorRegular; - mOverflowNumberColorDark = colorDark; if (numberView != null) { updateOverFlowNumberColor(numberView); } @@ -107,7 +97,7 @@ public class HybridGroupManager { public HybridNotificationView bindAmbientFromNotification(HybridNotificationView reusableView, Notification notification) { return bindFromNotificationWithStyle(reusableView, notification, - R.style.HybridNotification_Ambient); + R.style.HybridNotification); } private HybridNotificationView bindFromNotificationWithStyle( @@ -150,6 +140,11 @@ public class HybridGroupManager { R.plurals.notification_group_overflow_description, number), number); reusableView.setContentDescription(contentDescription); + reusableView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mOverflowNumberSize); + reusableView.setPaddingRelative(reusableView.getPaddingStart(), + reusableView.getPaddingTop(), mOverflowNumberPadding, + reusableView.getPaddingBottom()); + updateOverFlowNumberColor(reusableView); return reusableView; } @@ -163,16 +158,4 @@ public class HybridGroupManager { } return titleView; } - - public void setOverflowNumberDark(TextView view, boolean dark, boolean fade, long delay) { - mDozer.setIntensityDark((f)->{ - mDarkAmount = f; - updateOverFlowNumberColor(view); - }, dark, fade, delay, view); - view.setTextSize(TypedValue.COMPLEX_UNIT_PX, - dark ? mOverflowNumberSizeDark : mOverflowNumberSize); - int paddingEnd = dark ? mOverflowNumberPaddingDark : mOverflowNumberPadding; - view.setPaddingRelative(view.getPaddingStart(), view.getPaddingTop(), paddingEnd, - view.getPaddingBottom()); - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java index 607d96dcbd3f..6df72febc8db 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java @@ -20,11 +20,13 @@ import static android.service.notification.NotificationListenerService.Ranking .USER_SENTIMENT_NEGATIVE; import android.content.Context; +import android.metrics.LogMaker; import android.util.Log; import androidx.annotation.VisibleForTesting; import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.Dependency; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.notification.NotificationEntryManager; @@ -58,6 +60,8 @@ public class NotificationBlockingHelperManager { */ private boolean mIsShadeExpanded; + private MetricsLogger mMetricsLogger = new MetricsLogger(); + @Inject public NotificationBlockingHelperManager(Context context) { mContext = context; @@ -100,6 +104,11 @@ public class NotificationBlockingHelperManager { mBlockingHelperRow = row; mBlockingHelperRow.setBlockingHelperShowing(true); + // Log triggering of blocking helper by the system. This log line + // should be emitted before the "display" log line. + mMetricsLogger.write( + getLogMaker().setSubtype(MetricsEvent.BLOCKING_HELPER_TRIGGERED_BY_SYSTEM)); + // We don't care about the touch origin (x, y) since we're opening guts without any // explicit user interaction. manager.openGuts(mBlockingHelperRow, 0, 0, menuRow.getLongpressMenuItem(mContext)); @@ -153,6 +162,13 @@ public class NotificationBlockingHelperManager { || mNonBlockablePkgs.contains(makeChannelKey(packageName, channelName)); } + private LogMaker getLogMaker() { + return mBlockingHelperRow.getStatusBarNotification() + .getLogMaker() + .setCategory(MetricsEvent.NOTIFICATION_ITEM) + .setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER); + } + // Format must stay in sync with frameworks/base/core/res/res/values/config.xml // config_nonBlockableNotificationPackages private String makeChannelKey(String pkg, String channel) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 02a310c29379..c161da348c28 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -46,8 +46,8 @@ import com.android.systemui.R; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.TransformableView; -import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationUtils; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.wrapper.NotificationCustomViewWrapper; import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; import com.android.systemui.statusbar.phone.NotificationGroupManager; @@ -1231,7 +1231,7 @@ public class NotificationContentView extends FrameLayout { updateAllSingleLineViews(); } - public void onNotificationUpdated(NotificationData.Entry entry) { + public void onNotificationUpdated(NotificationEntry entry) { mStatusBarNotification = entry.notification; mOnContentViewInactiveListeners.clear(); mBeforeN = entry.targetSdk < Build.VERSION_CODES.N; @@ -1292,7 +1292,7 @@ public class NotificationContentView extends FrameLayout { } } - private void applyRemoteInputAndSmartReply(final NotificationData.Entry entry) { + private void applyRemoteInputAndSmartReply(final NotificationEntry entry) { if (mRemoteInputController == null) { return; } @@ -1313,7 +1313,7 @@ public class NotificationContentView extends FrameLayout { @VisibleForTesting static SmartRepliesAndActions chooseSmartRepliesAndActions( SmartReplyConstants smartReplyConstants, - final NotificationData.Entry entry) { + final NotificationEntry entry) { boolean enableAppGeneratedSmartReplies = (smartReplyConstants.isEnabled() && (!smartReplyConstants.requiresTargetingP() || entry.targetSdk >= Build.VERSION_CODES.P)); @@ -1370,7 +1370,7 @@ public class NotificationContentView extends FrameLayout { smartReplies, smartActions, freeformRemoteInputActionPair != null); } - private void applyRemoteInput(NotificationData.Entry entry, boolean hasFreeformRemoteInput) { + private void applyRemoteInput(NotificationEntry entry, boolean hasFreeformRemoteInput) { View bigContentView = mExpandedChild; if (bigContentView != null) { mExpandedRemoteInput = applyRemoteInput(bigContentView, entry, hasFreeformRemoteInput, @@ -1402,7 +1402,7 @@ public class NotificationContentView extends FrameLayout { mCachedHeadsUpRemoteInput = null; } - private RemoteInputView applyRemoteInput(View view, NotificationData.Entry entry, + private RemoteInputView applyRemoteInput(View view, NotificationEntry entry, boolean hasRemoteInput, PendingIntent existingPendingIntent, RemoteInputView cachedView, NotificationViewWrapper wrapper) { View actionContainerCandidate = view.findViewById( @@ -1470,7 +1470,7 @@ public class NotificationContentView extends FrameLayout { } private void applySmartReplyView(SmartRepliesAndActions smartRepliesAndActions, - NotificationData.Entry entry) { + NotificationEntry entry) { if (mExpandedChild != null) { mExpandedSmartReplyView = applySmartReplyView(mExpandedChild, smartRepliesAndActions, entry); @@ -1489,14 +1489,14 @@ public class NotificationContentView extends FrameLayout { } } } - if (mHeadsUpChild != null) { + if (mHeadsUpChild != null && mSmartReplyConstants.getShowInHeadsUp()) { mHeadsUpSmartReplyView = applySmartReplyView(mHeadsUpChild, smartRepliesAndActions, entry); } } private SmartReplyView applySmartReplyView(View view, - SmartRepliesAndActions smartRepliesAndActions, NotificationData.Entry entry) { + SmartRepliesAndActions smartRepliesAndActions, NotificationEntry entry) { View smartReplyContainerCandidate = view.findViewById( com.android.internal.R.id.smart_reply_container); if (!(smartReplyContainerCandidate instanceof LinearLayout)) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index ac4e5830d76b..bd1dfb181afe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -48,7 +48,7 @@ import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.NotificationActivityStarter; -import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.StatusBar; @@ -116,7 +116,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx mNotificationActivityStarter = notificationActivityStarter; } - public void onDensityOrFontScaleChanged(NotificationData.Entry entry) { + public void onDensityOrFontScaleChanged(NotificationEntry entry) { setExposedGuts(entry.getGuts()); bindGuts(entry.getRow()); } @@ -429,7 +429,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx } @Override - public boolean shouldExtendLifetime(NotificationData.Entry entry) { + public boolean shouldExtendLifetime(NotificationEntry entry) { return entry != null &&(mNotificationGutsExposed != null && entry.getGuts() != null @@ -438,7 +438,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx } @Override - public void setShouldManageLifetime(NotificationData.Entry entry, boolean shouldExtend) { + public void setShouldManageLifetime(NotificationEntry entry, boolean shouldExtend) { if (shouldExtend) { mKeyToRemoveOnGutsClosed = entry.key; if (Log.isLoggable(TAG, Log.DEBUG)) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java index 9908049984d1..42ebfceca334 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java @@ -37,7 +37,7 @@ import com.android.internal.widget.ImageMessageConsumer; import com.android.systemui.statusbar.InflationTask; import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.MediaNotificationProcessor; -import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.util.Assert; @@ -614,7 +614,7 @@ public class NotificationInflater { @Nullable InflationCallback endListener, ExpandableNotificationRow row, boolean redactAmbient) { Assert.isMainThread(); - NotificationData.Entry entry = row.getEntry(); + NotificationEntry entry = row.getEntry(); NotificationContentView privateLayout = row.getPrivateLayout(); NotificationContentView publicLayout = row.getPublicLayout(); if (runningInflations.isEmpty()) { @@ -724,7 +724,7 @@ public class NotificationInflater { * @param entry the entry with the content views set * @param inflatedFlags the flags associated with the content views that were inflated */ - void onAsyncInflationFinished(NotificationData.Entry entry, + void onAsyncInflationFinished(NotificationEntry entry, @InflationFlag int inflatedFlags); /** @@ -782,7 +782,7 @@ public class NotificationInflater { mRedactAmbient = redactAmbient; mRemoteViewClickHandler = remoteViewClickHandler; mCallback = callback; - NotificationData.Entry entry = row.getEntry(); + NotificationEntry entry = row.getEntry(); entry.setInflationTask(this); } @@ -857,7 +857,7 @@ public class NotificationInflater { } @Override - public void onAsyncInflationFinished(NotificationData.Entry entry, + public void onAsyncInflationFinished(NotificationEntry entry, @InflationFlag int inflatedFlags) { mRow.getEntry().onInflationTaskFinished(); mRow.onNotificationUpdated(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java index b1eab8069307..5253e38fd675 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java @@ -123,11 +123,15 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G private OnClickListener mOnKeepShowing = v -> { mExitReason = NotificationCounters.BLOCKING_HELPER_KEEP_SHOWING; closeControls(v); + mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER) + .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_STAY_SILENT)); }; private OnClickListener mOnToggleSilent = v -> { Runnable saveImportance = () -> { swapContent(ACTION_TOGGLE_SILENT, true /* animate */); + mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER) + .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_ALERT_ME)); }; if (mCheckSaveListener != null) { mCheckSaveListener.checkSave(saveImportance, mSbn); @@ -139,6 +143,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G private OnClickListener mOnStopOrMinimizeNotifications = v -> { Runnable saveImportance = () -> { swapContent(ACTION_BLOCK, true /* animate */); + mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER) + .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_BLOCKED)); }; if (mCheckSaveListener != null) { mCheckSaveListener.checkSave(saveImportance, mSbn); @@ -153,6 +159,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G logBlockingHelperCounter(NotificationCounters.BLOCKING_HELPER_UNDO); mMetricsLogger.write(importanceChangeLogMaker().setType(MetricsEvent.TYPE_DISMISS)); swapContent(ACTION_UNDO, true /* animate */); + mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER) + .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_UNDO)); }; public NotificationInfo(Context context, AttributeSet attrs) { @@ -251,6 +259,9 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G bindHeader(); bindPrompt(); bindButtons(); + + mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER) + .setSubtype(MetricsEvent.BLOCKING_HELPER_DISPLAY)); } private void bindHeader() throws RemoteException { @@ -588,6 +599,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G confirmation.setAlpha(1f); header.setVisibility(VISIBLE); header.setAlpha(1f); + mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER) + .setSubtype(MetricsEvent.BLOCKING_HELPER_DISMISS)); } @Override @@ -733,4 +746,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G } } } + + private LogMaker getLogMaker() { + return mSbn.getLogMaker().setCategory(MetricsEvent.NOTIFICATION_ITEM); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java index 1741a0b6b37c..0160c547b336 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java @@ -25,7 +25,7 @@ import androidx.asynclayoutinflater.view.AsyncLayoutInflater; import com.android.systemui.R; import com.android.systemui.statusbar.InflationTask; -import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; /** * An inflater task that asynchronously inflates a ExpandableNotificationRow @@ -36,14 +36,14 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf private static final boolean TRACE_ORIGIN = true; private RowInflationFinishedListener mListener; - private NotificationData.Entry mEntry; + private NotificationEntry mEntry; private boolean mCancelled; private Throwable mInflateOrigin; /** * Inflates a new notificationView. This should not be called twice on this object */ - public void inflate(Context context, ViewGroup parent, NotificationData.Entry entry, + public void inflate(Context context, ViewGroup parent, NotificationEntry entry, RowInflationFinishedListener listener) { if (TRACE_ORIGIN) { mInflateOrigin = new Throwable("inflate requested here"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index 670908fe175d..1b5013dedb0e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -25,7 +25,7 @@ import com.android.systemui.R; import com.android.systemui.statusbar.AmbientPulseManager; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; @@ -75,7 +75,6 @@ public class AmbientState { private int mIntrinsicPadding; private int mExpandAnimationTopChange; private ExpandableNotificationRow mExpandingNotification; - private int mDarkTopPadding; private float mDarkAmount; private boolean mAppearing; @@ -351,14 +350,15 @@ public class AmbientState { } public boolean hasPulsingNotifications() { - return mPulsing; + return mPulsing && mAmbientPulseManager != null + && mAmbientPulseManager.hasNotifications(); } public void setPulsing(boolean hasPulsing) { mPulsing = hasPulsing; } - public boolean isPulsing(NotificationData.Entry entry) { + public boolean isPulsing(NotificationEntry entry) { if (!mPulsing || mAmbientPulseManager == null) { return false; } @@ -458,14 +458,6 @@ public class AmbientState { return mDarkAmount != 0; } - public void setDarkTopPadding(int darkTopPadding) { - mDarkTopPadding = darkTopPadding; - } - - public int getDarkTopPadding() { - return mDarkTopPadding; - } - public void setAppearing(boolean appearing) { mAppearing = appearing; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java index 51180361806b..8ffada43c53f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java @@ -316,7 +316,7 @@ public class NotificationChildrenContainer extends ViewGroup { StatusBarNotification notification = mContainingNotification.getStatusBarNotification(); final Notification.Builder builder = Notification.Builder.recoverBuilder(getContext(), notification.getNotification()); - RemoteViews header = builder.makeNotificationHeader(false /* ambient */); + RemoteViews header = builder.makeNotificationHeader(); if (mNotificationHeader == null) { mNotificationHeader = (NotificationHeaderView) header.apply(getContext(), this); final View expandButton = mNotificationHeader.findViewById( @@ -344,7 +344,7 @@ public class NotificationChildrenContainer extends ViewGroup { builder = Notification.Builder.recoverBuilder(getContext(), notification.getNotification()); } - header = builder.makeNotificationHeader(true /* ambient */); + header = builder.makeNotificationHeader(); if (mNotificationHeaderAmbient == null) { mNotificationHeaderAmbient = (ViewGroup) header.apply(getContext(), this); mNotificationHeaderWrapperAmbient = NotificationViewWrapper.wrap(getContext(), @@ -1171,12 +1171,6 @@ public class NotificationChildrenContainer extends ViewGroup { return mIsLowPriority && !mContainingNotification.isExpanded(); } - public void setDark(boolean dark, boolean fade, long delay) { - if (mOverflowNumber != null) { - mHybridGroupManager.setOverflowNumberDark(mOverflowNumber, dark, fade, delay); - } - } - public void reInflateViews(OnClickListener listener, StatusBarNotification notification) { if (mNotificationHeader != null) { removeView(mNotificationHeader); @@ -1227,8 +1221,7 @@ public class NotificationChildrenContainer extends ViewGroup { public void onNotificationUpdated() { mHybridGroupManager.setOverflowNumberColor(mOverflowNumber, - mContainingNotification.getNotificationColor(), - mContainingNotification.getNotificationColorAmbient()); + mContainingNotification.getNotificationColor()); } public int getPositionInLinearLayout(View childInGroup) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java index f0a26536d7b8..f771be043c07 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java @@ -16,15 +16,14 @@ package com.android.systemui.statusbar.notification.stack; -import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator - .ExpandAnimationParameters; +import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters; import android.view.View; import android.view.ViewGroup; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; -import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.VisibilityLocationProvider; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; @@ -113,19 +112,12 @@ public interface NotificationListContainer extends ExpandableView.OnHeightChange void setMaxDisplayedNotifications(int maxNotifications); /** - * Handle snapping a non-dismissable row back if the user tried to dismiss it. - * - * @param entry the entry whose row needs to snap back - */ - void snapViewIfNeeded(NotificationData.Entry entry); - - /** * Get the view parent for a notification entry. For example, NotificationStackScrollLayout. * * @param entry entry to get the view parent for * @return the view parent for entry */ - ViewGroup getViewParentForNotification(NotificationData.Entry entry); + ViewGroup getViewParentForNotification(NotificationEntry entry); /** * Resets the currently exposed menu view. @@ -148,7 +140,7 @@ public interface NotificationListContainer extends ExpandableView.OnHeightChange * * @param entry the entry whose view's view state needs to be cleaned up (say that 5 times fast) */ - void cleanUpViewStateForEntry(NotificationData.Entry entry); + void cleanUpViewStateForEntry(NotificationEntry entry); /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java index 4f0831f1043c..c5ab9f6049c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java @@ -18,7 +18,8 @@ package com.android.systemui.statusbar.notification.stack; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.NUM_SECTIONS; -import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.AmbientPulseManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; @@ -26,35 +27,43 @@ import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import java.util.HashSet; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * A class that manages the roundness for notification views */ -class NotificationRoundnessManager implements OnHeadsUpChangedListener { - +@Singleton +class NotificationRoundnessManager implements OnHeadsUpChangedListener, + AmbientPulseManager.OnAmbientChangedListener { + + private final ActivatableNotificationView[] mFirstInSectionViews; + private final ActivatableNotificationView[] mLastInSectionViews; + private final ActivatableNotificationView[] mTmpFirstInSectionViews; + private final ActivatableNotificationView[] mTmpLastInSectionViews; private boolean mExpanded; - private ActivatableNotificationView[] mFirstInSectionViews; - private ActivatableNotificationView[] mLastInSectionViews; - private ActivatableNotificationView[] mTmpFirstInSectionViews; - private ActivatableNotificationView[] mTmpLastInSectionViews; private HashSet<ExpandableView> mAnimatedChildren; private Runnable mRoundingChangedCallback; private ExpandableNotificationRow mTrackedHeadsUp; + private ActivatableNotificationView mTrackedAmbient; private float mAppearFraction; - NotificationRoundnessManager() { + @Inject + NotificationRoundnessManager(AmbientPulseManager ambientPulseManager) { mFirstInSectionViews = new ActivatableNotificationView[NUM_SECTIONS]; mLastInSectionViews = new ActivatableNotificationView[NUM_SECTIONS]; mTmpFirstInSectionViews = new ActivatableNotificationView[NUM_SECTIONS]; mTmpLastInSectionViews = new ActivatableNotificationView[NUM_SECTIONS]; + ambientPulseManager.addListener(this); } @Override - public void onHeadsUpPinned(NotificationData.Entry headsUp) { + public void onHeadsUpPinned(NotificationEntry headsUp) { updateView(headsUp.getRow(), false /* animate */); } @Override - public void onHeadsUpUnPinned(NotificationData.Entry headsUp) { + public void onHeadsUpUnPinned(NotificationEntry headsUp) { updateView(headsUp.getRow(), true /* animate */); } @@ -63,6 +72,17 @@ class NotificationRoundnessManager implements OnHeadsUpChangedListener { updateView(row, false /* animate */); } + @Override + public void onAmbientStateChanged(NotificationEntry entry, boolean isPulsing) { + ActivatableNotificationView row = entry.getRow(); + if (isPulsing) { + mTrackedAmbient = row; + } else if (mTrackedAmbient == row) { + mTrackedAmbient = null; + } + updateView(row, false /* animate */); + } + private void updateView(ActivatableNotificationView view, boolean animate) { boolean changed = updateViewWithoutCallback(view, animate); if (changed) { @@ -125,6 +145,9 @@ class NotificationRoundnessManager implements OnHeadsUpChangedListener { // rounded. return 1.0f; } + if (view == mTrackedAmbient) { + return 1.0f; + } return 0.0f; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 8deb7d5e8456..d0665670bf8f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -96,12 +96,13 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarStateController.StateListener; import com.android.systemui.statusbar.notification.FakeShadowView; -import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.ShadeViewRefactor; import com.android.systemui.statusbar.notification.ShadeViewRefactor.RefactorComponent; import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -112,6 +113,7 @@ import com.android.systemui.statusbar.notification.row.NotificationGuts; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.NotificationSnooze; import com.android.systemui.statusbar.notification.row.StackScrollerDecorView; +import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; @@ -199,12 +201,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd private int mPaddingBetweenElements; private int mIncreasedPaddingBetweenElements; private int mMaxTopPadding; - private int mRegularTopPadding; - private int mDarkTopPadding; - // Current padding, will be either mRegularTopPadding or mDarkTopPadding private int mTopPadding; - // Distance between AOD separator and shelf - private int mDarkShelfPadding; private int mBottomMargin; private int mBottomInset = 0; private float mQsExpansionFraction; @@ -316,7 +313,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations = new HashSet<>(); private HeadsUpManagerPhone mHeadsUpManager; - private NotificationRoundnessManager mRoundnessManager = new NotificationRoundnessManager(); + private final NotificationRoundnessManager mRoundnessManager; private boolean mTrackingHeadsUp; private ScrimController mScrimController; private boolean mForceNoOverlappingRendering; @@ -429,7 +426,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd private int mHeadsUpInset; private HeadsUpAppearanceController mHeadsUpAppearanceController; private NotificationIconAreaController mIconAreaController; - private float mVerticalPanelTranslation; + private float mHorizontalPanelTranslation; private final NotificationLockscreenUserManager mLockscreenUserManager = Dependency.get(NotificationLockscreenUserManager.class); protected final NotificationGutsManager mGutsManager = @@ -439,7 +436,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd Dependency.get(NotificationEntryManager.class); private final IStatusBarService mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); - private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); + @VisibleForTesting + protected final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); private final NotificationRemoteInputManager mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class); private final SysuiColorExtractor mColorExtractor = Dependency.get(SysuiColorExtractor.class); @@ -457,12 +455,17 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd private final NotificationGutsManager mNotificationGutsManager = Dependency.get(NotificationGutsManager.class); + /** + * If the {@link NotificationShelf} should be visible when dark. + */ + private boolean mShowDarkShelf; @Inject public NotificationStackScrollLayout( @Named(VIEW_CONTEXT) Context context, AttributeSet attrs, - @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress) { + @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress, + NotificationRoundnessManager notificationRoundnessManager) { super(context, attrs, 0, 0); Resources res = getResources(); @@ -473,6 +476,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } mAmbientState = new AmbientState(context); + mRoundnessManager = notificationRoundnessManager; mBgColor = context.getColor(R.color.notification_shade_background_color); int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height); int maxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height); @@ -489,7 +493,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd res.getBoolean(R.bool.config_drawNotificationBackground); mFadeNotificationsOnDismiss = res.getBoolean(R.bool.config_fadeNotificationsOnDismiss); - mDarkShelfPadding = res.getDimensionPixelSize(R.dimen.widget_bottom_separator_padding); mRoundnessManager.setAnimatedChildren(mChildrenToAddAnimated); mRoundnessManager.setOnRoundingChangedCallback(this::invalidate); addOnExpandedHeightListener(mRoundnessManager::setExpanded); @@ -517,6 +520,17 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd mLowPriorityBeforeSpeedBump = "1".equals(newValue); } }, LOW_PRIORITY); + + mEntryManager.addNotificationEntryListener(new NotificationEntryListener() { + @Override + public void onPostEntryUpdated(NotificationEntry entry) { + if (!entry.notification.isClearable()) { + // The user may have performed a dismiss action on the notification, since it's + // not clearable we should snap it back. + snapViewIfNeeded(entry); + } + } + }); } @Override @@ -590,14 +604,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public RemoteInputController.Delegate createDelegate() { return new RemoteInputController.Delegate() { - public void setRemoteInputActive(NotificationData.Entry entry, + public void setRemoteInputActive(NotificationEntry entry, boolean remoteInputActive) { mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive); entry.notifyHeightChanged(true /* needsAnimation */); updateFooter(); } - public void lockScrollTo(NotificationData.Entry entry) { + public void lockScrollTo(NotificationEntry entry) { NotificationStackScrollLayout.this.lockScrollTo(entry.getRow()); } @@ -667,7 +681,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd int lockScreenTop = mSections[0].getCurrentBounds().top; int lockScreenBottom = mSections[NUM_SECTIONS - 1].getCurrentBounds().bottom; int darkLeft = getWidth() / 2; - int darkTop = mRegularTopPadding; + int darkTop = mTopPadding; float yProgress = 1 - mInterpolatedDarkAmount; float xProgress = mDarkXInterpolator.getInterpolation( @@ -910,7 +924,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @Override @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM) - public boolean isInVisibleLocation(NotificationData.Entry entry) { + public boolean isInVisibleLocation(NotificationEntry entry) { ExpandableNotificationRow row = entry.getRow(); ExpandableViewState childViewState = row.getViewState(); @@ -935,8 +949,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM) private void updateAlgorithmHeightAndPadding() { - mTopPadding = (int) MathUtils.lerp(mRegularTopPadding, mDarkTopPadding, - mInterpolatedDarkAmount); mAmbientState.setLayoutHeight(getLayoutHeight()); updateAlgorithmLayoutMinHeight(); mAmbientState.setTopPadding(mTopPadding); @@ -1074,10 +1086,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private void setTopPadding(int topPadding, boolean animate) { - if (mRegularTopPadding != topPadding) { - mRegularTopPadding = topPadding; - mDarkTopPadding = topPadding + mDarkShelfPadding; - mAmbientState.setDarkTopPadding(mDarkTopPadding); + if (mTopPadding != topPadding) { + mTopPadding = topPadding; updateAlgorithmHeightAndPadding(); updateContentHeight(); if (animate && mAnimationsEnabled && mIsExpanded) { @@ -1184,7 +1194,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd mIsClipped = clipped; } - if (mPulsing) { + if (mPulsing || mAmbientState.isFullyDark() && mShowDarkShelf) { setClipBounds(null); } else if (mAmbientState.isDarkAtAll()) { setClipBounds(mBackgroundAnimationRect); @@ -1224,13 +1234,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd */ @ShadeViewRefactor(RefactorComponent.COORDINATOR) private int getTopHeadsUpPinnedHeight() { - NotificationData.Entry topEntry = mHeadsUpManager.getTopEntry(); + NotificationEntry topEntry = mHeadsUpManager.getTopEntry(); if (topEntry == null) { return 0; } ExpandableNotificationRow row = topEntry.getRow(); if (row.isChildInGroup()) { - final NotificationData.Entry groupSummary + final NotificationEntry groupSummary = mGroupManager.getGroupSummary(row.getStatusBarNotification()); if (groupSummary != null) { row = groupSummary.getRow(); @@ -1405,7 +1415,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd && touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) { if (slidingChild instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild; - NotificationData.Entry entry = row.getEntry(); + NotificationEntry entry = row.getEntry(); if (!mIsExpanded && row.isHeadsUp() && row.isPinned() && mHeadsUpManager.getTopEntry().getRow() != row && mGroupManager.getGroupSummary( @@ -1538,9 +1548,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd true /* isDismissAll */); } - @Override @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) - public void snapViewIfNeeded(NotificationData.Entry entry) { + private void snapViewIfNeeded(NotificationEntry entry) { ExpandableNotificationRow child = entry.getRow(); boolean animate = mIsExpanded || isPinnedHeadsUp(child); // If the child is showing the notification menu snap to that @@ -1550,7 +1559,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @Override @ShadeViewRefactor(RefactorComponent.ADAPTER) - public ViewGroup getViewParentForNotification(NotificationData.Entry entry) { + public ViewGroup getViewParentForNotification(NotificationEntry entry) { return this; } @@ -2043,17 +2052,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } mIntrinsicContentHeight = height; - // We don't want to use the toppadding since that might be interpolated and we want - // to take the final value of the animation. - int topPadding = mAmbientState.isFullyDark() ? mDarkTopPadding : mRegularTopPadding; - mContentHeight = height + topPadding + mBottomMargin; + mContentHeight = height + mTopPadding + mBottomMargin; updateScrollability(); clampScrollPosition(); mAmbientState.setLayoutMaxHeight(mContentHeight); } @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - private boolean isPulsing(NotificationData.Entry entry) { + private boolean isPulsing(NotificationEntry entry) { return mAmbientState.isPulsing(entry); } @@ -2210,13 +2216,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd int top = 0; if (section != null) { ActivatableNotificationView firstView = section.getFirstVisibleChild(); - // Round Y up to avoid seeing the background during animation - int finalTranslationY = (int) Math.ceil(ViewState.getFinalTranslationY(firstView)); - if (alreadyAnimating || section.isTargetTop(finalTranslationY)) { - // we're ending up at the same location as we are now, let's just skip the animation - top = finalTranslationY; - } else { - top = (int) Math.ceil(firstView.getTranslationY()); + if (firstView != null) { + // Round Y up to avoid seeing the background during animation + int finalTranslationY = (int) Math.ceil(ViewState.getFinalTranslationY(firstView)); + if (alreadyAnimating || section.isTargetTop(finalTranslationY)) { + // we're ending up at the same location as we are now, let's just skip the + // animation + top = finalTranslationY; + } else { + top = (int) Math.ceil(firstView.getTranslationY()); + } } } return top; @@ -2558,7 +2567,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) @Override - public void cleanUpViewStateForEntry(NotificationData.Entry entry) { + public void cleanUpViewStateForEntry(NotificationEntry entry) { View child = entry.getRow(); if (child == mSwipeHelper.getTranslatingParentView()) { mSwipeHelper.clearTranslatingParentView(); @@ -2686,7 +2695,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd private boolean isChildInInvisibleGroup(View child) { if (child instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) child; - NotificationData.Entry groupSummary = + NotificationEntry groupSummary = mGroupManager.getGroupSummary(row.getStatusBarNotification()); if (groupSummary != null && groupSummary.getRow() != row) { return row.getVisibility() == View.INVISIBLE; @@ -4350,6 +4359,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd if (mAmbientState.isDark() == dark) { return; } + if (!dark) { + mShowDarkShelf = false; + } mAmbientState.setDark(dark); if (animate && mAnimationsEnabled) { mDarkNeedsAnimation = true; @@ -4366,12 +4378,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private void updatePanelTranslation() { - setTranslationX(mVerticalPanelTranslation + mAntiBurnInOffsetX * mInterpolatedDarkAmount); + setTranslationX(mHorizontalPanelTranslation + mAntiBurnInOffsetX * mInterpolatedDarkAmount); } @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - public void setVerticalPanelTranslation(float verticalPanelTranslation) { - mVerticalPanelTranslation = verticalPanelTranslation; + public void setHorizontalPanelTranslation(float verticalPanelTranslation) { + mHorizontalPanelTranslation = verticalPanelTranslation; updatePanelTranslation(); } @@ -4411,9 +4423,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd boolean nowDarkAtAll = mAmbientState.isDarkAtAll(); if (nowFullyDark != wasFullyDark) { updateContentHeight(); - } - if (mIconAreaController != null) { - mIconAreaController.setDarkAmount(interpolatedDarkAmount); + if (nowFullyDark && mShowDarkShelf) { + updateDarkShelfVisibility(); + } } if (!wasDarkAtAll && nowDarkAtAll) { resetExposedMenuView(true /* animate */, true /* animate */); @@ -4424,6 +4436,22 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd requestChildrenUpdate(); } + /** + * If the shelf should be visible when the device is in ambient mode (dozing.) + */ + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) + public void setShowDarkShelf(boolean showDarkShelf) { + mShowDarkShelf = showDarkShelf; + } + + private void updateDarkShelfVisibility() { + DozeParameters dozeParameters = DozeParameters.getInstance(mContext); + if (dozeParameters.shouldControlScreenOff()) { + mShelf.fadeInTranslating(); + } + updateClipping(); + } + @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) public void notifyDarkAnimationStart(boolean dark) { // We only swap the scaling factor if we're fully dark or fully awake to avoid @@ -4705,7 +4733,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd mHeadsUpManager.setAnimationStateHandler(this::setHeadsUpGoingAwayAnimationsAllowed); } - public void generateHeadsUpAnimation(NotificationData.Entry entry, boolean isHeadsUp) { + public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) { ExpandableNotificationRow row = entry.getHeadsUpAnimationView(); generateHeadsUpAnimation(row, isHeadsUp); } @@ -5183,9 +5211,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd // another "changeViewPosition" call is ever added. changeViewPosition(mShelf, getChildCount() - offsetFromEnd); - - // Scrim opacity varies based on notification count - mScrimController.setNotificationCount(getNotGoneChildCount()); } @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) @@ -5603,8 +5628,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } }; + @VisibleForTesting @ShadeViewRefactor(RefactorComponent.INPUT) - private final OnMenuEventListener mMenuEventListener = new OnMenuEventListener() { + protected final OnMenuEventListener mMenuEventListener = new OnMenuEventListener() { @Override public void onMenuClicked(View view, int x, int y, MenuItem item) { if (mLongPressListener == null) { @@ -5612,8 +5638,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } if (view instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) view; - MetricsLogger.action(mContext, MetricsEvent.ACTION_TOUCH_GEAR, - row.getStatusBarNotification().getPackageName()); + mMetricsLogger.write(row.getStatusBarNotification().getLogMaker() + .setCategory(MetricsEvent.ACTION_TOUCH_GEAR) + .setType(MetricsEvent.TYPE_ACTION) + ); } mLongPressListener.onLongPress(view, x, y, item); } @@ -5636,8 +5664,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd public void onMenuShown(View row) { if (row instanceof ExpandableNotificationRow) { ExpandableNotificationRow notificationRow = (ExpandableNotificationRow) row; - MetricsLogger.action(mContext, MetricsEvent.ACTION_REVEAL_GEAR, - notificationRow.getStatusBarNotification().getPackageName()); + mMetricsLogger.write(notificationRow.getStatusBarNotification().getLogMaker() + .setCategory(MetricsEvent.ACTION_REVEAL_GEAR) + .setType(MetricsEvent.TYPE_ACTION)); mHeadsUpManager.setMenuShown(notificationRow.getEntry(), true); } mSwipeHelper.onMenuShown(row); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index 19fce485b0c7..b4c205ab980c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java @@ -426,6 +426,9 @@ public class StackStateAnimator { mTmpState.yTranslation += mPulsingAppearingTranslation; mTmpState.alpha = 0; mTmpState.applyToView(changingView); + + mTmpState.copyFrom(mShelf.getViewState()); + mTmpState.applyToView(mShelf); } } else if (event.animationType == NotificationStackScrollLayout .AnimationEvent.ANIMATION_TYPE_PULSE_DISAPPEAR) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java index f907b6523bef..35763dce5529 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java @@ -215,7 +215,10 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } } - if (mStatusBarStateController.isDozing()) { + // The shelf will be hidden when dozing with a custom clock, we must show notification + // icons in this occasion. + if (mStatusBarStateController.isDozing() + && mStatusBarComponent.getPanel().hasCustomClock()) { state |= DISABLE_CLOCK | DISABLE_SYSTEM_INFO; state &= ~DISABLE_NOTIFICATION_ICONS; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java index 6f2b63d2e64d..10497734d0b1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java @@ -158,8 +158,7 @@ public class DozeParameters implements TunerService.Tunable { * @return duration in millis. */ public long getWallpaperAodDuration() { - if (mAmbientDisplayConfiguration.wakeLockScreenGestureEnabled(UserHandle.USER_CURRENT) - || shouldControlScreenOff()) { + if (shouldControlScreenOff()) { return DozeScreenState.ENTER_DOZE_HIDE_WALLPAPER_DELAY; } return mAlwaysOnPolicy.wallpaperVisibilityDuration; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java index d1e488ab2b4d..876b9028a9b6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java @@ -28,7 +28,7 @@ import com.android.systemui.R; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.HeadsUpStatusBarView; -import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; @@ -143,7 +143,7 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, } @Override - public void onHeadsUpPinned(NotificationData.Entry entry) { + public void onHeadsUpPinned(NotificationEntry entry) { updateTopEntry(); updateHeader(entry); } @@ -206,11 +206,11 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, } private void updateTopEntry() { - NotificationData.Entry newEntry = null; + NotificationEntry newEntry = null; if (!mIsExpanded && mHeadsUpManager.hasPinnedHeadsUp()) { newEntry = mHeadsUpManager.getTopEntry(); } - NotificationData.Entry previousEntry = mHeadsUpStatusBarView.getShowingEntry(); + NotificationEntry previousEntry = mHeadsUpStatusBarView.getShowingEntry(); mHeadsUpStatusBarView.setEntry(newEntry); if (newEntry != previousEntry) { boolean animateIsolation = false; @@ -298,7 +298,7 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, } @Override - public void onHeadsUpUnPinned(NotificationData.Entry entry) { + public void onHeadsUpUnPinned(NotificationEntry entry) { updateTopEntry(); updateHeader(entry); } @@ -338,7 +338,7 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, }); } - public void updateHeader(NotificationData.Entry entry) { + public void updateHeader(NotificationEntry entry) { ExpandableNotificationRow row = entry.getRow(); float headerVisibleAmount = 1.0f; if (row.isPinned() || row.isHeadsUpAnimatingAway() || row == mTrackedChild) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index f4cfd4197637..7f75223407de 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -41,8 +41,8 @@ import com.android.systemui.bubbles.BubbleController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarStateController.StateListener; -import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -58,7 +58,7 @@ import java.util.Stack; */ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, ViewTreeObserver.OnComputeInternalInsetsListener, VisualStabilityManager.Callback, - OnHeadsUpChangedListener, ConfigurationController.ConfigurationListener { + OnHeadsUpChangedListener, ConfigurationController.ConfigurationListener, StateListener { private static final String TAG = "HeadsUpManagerPhone"; private final View mStatusBarWindowView; @@ -72,8 +72,8 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, private int mDisplayCutoutTouchableRegionSize; private boolean mTrackingHeadsUp; private HashSet<String> mSwipedOutKeys = new HashSet<>(); - private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>(); - private ArraySet<NotificationData.Entry> mEntriesToRemoveWhenReorderingAllowed + private HashSet<NotificationEntry> mEntriesToRemoveAfterExpand = new HashSet<>(); + private ArraySet<NotificationEntry> mEntriesToRemoveWhenReorderingAllowed = new ArraySet<>(); private boolean mIsExpanded; private int[] mTmpTwoArray = new int[2]; @@ -83,7 +83,6 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, private boolean mIsObserving; private int mStatusBarState; - private final StateListener mStateListener = this::setStatusBarState; private AnimationStateHandler mAnimationStateHandler; private BubbleController mBubbleController = Dependency.get(BubbleController.class); @@ -129,7 +128,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, updateTouchableRegionListener(); } }); - Dependency.get(StatusBarStateController.class).addCallback(mStateListener); + Dependency.get(StatusBarStateController.class).addCallback(this); mBubbleController.setBubbleStateChangeListener((hasBubbles) -> { if (!hasBubbles) { mBubbleGoingAway = true; @@ -143,7 +142,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, } public void destroy() { - Dependency.get(StatusBarStateController.class).removeCallback(mStateListener); + Dependency.get(StatusBarStateController.class).removeCallback(this); } private void initResources() { @@ -187,7 +186,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, releaseAllImmediately(); mReleaseOnExpandFinish = false; } else { - for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) { + for (NotificationEntry entry : mEntriesToRemoveAfterExpand) { if (isAlerting(entry.key)) { // Maybe the heads-up was removed already removeAlertEntry(entry.key); @@ -225,11 +224,9 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, } } - /** - * Set the current state of the statusbar. - */ - private void setStatusBarState(int statusBarState) { - mStatusBarState = statusBarState; + @Override + public void onStateChanged(int newState) { + mStatusBarState = newState; } /** @@ -252,7 +249,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, * @param remoteInputActive True to notify active, False to notify inactive. */ public void setRemoteInputActive( - @NonNull NotificationData.Entry entry, boolean remoteInputActive) { + @NonNull NotificationEntry entry, boolean remoteInputActive) { HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(entry.key); if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) { headsUpEntry.remoteInputActive = remoteInputActive; @@ -268,7 +265,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, * Sets whether an entry's menu row is exposed and therefore it should stick in the heads up * area if it's pinned until it's hidden again. */ - public void setMenuShown(@NonNull NotificationData.Entry entry, boolean menuShown) { + public void setMenuShown(@NonNull NotificationEntry entry, boolean menuShown) { HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.key); if (headsUpEntry instanceof HeadsUpEntryPhone && entry.isRowPinned()) { ((HeadsUpEntryPhone) headsUpEntry).setMenuShownPinned(menuShown); @@ -315,9 +312,9 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, return; } if (hasPinnedHeadsUp()) { - NotificationData.Entry topEntry = getTopEntry(); + NotificationEntry topEntry = getTopEntry(); if (topEntry.isChildInGroup()) { - final NotificationData.Entry groupSummary + final NotificationEntry groupSummary = mGroupManager.getGroupSummary(topEntry.notification); if (groupSummary != null) { topEntry = groupSummary; @@ -374,7 +371,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, @Override public void onReorderingAllowed() { mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false); - for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) { + for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) { if (isAlerting(entry.key)) { // Maybe the heads-up was removed already removeAlertEntry(entry.key); @@ -399,7 +396,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, } @Override - protected boolean shouldHeadsUpBecomePinned(NotificationData.Entry entry) { + protected boolean shouldHeadsUpBecomePinned(NotificationEntry entry) { return mStatusBarState != StatusBarState.KEYGUARD && !mIsExpanded || super.shouldHeadsUpBecomePinned(entry); } @@ -488,7 +485,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, return super.isSticky() || mMenuShownPinned; } - public void setEntry(@NonNull final NotificationData.Entry entry) { + public void setEntry(@NonNull final NotificationEntry entry) { Runnable removeHeadsUpRunnable = () -> { if (!mVisualStabilityManager.isReorderingAllowed()) { mEntriesToRemoveWhenReorderingAllowed.add(entry); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java index 9c1c71a2dec8..dd200da56d20 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java @@ -21,7 +21,7 @@ import android.view.MotionEvent; import android.view.ViewConfiguration; import com.android.systemui.Gefingerpoken; -import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; @@ -83,7 +83,7 @@ public class HeadsUpTouchHelper implements Gefingerpoken { } else if (child == null && !mCallback.isExpanded()) { // We might touch above the visible heads up child, but then we still would // like to capture it. - NotificationData.Entry topEntry = mHeadsUpManager.getTopEntry(); + NotificationEntry topEntry = mHeadsUpManager.getTopEntry(); if (topEntry != null && topEntry.isRowPinned()) { mPickedChild = topEntry.getRow(); mTouchingHeadsUpView = true; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java index a81b7e572a79..c68fdd4ec4a7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java @@ -91,11 +91,6 @@ public class KeyguardClockPositionAlgorithm { private int mBurnInPreventionOffsetY; /** - * Clock vertical padding when pulsing. - */ - private int mPulsingPadding; - - /** * Doze/AOD transition amount. */ private float mDarkAmount; @@ -105,10 +100,6 @@ public class KeyguardClockPositionAlgorithm { */ private boolean mCurrentlySecure; - /** - * Dozing and receiving a notification (AOD notification.) - */ - private boolean mPulsing; private float mEmptyDragAmount; /** @@ -123,13 +114,11 @@ public class KeyguardClockPositionAlgorithm { R.dimen.burn_in_prevention_offset_x); mBurnInPreventionOffsetY = res.getDimensionPixelSize( R.dimen.burn_in_prevention_offset_y); - mPulsingPadding = res.getDimensionPixelSize( - R.dimen.widget_pulsing_bottom_padding); } public void setup(int minTopMargin, int maxShadeBottom, int notificationStackHeight, float panelExpansion, int parentHeight, int keyguardStatusHeight, float dark, - boolean secure, boolean pulsing, float emptyDragAmount) { + boolean secure, float emptyDragAmount) { mMinTopMargin = minTopMargin + mContainerTopPadding; mMaxShadeBottom = maxShadeBottom; mNotificationStackHeight = notificationStackHeight; @@ -138,7 +127,6 @@ public class KeyguardClockPositionAlgorithm { mKeyguardStatusHeight = keyguardStatusHeight; mDarkAmount = dark; mCurrentlySecure = secure; - mPulsing = pulsing; mEmptyDragAmount = emptyDragAmount; } @@ -146,7 +134,7 @@ public class KeyguardClockPositionAlgorithm { final int y = getClockY(); result.clockY = y; result.clockAlpha = getClockAlpha(y); - result.stackScrollerPadding = y + (mPulsing ? mPulsingPadding : mKeyguardStatusHeight); + result.stackScrollerPadding = y + mKeyguardStatusHeight; result.clockX = (int) interpolate(0, burnInPreventionOffsetX(), mDarkAmount); } @@ -185,9 +173,6 @@ public class KeyguardClockPositionAlgorithm { private int getClockY() { // Dark: Align the bottom edge of the clock at about half of the screen: float clockYDark = getMaxClockY() + burnInPreventionOffsetY(); - if (mPulsing) { - clockYDark -= mPulsingPadding; - } clockYDark = MathUtils.max(0, clockYDark); float clockYRegular = getExpandedClockPosition(); @@ -230,11 +215,6 @@ public class KeyguardClockPositionAlgorithm { - mBurnInPreventionOffsetX; } - @VisibleForTesting - void setPulsingPadding(int padding) { - mPulsingPadding = padding; - } - public static class Result { /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java index 927228eae20e..925a19d6f5eb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java @@ -22,7 +22,7 @@ import android.util.Log; import com.android.systemui.Dependency; import com.android.systemui.statusbar.NotificationLockscreenUserManager; -import com.android.systemui.statusbar.notification.NotificationData.KeyguardEnvironment; +import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment; import com.android.systemui.statusbar.policy.DeviceProvisionedController; public class KeyguardEnvironmentImpl implements KeyguardEnvironment { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationAssistantAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationAssistantAction.java new file mode 100644 index 000000000000..323e7761b475 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationAssistantAction.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2019 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 android.annotation.NonNull; +import android.os.Bundle; +import android.view.MotionEvent; + +import com.android.systemui.assist.AssistManager; +import com.android.systemui.recents.OverviewProxyService; + +/** + * Assistant is triggered with this action + */ +public class NavigationAssistantAction extends NavigationGestureAction { + private static final String TAG = "NavigationAssistantActions"; + + private final AssistManager mAssistManager; + + public NavigationAssistantAction(@NonNull NavigationBarView navigationBarView, + @NonNull OverviewProxyService service, AssistManager assistManager) { + super(navigationBarView, service); + mAssistManager = assistManager; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public boolean disableProxyEvents() { + return true; + } + + @Override + public void onGestureStart(MotionEvent event) { + mAssistManager.startAssist(new Bundle()); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBackAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBackAction.java index 9c8b1b1e5227..7a42b03947ae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBackAction.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBackAction.java @@ -67,7 +67,7 @@ public class NavigationBackAction extends NavigationGestureAction { @Override public boolean isEnabled() { - return !getGlobalBoolean(NavigationPrototypeController.NAVBAR_EXPERIMENTS_DISABLED); + return true; } @Override @@ -95,6 +95,11 @@ public class NavigationBackAction extends NavigationGestureAction { } } + @Override + public boolean disableProxyEvents() { + return true; + } + private void performBack() { sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK); @@ -102,8 +107,7 @@ public class NavigationBackAction extends NavigationGestureAction { } private boolean shouldExecuteBackOnUp() { - return !getGlobalBoolean(NavigationPrototypeController.NAVBAR_EXPERIMENTS_DISABLED) - && getGlobalBoolean(BACK_AFTER_END_PROP); + return getGlobalBoolean(BACK_AFTER_END_PROP); } private void sendEvent(int action, int code) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 6d97d6724d40..d3c6a1d8ee74 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -269,7 +269,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback mIsOnDefaultDisplay = mDisplayId == Display.DEFAULT_DISPLAY; } - mNavigationBarView.setComponents(mStatusBar.getPanel()); + mNavigationBarView.setComponents(mStatusBar.getPanel(), mAssistManager); mNavigationBarView.setDisabledFlags(mDisabledFlags1); mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged); mNavigationBarView.setOnTouchListener(this::onNavigationTouch); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 2fc7b78c0ca3..8bf1c58df699 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -69,6 +69,7 @@ import com.android.systemui.DockedStackExistsListener; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.SysUiServiceProvider; +import com.android.systemui.assist.AssistManager; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.statusbar.phone.NavGesture; import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper; @@ -156,6 +157,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav private QuickStepAction mQuickStepAction; private NavigationBackAction mBackAction; private QuickSwitchAction mQuickSwitchAction; + private NavigationAssistantAction mAssistantAction; /** * Helper that is responsible for showing the right toast when a disallowed activity operation @@ -366,8 +368,12 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav return mBarTransitions.getLightTransitionsController(); } - public void setComponents(NotificationPanelView panel) { + public void setComponents(NotificationPanelView panel, AssistManager assistManager) { mPanelView = panel; + if (mAssistantAction == null) { + mAssistantAction = new NavigationAssistantAction(this, mOverviewProxyService, + assistManager); + } if (mGestureHelper instanceof QuickStepController) { ((QuickStepController) mGestureHelper).setComponents(this); updateNavigationGestures(); @@ -398,6 +404,10 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav return mBackAction; case NavigationPrototypeController.ACTION_QUICKSWITCH: return mQuickSwitchAction; + case NavigationPrototypeController.ACTION_ASSISTANT: + return mAssistantAction; + case NavigationPrototypeController.ACTION_NOTHING: + return null; default: return defaultAction; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java index 8c57fc31c3b4..a5d938216f5c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java @@ -24,6 +24,7 @@ import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_ import android.annotation.NonNull; import android.content.Context; import android.graphics.Canvas; +import android.provider.Settings; import android.view.MotionEvent; import com.android.systemui.recents.OverviewProxyService; @@ -32,6 +33,9 @@ import com.android.systemui.recents.OverviewProxyService; * A gesture action that would be triggered and reassigned by {@link QuickStepController} */ public abstract class NavigationGestureAction { + private static final String ENABLE_TASK_STABILIZER_FLAG = "ENABLE_TASK_STABILIZER"; + + static private boolean sLastTaskStabilizationFlag; protected final NavigationBarView mNavigationBarView; protected final OverviewProxyService mProxySender; @@ -45,6 +49,9 @@ public abstract class NavigationGestureAction { @NonNull OverviewProxyService service) { mNavigationBarView = navigationBarView; mProxySender = service; + sLastTaskStabilizationFlag = Settings.Global.getInt( + mNavigationBarView.getContext().getContentResolver(), + ENABLE_TASK_STABILIZER_FLAG, 0) != 0; } /** @@ -74,6 +81,15 @@ public abstract class NavigationGestureAction { */ public void startGesture(MotionEvent event) { mIsActive = true; + + // Tell launcher that this action requires a stable task list or not + boolean flag = requiresStableTaskList(); + if (flag != sLastTaskStabilizationFlag) { + Settings.Global.putInt(mNavigationBarView.getContext().getContentResolver(), + ENABLE_TASK_STABILIZER_FLAG, flag ? 1 : 0); + sLastTaskStabilizationFlag = flag; + } + onGestureStart(event); } @@ -146,6 +162,13 @@ public abstract class NavigationGestureAction { */ public abstract boolean isEnabled(); + /** + * @return action requires a stable task list from launcher + */ + protected boolean requiresStableTaskList() { + return false; + } + protected void onDarkIntensityChange(float intensity) { } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java index fb6254b0e4be..a09e5858d576 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java @@ -22,7 +22,6 @@ import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; import android.provider.Settings; -import android.provider.Settings.SettingNotFoundException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -36,18 +35,20 @@ public class NavigationPrototypeController extends ContentObserver { private static final String HIDE_BACK_BUTTON_SETTING = "quickstepcontroller_hideback"; private static final String HIDE_HOME_BUTTON_SETTING = "quickstepcontroller_hidehome"; - static final String NAVBAR_EXPERIMENTS_DISABLED = "navbarexperiments_disabled"; private final String GESTURE_MATCH_SETTING = "quickstepcontroller_gesture_match_map"; public static final String NAV_COLOR_ADAPT_ENABLE_SETTING = "navbar_color_adapt_enable"; @Retention(RetentionPolicy.SOURCE) - @IntDef({ACTION_DEFAULT, ACTION_QUICKSTEP, ACTION_QUICKSCRUB, ACTION_BACK}) + @IntDef({ACTION_DEFAULT, ACTION_QUICKSTEP, ACTION_QUICKSCRUB, ACTION_BACK, + ACTION_QUICKSWITCH, ACTION_NOTHING, ACTION_ASSISTANT}) @interface GestureAction {} static final int ACTION_DEFAULT = 0; static final int ACTION_QUICKSTEP = 1; static final int ACTION_QUICKSCRUB = 2; static final int ACTION_BACK = 3; static final int ACTION_QUICKSWITCH = 4; + static final int ACTION_NOTHING = 5; + static final int ACTION_ASSISTANT = 6; private OnPrototypeChangedListener mListener; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java index 3839ed5d5d88..d364356b19fe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java @@ -31,9 +31,9 @@ import com.android.systemui.statusbar.AmbientPulseManager.OnAmbientChangedListen import com.android.systemui.statusbar.InflationTask; import com.android.systemui.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarStateController.StateListener; -import com.android.systemui.statusbar.notification.NotificationData.Entry; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationInflater.AsyncInflationTask; import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag; import com.android.systemui.statusbar.phone.NotificationGroupManager.NotificationGroup; @@ -108,7 +108,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis * @param entry notification to check * @return true if the entry was transferred to and should inflate + alert */ - public boolean isAlertTransferPending(@NonNull Entry entry) { + public boolean isAlertTransferPending(@NonNull NotificationEntry entry) { PendingAlertInfo alertInfo = mPendingAlerts.get(entry.key); return alertInfo != null && alertInfo.isStillValid(); } @@ -172,16 +172,16 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis }; @Override - public void onAmbientStateChanged(Entry entry, boolean isAmbient) { + public void onAmbientStateChanged(NotificationEntry entry, boolean isAmbient) { onAlertStateChanged(entry, isAmbient, mAmbientPulseManager); } @Override - public void onHeadsUpStateChanged(Entry entry, boolean isHeadsUp) { + public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) { onAlertStateChanged(entry, isHeadsUp, mHeadsUpManager); } - private void onAlertStateChanged(Entry entry, boolean isAlerting, + private void onAlertStateChanged(NotificationEntry entry, boolean isAlerting, AlertingNotificationManager alertManager) { if (isAlerting && mGroupManager.isSummaryOfSuppressedGroup(entry.notification)) { handleSuppressedSummaryAlerted(entry, alertManager); @@ -193,7 +193,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis // Called when a new notification has been posted but is not inflated yet. We use this to // see as early as we can if we need to abort a transfer. @Override - public void onPendingEntryAdded(Entry entry) { + public void onPendingEntryAdded(NotificationEntry entry) { String groupKey = mGroupManager.getGroupKey(entry.notification); GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(groupKey); if (groupAlertEntry != null) { @@ -204,7 +204,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis // Called when the entry's reinflation has finished. If there is an alert pending, we // then show the alert. @Override - public void onEntryReinflated(Entry entry) { + public void onEntryReinflated(NotificationEntry entry) { PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.key); if (alertInfo != null) { if (alertInfo.isStillValid()) { @@ -219,16 +219,13 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis @Override public void onEntryRemoved( - @Nullable Entry entry, - String key, - StatusBarNotification old, + @Nullable NotificationEntry entry, NotificationVisibility visibility, - boolean lifetimeExtended, boolean removedByUser) { // Removes any alerts pending on this entry. Note that this will not stop any inflation // tasks started by a transfer, so this should only be used as clean-up for when // inflation is stopped and the pending alert no longer needs to happen. - mPendingAlerts.remove(key); + mPendingAlerts.remove(entry.key); } }; @@ -244,8 +241,8 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis return 0; } int number = 0; - Iterable<Entry> values = mEntryManager.getPendingNotificationsIterator(); - for (Entry entry : values) { + Iterable<NotificationEntry> values = mEntryManager.getPendingNotificationsIterator(); + for (NotificationEntry entry : values) { if (isPendingNotificationInGroup(entry, group) && onlySummaryAlerts(entry)) { number++; } @@ -263,8 +260,8 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis if (mEntryManager == null) { return false; } - Iterable<Entry> values = mEntryManager.getPendingNotificationsIterator(); - for (Entry entry : values) { + Iterable<NotificationEntry> values = mEntryManager.getPendingNotificationsIterator(); + for (NotificationEntry entry : values) { if (isPendingNotificationInGroup(entry, group)) { return true; } @@ -279,7 +276,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis * @param group group to check * @return true if the notification will add to the group, false o/w */ - private boolean isPendingNotificationInGroup(@NonNull Entry entry, + private boolean isPendingNotificationInGroup(@NonNull NotificationEntry entry, @NonNull NotificationGroup group) { String groupKey = mGroupManager.getGroupKey(group.summary.notification); return mGroupManager.isGroupChild(entry.notification) @@ -296,7 +293,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis * @param summary the summary that is suppressed and alerting * @param alertManager the alert manager that manages the alerting summary */ - private void handleSuppressedSummaryAlerted(@NonNull Entry summary, + private void handleSuppressedSummaryAlerted(@NonNull NotificationEntry summary, @NonNull AlertingNotificationManager alertManager) { StatusBarNotification sbn = summary.notification; GroupAlertEntry groupAlertEntry = @@ -312,7 +309,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis return; } - Entry child = mGroupManager.getLogicalChildren(summary.notification).iterator().next(); + NotificationEntry child = mGroupManager.getLogicalChildren(summary.notification).iterator().next(); if (child != null) { if (child.getRow().keepInParent() || child.isRowRemoved() @@ -336,7 +333,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis * @param toEntry entry to transfer to * @param alertManager alert manager for the alert type */ - private void transferAlertState(@NonNull Entry fromEntry, @NonNull Entry toEntry, + private void transferAlertState(@NonNull NotificationEntry fromEntry, @NonNull NotificationEntry toEntry, @NonNull AlertingNotificationManager alertManager) { alertManager.removeNotification(fromEntry.key, true /* releaseImmediately */); alertNotificationWhenPossible(toEntry, alertManager); @@ -356,13 +353,13 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis private void checkShouldTransferBack(@NonNull GroupAlertEntry groupAlertEntry) { if (SystemClock.elapsedRealtime() - groupAlertEntry.mLastAlertTransferTime < ALERT_TRANSFER_TIMEOUT) { - Entry summary = groupAlertEntry.mGroup.summary; + NotificationEntry summary = groupAlertEntry.mGroup.summary; AlertingNotificationManager alertManager = getActiveAlertManager(); if (!onlySummaryAlerts(summary)) { return; } - ArrayList<Entry> children = mGroupManager.getLogicalChildren(summary.notification); + ArrayList<NotificationEntry> children = mGroupManager.getLogicalChildren(summary.notification); int numChildren = children.size(); int numPendingChildren = getPendingChildrenNotAlerting(groupAlertEntry.mGroup); numChildren += numPendingChildren; @@ -371,7 +368,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis } boolean releasedChild = false; for (int i = 0; i < children.size(); i++) { - Entry entry = children.get(i); + NotificationEntry entry = children.get(i); if (onlySummaryAlerts(entry) && alertManager.isAlerting(entry.key)) { releasedChild = true; alertManager.removeNotification(entry.key, true /* releaseImmediately */); @@ -402,7 +399,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis * @param entry entry to show * @param alertManager alert manager for the alert type */ - private void alertNotificationWhenPossible(@NonNull Entry entry, + private void alertNotificationWhenPossible(@NonNull NotificationEntry entry, @NonNull AlertingNotificationManager alertManager) { @InflationFlag int contentFlag = alertManager.getContentFlag(); if (!entry.getRow().isInflationFlagSet(contentFlag)) { @@ -422,7 +419,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis return mIsDozing ? mAmbientPulseManager : mHeadsUpManager; } - private boolean onlySummaryAlerts(Entry entry) { + private boolean onlySummaryAlerts(NotificationEntry entry) { return entry.notification.getNotification().getGroupAlertBehavior() == Notification.GROUP_ALERT_SUMMARY; } @@ -442,7 +439,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis * the transfer is still valid if the notification is updated. */ final StatusBarNotification mOriginalNotification; - final Entry mEntry; + final NotificationEntry mEntry; /** * The notification is still pending inflation but we've decided that we no longer need @@ -453,7 +450,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis */ boolean mAbortOnInflation; - PendingAlertInfo(Entry entry, AlertingNotificationManager alertManager) { + PendingAlertInfo(NotificationEntry entry, AlertingNotificationManager alertManager) { mOriginalNotification = entry.notification; mEntry = entry; mAlertManager = alertManager; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java index 3c1c0765a3b8..bb9e4183ba76 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java @@ -27,7 +27,7 @@ import com.android.systemui.statusbar.AmbientPulseManager.OnAmbientChangedListen import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarStateController.StateListener; -import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; @@ -97,7 +97,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, } } - public void onEntryRemoved(NotificationData.Entry removed) { + public void onEntryRemoved(NotificationEntry removed) { onEntryRemovedInternal(removed, removed.notification); mIsolatedEntries.remove(removed.key); } @@ -109,7 +109,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, * @param sbn the notification the entry has, which doesn't need to be the same as it's internal * notification */ - private void onEntryRemovedInternal(NotificationData.Entry removed, + private void onEntryRemovedInternal(NotificationEntry removed, final StatusBarNotification sbn) { String groupKey = getGroupKey(sbn); final NotificationGroup group = mGroupMap.get(groupKey); @@ -136,7 +136,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, } } - public void onEntryAdded(final NotificationData.Entry added) { + public void onEntryAdded(final NotificationEntry added) { if (added.isRowRemoved()) { added.setDebugThrowable(new Throwable()); } @@ -152,7 +152,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, } } if (isGroupChild) { - NotificationData.Entry existing = group.children.get(added.key); + NotificationEntry existing = group.children.get(added.key); if (existing != null && existing != added) { Throwable existingThrowable = existing.getDebugThrowable(); Log.wtf(TAG, "Inconsistent entries found with the same key " + added.key @@ -169,9 +169,9 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, group.expanded = added.areChildrenExpanded(); updateSuppression(group); if (!group.children.isEmpty()) { - ArrayList<NotificationData.Entry> childrenCopy + ArrayList<NotificationEntry> childrenCopy = new ArrayList<>(group.children.values()); - for (NotificationData.Entry child : childrenCopy) { + for (NotificationEntry child : childrenCopy) { onEntryBecomingChild(child); } for (OnGroupChangeListener listener : mListeners) { @@ -181,7 +181,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, } } - private void onEntryBecomingChild(NotificationData.Entry entry) { + private void onEntryBecomingChild(NotificationEntry entry) { if (shouldIsolate(entry)) { isolateNotification(entry); } @@ -221,7 +221,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, return count; } - private NotificationData.Entry getIsolatedChild(String groupKey) { + private NotificationEntry getIsolatedChild(String groupKey) { for (StatusBarNotification sbn : mIsolatedEntries.values()) { if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn)) { return mGroupMap.get(sbn.getKey()).summary; @@ -230,7 +230,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, return null; } - public void onEntryUpdated(NotificationData.Entry entry, + public void onEntryUpdated(NotificationEntry entry, StatusBarNotification oldNotification) { String oldKey = oldNotification.getGroupKey(); String newKey = entry.notification.getGroupKey(); @@ -267,7 +267,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, if (!isOnlyChild(sbn)) { return false; } - NotificationData.Entry logicalGroupSummary = getLogicalGroupSummary(sbn); + NotificationEntry logicalGroupSummary = getLogicalGroupSummary(sbn); return logicalGroupSummary != null && !logicalGroupSummary.notification.equals(sbn); } @@ -343,7 +343,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, * Get the summary of a specified status bar notification. For isolated notification this return * itself. */ - public NotificationData.Entry getGroupSummary(StatusBarNotification sbn) { + public NotificationEntry getGroupSummary(StatusBarNotification sbn) { return getGroupSummary(getGroupKey(sbn)); } @@ -352,12 +352,12 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, * but the logical summary, i.e when a child is isolated, it still returns the summary as if * it wasn't isolated. */ - public NotificationData.Entry getLogicalGroupSummary(StatusBarNotification sbn) { + public NotificationEntry getLogicalGroupSummary(StatusBarNotification sbn) { return getGroupSummary(sbn.getGroupKey()); } @Nullable - private NotificationData.Entry getGroupSummary(String groupKey) { + private NotificationEntry getGroupSummary(String groupKey) { NotificationGroup group = mGroupMap.get(groupKey); //TODO: see if this can become an Entry return group == null ? null @@ -371,13 +371,13 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, * @param summary summary of a group * @return list of the children */ - public ArrayList<NotificationData.Entry> getLogicalChildren(StatusBarNotification summary) { + public ArrayList<NotificationEntry> getLogicalChildren(StatusBarNotification summary) { NotificationGroup group = mGroupMap.get(summary.getGroupKey()); if (group == null) { return null; } - ArrayList<NotificationData.Entry> children = new ArrayList<>(group.children.values()); - NotificationData.Entry isolatedChild = getIsolatedChild(summary.getGroupKey()); + ArrayList<NotificationEntry> children = new ArrayList<>(group.children.values()); + NotificationEntry isolatedChild = getIsolatedChild(summary.getGroupKey()); if (isolatedChild != null) { children.add(isolatedChild); } @@ -443,24 +443,24 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, } @Override - public void onHeadsUpPinned(NotificationData.Entry entry) { + public void onHeadsUpPinned(NotificationEntry entry) { } @Override - public void onHeadsUpUnPinned(NotificationData.Entry entry) { + public void onHeadsUpUnPinned(NotificationEntry entry) { } @Override - public void onAmbientStateChanged(NotificationData.Entry entry, boolean isAmbient) { + public void onAmbientStateChanged(NotificationEntry entry, boolean isAmbient) { onAlertStateChanged(entry, isAmbient); } @Override - public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) { + public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) { onAlertStateChanged(entry, isHeadsUp); } - private void onAlertStateChanged(NotificationData.Entry entry, boolean isAlerting) { + private void onAlertStateChanged(NotificationEntry entry, boolean isAlerting) { if (isAlerting) { if (shouldIsolate(entry)) { isolateNotification(entry); @@ -479,7 +479,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, * @return true if the entry should be isolated */ - private boolean shouldIsolate(NotificationData.Entry entry) { + private boolean shouldIsolate(NotificationEntry entry) { StatusBarNotification sbn = entry.notification; NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey()); if (!sbn.isGroup() || sbn.getNotification().isGroupSummary()) { @@ -499,7 +499,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, * * @param entry the notification to isolate */ - private void isolateNotification(NotificationData.Entry entry) { + private void isolateNotification(NotificationEntry entry) { StatusBarNotification sbn = entry.notification; // We will be isolated now, so lets update the groups @@ -523,7 +523,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, * * @param entry the notification to un-isolate */ - private void stopIsolatingNotification(NotificationData.Entry entry) { + private void stopIsolatingNotification(NotificationEntry entry) { StatusBarNotification sbn = entry.notification; if (mIsolatedEntries.containsKey(sbn.getKey())) { // not isolated anymore, we need to update the groups @@ -564,8 +564,8 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, } public static class NotificationGroup { - public final HashMap<String, NotificationData.Entry> children = new HashMap<>(); - public NotificationData.Entry summary; + public final HashMap<String, NotificationEntry> children = new HashMap<>(); + public NotificationEntry summary; public boolean expanded; /** * Is this notification group suppressed, i.e its summary is hidden @@ -580,7 +580,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, ? Log.getStackTraceString(summary.getDebugThrowable()) : ""); result += "\n children size: " + children.size(); - for (NotificationData.Entry child : children.values()) { + for (NotificationEntry child : children.values()) { result += "\n " + child.notification + (child.getDebugThrowable() != null ? Log.getStackTraceString(child.getDebugThrowable()) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java index 056c8a7f5f5e..077fcda70f0c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -23,9 +23,10 @@ import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StatusBarIconView; -import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationUtils; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.tuner.TunerService; @@ -36,13 +37,15 @@ import java.util.function.Function; * A controller for the space in the status bar to the left of the system icons. This area is * normally reserved for notifications. */ -public class NotificationIconAreaController implements DarkReceiver { +public class NotificationIconAreaController implements DarkReceiver, + StatusBarStateController.StateListener { public static final String LOW_PRIORITY = "low_priority"; private final ContrastColorUtil mContrastColorUtil; private final NotificationEntryManager mEntryManager; private final Runnable mUpdateStatusBarIcons = this::updateStatusBarIcons; + private final StatusBarStateController mStatusBarStateController; private final TunerService.Tunable mTunable = new TunerService.Tunable() { @Override public void onTuningChanged(String key, String newValue) { @@ -86,11 +89,14 @@ public class NotificationIconAreaController implements DarkReceiver { private final ViewClippingUtil.ClippingParameters mClippingParameters = view -> view instanceof StatusBarWindowView; - public NotificationIconAreaController(Context context, StatusBar statusBar) { + public NotificationIconAreaController(Context context, StatusBar statusBar, + StatusBarStateController statusBarStateController) { mStatusBar = statusBar; mContrastColorUtil = ContrastColorUtil.getInstance(context); mContext = context; mEntryManager = Dependency.get(NotificationEntryManager.class); + mStatusBarStateController = statusBarStateController; + mStatusBarStateController.addCallback(this); Dependency.get(TunerService.class).addTunable(mTunable, LOW_PRIORITY); @@ -182,7 +188,7 @@ public class NotificationIconAreaController implements DarkReceiver { return mStatusBar.getStatusBarHeight(); } - protected boolean shouldShowNotificationIcon(NotificationData.Entry entry, + protected boolean shouldShowNotificationIcon(NotificationEntry entry, boolean showAmbient, boolean showLowPriority, boolean hideDismissed, boolean hideRepliedMessages) { if (mEntryManager.getNotificationData().isAmbient(entry.key) && !showAmbient) { @@ -247,7 +253,7 @@ public class NotificationIconAreaController implements DarkReceiver { * @param hideDismissed should dismissed icons be hidden * @param hideRepliedMessages should messages that have been replied to be hidden */ - private void updateIconsForLayout(Function<NotificationData.Entry, StatusBarIconView> function, + private void updateIconsForLayout(Function<NotificationEntry, StatusBarIconView> function, NotificationIconContainer hostLayout, boolean showAmbient, boolean showLowPriority, boolean hideDismissed, boolean hideRepliedMessages) { ArrayList<StatusBarIconView> toShow = new ArrayList<>( @@ -257,7 +263,7 @@ public class NotificationIconAreaController implements DarkReceiver { for (int i = 0; i < mNotificationScrollLayout.getChildCount(); i++) { View view = mNotificationScrollLayout.getChildAt(i); if (view instanceof ExpandableNotificationRow) { - NotificationData.Entry ent = ((ExpandableNotificationRow) view).getEntry(); + NotificationEntry ent = ((ExpandableNotificationRow) view).getEntry(); if (shouldShowNotificationIcon(ent, showAmbient, showLowPriority, hideDismissed, hideRepliedMessages)) { toShow.add(function.apply(ent)); @@ -373,24 +379,6 @@ public class NotificationIconAreaController implements DarkReceiver { v.setDecorColor(mIconTint); } - /** - * Dark amount, from 0 to 1, representing being awake or in AOD. - */ - public void setDarkAmount(float darkAmount) { - mDarkAmount = darkAmount; - if (darkAmount == 0 || darkAmount == 1) { - ViewClippingUtil.setClippingDeactivated(mNotificationIcons, darkAmount != 0, - mClippingParameters); - } - dozeTimeTick(); - - boolean fullyDark = darkAmount == 1f; - if (mFullyDark != fullyDark) { - mFullyDark = fullyDark; - updateShelfIcons(); - } - } - public void setDark(boolean dark) { mNotificationIcons.setDark(dark, false, 0); mShelfIcons.setDark(dark, false, 0); @@ -408,10 +396,45 @@ public class NotificationIconAreaController implements DarkReceiver { * Moves icons whenever the device wakes up in AOD, to avoid burn in. */ public void dozeTimeTick() { + if (mNotificationIcons.getVisibility() != View.VISIBLE) { + return; + } + + if (mDarkAmount == 0 && !mStatusBarStateController.isDozing()) { + mNotificationIcons.setTranslationX(0); + mNotificationIcons.setTranslationY(0); + return; + } + int yOffset = (mKeyguardStatusBarHeight - getHeight()) / 2; int translationX = getBurnInOffset(mBurnInOffset, true /* xAxis */); int translationY = getBurnInOffset(mBurnInOffset, false /* xAxis */) + yOffset; - mNotificationIcons.setTranslationX(translationX * mDarkAmount); - mNotificationIcons.setTranslationY(translationY * mDarkAmount); + mNotificationIcons.setTranslationX(translationX); + mNotificationIcons.setTranslationY(translationY); + } + + @Override + public void onDozingChanged(boolean isDozing) { + dozeTimeTick(); + } + + @Override + public void onDozeAmountChanged(float linear, float eased) { + boolean wasOrIsAwake = mDarkAmount == 0 || linear == 0; + boolean wasOrIsDozing = mDarkAmount == 1 || linear == 1; + mDarkAmount = linear; + if (wasOrIsAwake) { + ViewClippingUtil.setClippingDeactivated(mNotificationIcons, mDarkAmount != 0, + mClippingParameters); + } + if (wasOrIsAwake || wasOrIsDozing) { + dozeTimeTick(); + } + + boolean fullyDark = mDarkAmount == 1f; + if (mFullyDark != fullyDark) { + mFullyDark = fullyDark; + updateShelfIcons(); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index 964b2210dd18..009afca5b034 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -128,6 +128,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { } }.setDuration(CONTENT_FADE_DURATION); + private static final int MAX_VISIBLE_ICONS_WHEN_DARK = 5; public static final int MAX_STATIC_ICONS = 4; private static final int MAX_DOTS = 1; @@ -371,7 +372,8 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { float translationX = getActualPaddingStart(); int firstOverflowIndex = -1; int childCount = getChildCount(); - int maxVisibleIcons = mIsStaticLayout ? MAX_STATIC_ICONS : childCount; + int maxVisibleIcons = mDark ? MAX_VISIBLE_ICONS_WHEN_DARK : + mIsStaticLayout ? MAX_STATIC_ICONS : childCount; float layoutEnd = getLayoutEnd(); float overflowStart = getMaxOverflowStart(); mVisualOverflowStart = 0; @@ -387,6 +389,9 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { boolean forceOverflow = mSpeedBumpIndex != -1 && i >= mSpeedBumpIndex && iconState.iconAppearAmount > 0.0f || i >= maxVisibleIcons; boolean noOverflowAfter = i == childCount - 1; + float drawingScale = mDark && view instanceof StatusBarIconView + ? ((StatusBarIconView) view).getIconScaleFullyDark() + : 1f; if (mOpenedAmount != 0.0f) { noOverflowAfter = noOverflowAfter && !hasAmbient && !forceOverflow; } @@ -402,7 +407,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { mVisualOverflowStart = Math.min(translationX, mVisualOverflowStart); } } - translationX += iconState.iconAppearAmount * view.getWidth(); + translationX += iconState.iconAppearAmount * view.getWidth() * drawingScale; } mNumDots = 0; if (firstOverflowIndex != -1) { @@ -432,6 +437,32 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { mFirstVisibleIconState = mIconStates.get(getChildAt(0)); } + boolean center = mDark; + if (center && translationX < getLayoutEnd()) { + float initialTranslation = + mFirstVisibleIconState == null ? 0 : mFirstVisibleIconState.xTranslation; + + float contentWidth = 0; + if (mLastVisibleIconState != null) { + contentWidth = mLastVisibleIconState.xTranslation + mIconSize; + contentWidth = Math.min(getWidth(), contentWidth) - initialTranslation; + } + float availableSpace = getLayoutEnd() - getActualPaddingStart(); + float delta = (availableSpace - contentWidth) / 2; + + if (firstOverflowIndex != -1) { + // If we have an overflow, only count those half for centering because the dots + // don't have a lot of visual weight. + float deltaIgnoringOverflow = (getLayoutEnd() - mVisualOverflowStart) / 2; + delta = (deltaIgnoringOverflow + delta) / 2; + } + for (int i = 0; i < childCount; i++) { + View view = getChildAt(i); + IconState iconState = mIconStates.get(view); + iconState.xTranslation += delta; + } + } + if (isLayoutRtl()) { for (int i = 0; i < childCount; i++) { View view = getChildAt(i); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 16576ad6e3e1..0d5ebb9b6578 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -50,6 +50,7 @@ import android.view.WindowInsets; import android.view.accessibility.AccessibilityManager; import android.widget.FrameLayout; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.keyguard.KeyguardClockSwitch; @@ -76,9 +77,9 @@ import com.android.systemui.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarStateController.StateListener; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.AnimatableProperty; -import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.PropertyAnimator; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; @@ -140,9 +141,11 @@ public class NotificationPanelView extends PanelView implements private KeyguardAffordanceHelper mAffordanceHelper; private KeyguardUserSwitcher mKeyguardUserSwitcher; private KeyguardStatusBarView mKeyguardStatusBar; + private ViewGroup mBigClockContainer; private QS mQs; private FrameLayout mQsFrame; - private KeyguardStatusView mKeyguardStatusView; + @VisibleForTesting + protected KeyguardStatusView mKeyguardStatusView; private View mQsNavbarScrim; protected NotificationsQuickSettingsContainer mNotificationContainerParent; protected NotificationStackScrollLayout mNotificationStackScroller; @@ -327,6 +330,13 @@ public class NotificationPanelView extends PanelView implements mDisplayId = context.getDisplayId(); } + /** + * Returns if there's a custom clock being presented. + */ + public boolean hasCustomClock() { + return mKeyguardStatusView.hasCustomClock(); + } + private void setStatusBar(StatusBar bar) { mStatusBar = bar; mKeyguardBottomArea.setStatusBar(mStatusBar); @@ -339,8 +349,8 @@ public class NotificationPanelView extends PanelView implements mKeyguardStatusView = findViewById(R.id.keyguard_status_view); KeyguardClockSwitch keyguardClockSwitch = findViewById(R.id.keyguard_clock_container); - ViewGroup bigClockContainer = findViewById(R.id.big_clock_container); - keyguardClockSwitch.setBigClockContainer(bigClockContainer); + mBigClockContainer = findViewById(R.id.big_clock_container); + keyguardClockSwitch.setBigClockContainer(mBigClockContainer); mNotificationContainerParent = findViewById(R.id.notification_container_parent); mNotificationStackScroller = findViewById(R.id.notification_stack_scroller); @@ -569,18 +579,23 @@ public class NotificationPanelView extends PanelView implements mKeyguardStatusView.getHeight(), mInterpolatedDarkAmount, mStatusBar.isKeyguardCurrentlySecure(), - mPulsing, mEmptyDragAmount); mClockPositionAlgorithm.run(mClockPositionResult); PropertyAnimator.setProperty(mKeyguardStatusView, AnimatableProperty.X, mClockPositionResult.clockX, CLOCK_ANIMATION_PROPERTIES, animateClock); PropertyAnimator.setProperty(mKeyguardStatusView, AnimatableProperty.Y, mClockPositionResult.clockY, CLOCK_ANIMATION_PROPERTIES, animateClock); + // Move big clock up while pulling up the bouncer + PropertyAnimator.setProperty(mBigClockContainer, AnimatableProperty.Y, + MathUtils.lerp(-mBigClockContainer.getHeight(), 0, + Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(getExpandedFraction())), + CLOCK_ANIMATION_PROPERTIES, animateClock); updateClock(); stackScrollerPadding = mClockPositionResult.stackScrollerPadding; } mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding); - mNotificationStackScroller.setAntiBurnInOffsetX(mClockPositionResult.clockX); + int burnInXOffset = mPulsing ? 0 : mClockPositionResult.clockX; + mNotificationStackScroller.setAntiBurnInOffsetX(burnInXOffset); mStackScrollerMeasuringPass++; requestScrollerTopPaddingUpdate(animate); @@ -956,7 +971,7 @@ public class NotificationPanelView extends PanelView implements handled = true; } handled |= super.onTouchEvent(event); - return mDozing ? handled : true; + return !mDozing || mPulsing || handled; } private boolean handleQsTouch(MotionEvent event) { @@ -1233,7 +1248,7 @@ public class NotificationPanelView extends PanelView implements updateDozingVisibilities(false /* animate */); } - resetVerticalPanelPosition(); + resetHorizontalPanelPosition(); updateQsState(); } @@ -2043,7 +2058,7 @@ public class NotificationPanelView extends PanelView implements super.onConfigurationChanged(newConfig); mAffordanceHelper.onConfigurationChanged(); if (newConfig.orientation != mLastOrientation) { - resetVerticalPanelPosition(); + resetHorizontalPanelPosition(); } mLastOrientation = newConfig.orientation; } @@ -2489,12 +2504,12 @@ public class NotificationPanelView extends PanelView implements } @Override - public void onHeadsUpPinned(NotificationData.Entry entry) { + public void onHeadsUpPinned(NotificationEntry entry) { mNotificationStackScroller.generateHeadsUpAnimation(entry.getHeadsUpAnimationView(), true); } @Override - public void onHeadsUpUnPinned(NotificationData.Entry entry) { + public void onHeadsUpUnPinned(NotificationEntry entry) { // When we're unpinning the notification via active edge they remain heads-upped, // we need to make sure that an animation happens in this case, otherwise the notification @@ -2507,7 +2522,7 @@ public class NotificationPanelView extends PanelView implements } @Override - public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) { + public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) { mNotificationStackScroller.generateHeadsUpAnimation(entry, isHeadsUp); } @@ -2529,7 +2544,7 @@ public class NotificationPanelView extends PanelView implements @Override protected void onClosingFinished() { super.onClosingFinished(); - resetVerticalPanelPosition(); + resetHorizontalPanelPosition(); setClosingWithAlphaFadeout(false); } @@ -2546,7 +2561,7 @@ public class NotificationPanelView extends PanelView implements */ protected void updateVerticalPanelPosition(float x) { if (mNotificationStackScroller.getWidth() * 1.75f > getWidth()) { - resetVerticalPanelPosition(); + resetHorizontalPanelPosition(); return; } float leftMost = mPositionMinSideMargin + mNotificationStackScroller.getWidth() / 2; @@ -2556,16 +2571,17 @@ public class NotificationPanelView extends PanelView implements x = getWidth() / 2; } x = Math.min(rightMost, Math.max(leftMost, x)); - setVerticalPanelTranslation(x - - (mNotificationStackScroller.getLeft() + mNotificationStackScroller.getWidth() / 2)); + float center = + mNotificationStackScroller.getLeft() + mNotificationStackScroller.getWidth() / 2; + setHorizontalPanelTranslation(x - center); } - private void resetVerticalPanelPosition() { - setVerticalPanelTranslation(0f); + private void resetHorizontalPanelPosition() { + setHorizontalPanelTranslation(0f); } - protected void setVerticalPanelTranslation(float translation) { - mNotificationStackScroller.setVerticalPanelTranslation(translation); + protected void setHorizontalPanelTranslation(float translation) { + mNotificationStackScroller.setHorizontalPanelTranslation(translation); mQsFrame.setTranslationX(translation); int size = mVerticalTranslationListener.size(); for (int i = 0; i < size; i++) { @@ -2773,6 +2789,9 @@ public class NotificationPanelView extends PanelView implements if (dozing == mDozing) return; mDozing = dozing; mNotificationStackScroller.setDark(mDozing, animate, wakeUpTouchLocation); + if (mDozing) { + mNotificationStackScroller.setShowDarkShelf(!hasCustomClock()); + } if (mBarState == StatusBarState.KEYGUARD || mBarState == StatusBarState.SHADE_LOCKED) { @@ -2811,7 +2830,7 @@ public class NotificationPanelView extends PanelView implements mAnimateNextPositionUpdate = false; } mNotificationStackScroller.setPulsing(pulsing, animatePulse); - mKeyguardStatusView.setPulsing(pulsing, animatePulse); + mKeyguardStatusView.setPulsing(pulsing); mKeyguardBottomArea.setPulsing(pulsing, animatePulse); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java index b3996f5e092c..a697603c689b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -149,8 +149,8 @@ public abstract class PanelView extends FrameLayout { private boolean mIgnoreXTouchSlop; private boolean mExpandLatencyTracking; protected final KeyguardMonitor mKeyguardMonitor = Dependency.get(KeyguardMonitor.class); - protected final StatusBarStateController - mStatusBarStateController = Dependency.get(StatusBarStateController.class); + protected final StatusBarStateController mStatusBarStateController = + Dependency.get(StatusBarStateController.class); protected void onExpandingFinished() { mBar.onExpandingFinished(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepAction.java index b18b79e0e6d6..1999f9a8e04d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepAction.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepAction.java @@ -47,6 +47,10 @@ public class QuickStepAction extends NavigationGestureAction { return mNavigationBarView.isQuickStepSwipeUpEnabled(); } + protected boolean requiresStableTaskList() { + return true; + } + @Override public void onGestureStart(MotionEvent event) { try { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java index 0cec6371d813..9e91ab70e3de 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java @@ -34,13 +34,20 @@ import android.content.Context; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Rect; +import android.hardware.input.InputManager; import android.os.RemoteException; +import android.os.SystemClock; import android.provider.Settings; import android.util.Log; +import android.view.InputDevice; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; import android.view.ViewPropertyAnimator; +import android.view.WindowManager; +import android.view.WindowManager.LayoutParams; + import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; @@ -64,7 +71,10 @@ public class QuickStepController implements GestureHelper { /** Experiment to swipe home button left to execute a back key press */ private static final String HIDE_BACK_BUTTON_PROP = "quickstepcontroller_hideback"; + private static final String ENABLE_CLICK_THROUGH_NAV_PROP = "quickstepcontroller_clickthrough"; private static final long BACK_BUTTON_FADE_IN_ALPHA = 150; + private static final long CLICK_THROUGH_TAP_DELAY = 70; + private static final long CLICK_THROUGH_TAP_RESET_DELAY = 100; /** When the home-swipe-back gesture is disallowed, make it harder to pull */ private static final float HORIZONTAL_GESTURE_DAMPING = 0.3f; @@ -100,6 +110,9 @@ public class QuickStepController implements GestureHelper { private float mMinDragLimit; private float mDragDampeningFactor; private float mEdgeSwipeThreshold; + private boolean mClickThroughPressed; + private float mClickThroughPressX; + private float mClickThroughPressY; private NavigationGestureAction mCurrentAction; private NavigationGestureAction[] mGestureActions = new NavigationGestureAction[MAX_GESTURES]; @@ -117,6 +130,19 @@ public class QuickStepController implements GestureHelper { mOverviewEventSender = Dependency.get(OverviewProxyService.class); } + private final Runnable mClickThroughSendTap = new Runnable() { + @Override + public void run() { + sendTap(mClickThroughPressX, mClickThroughPressY); + mNavigationBarView.postDelayed(mClickThroughResetTap, CLICK_THROUGH_TAP_RESET_DELAY); + } + }; + + private final Runnable mClickThroughResetTap = () -> { + setWindowTouchable(true); + mClickThroughPressed = false; + }; + public void setComponents(NavigationBarView navigationBarView) { mNavigationBarView = navigationBarView; @@ -320,6 +346,25 @@ public class QuickStepController implements GestureHelper { case MotionEvent.ACTION_UP: if (mCurrentAction != null) { mCurrentAction.endGesture(); + } else if (action == MotionEvent.ACTION_UP + && getBoolGlobalSetting(mContext, ENABLE_CLICK_THROUGH_NAV_PROP) + && !mClickThroughPressed) { + // Enable click through functionality where no gesture has been detected and not + // passed the drag slop so inject a touch event at the same location + // after making the navigation bar window untouchable. After a some time, the + // navigation bar will be able to take input events again + float diffX = Math.abs(event.getX() - mTouchDownX); + float diffY = Math.abs(event.getY() - mTouchDownY); + + if ((diffX <= NavigationBarCompat.getQuickStepDragSlopPx() + && diffY <= NavigationBarCompat.getQuickStepDragSlopPx())) { + setWindowTouchable(false); + mClickThroughPressX = event.getRawX(); + mClickThroughPressY = event.getRawY(); + mClickThroughPressed = true; + mNavigationBarView.postDelayed(mClickThroughSendTap, + CLICK_THROUGH_TAP_DELAY); + } } // Return the hit target back to its original position @@ -350,6 +395,19 @@ public class QuickStepController implements GestureHelper { return mCurrentAction != null || deadZoneConsumed; } + private void setWindowTouchable(boolean flag) { + final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) + ((ViewGroup) mNavigationBarView.getParent()).getLayoutParams(); + if (flag) { + lp.flags &= ~LayoutParams.FLAG_NOT_TOUCHABLE; + } else { + lp.flags |= LayoutParams.FLAG_NOT_TOUCHABLE; + } + final WindowManager wm = (WindowManager) mNavigationBarView.getContext() + .getSystemService(Context.WINDOW_SERVICE); + wm.updateViewLayout((View) mNavigationBarView.getParent(), lp); + } + private boolean isEdgeSwipeAlongNavBar(int touchDown, boolean dragPositiveDirection) { // Detect edge swipe from side of 0 -> threshold if (dragPositiveDirection) { @@ -562,6 +620,38 @@ public class QuickStepController implements GestureHelper { return false; } + private void sendTap(float x, float y) { + long now = SystemClock.uptimeMillis(); + injectMotionEvent(InputDevice.SOURCE_TOUCHSCREEN, MotionEvent.ACTION_DOWN, now, x, y, 1.0f); + injectMotionEvent(InputDevice.SOURCE_TOUCHSCREEN, MotionEvent.ACTION_UP, now, x, y, 0.0f); + } + + private int getInputDeviceId(int inputSource) { + int[] devIds = InputDevice.getDeviceIds(); + for (int devId : devIds) { + InputDevice inputDev = InputDevice.getDevice(devId); + if (inputDev.supportsSource(inputSource)) { + return devId; + } + } + return 0; + } + + private void injectMotionEvent(int inputSource, int action, long when, float x, float y, + float pressure) { + final float defaultSize = 1.0f; + final int defaultMetaState = 0; + final float defaultPrecisionX = 1.0f; + final float defaultPrecisionY = 1.0f; + final int defaultEdgeFlags = 0; + MotionEvent event = MotionEvent.obtain(when, when, action, x, y, pressure, defaultSize, + defaultMetaState, defaultPrecisionX, defaultPrecisionY, + getInputDeviceId(inputSource), defaultEdgeFlags); + event.setSource(inputSource); + InputManager.getInstance().injectInputEvent(event, + InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + } + private boolean proxyMotionEvents(MotionEvent event) { final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy(); event.transform(mTransformGlobalMatrix); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index e25c8292b637..bf143c8940e5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -88,16 +88,12 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo /** * Default alpha value for most scrims. */ - public static final float GRADIENT_SCRIM_ALPHA = 0.45f; + public static final float GRADIENT_SCRIM_ALPHA = 0.2f; /** * A scrim varies its opacity based on a busyness factor, for example * how many notifications are currently visible. */ public static final float GRADIENT_SCRIM_ALPHA_BUSY = 0.7f; - /** - * Scrim opacity when a wallpaper doesn't support ambient mode. - */ - public static final float PULSING_WALLPAPER_SCRIM_ALPHA = 0.6f; /** * The most common scrim, the one under the keyguard. @@ -154,7 +150,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo private Callback mCallback; private boolean mWallpaperSupportsAmbientMode; private boolean mScreenOn; - private float mNotificationDensity; // Scrim blanking callbacks private Runnable mPendingFrameCallback; @@ -245,7 +240,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo mCurrentInFrontTint = state.getFrontTint(); mCurrentBehindTint = state.getBehindTint(); mCurrentInFrontAlpha = state.getFrontAlpha(); - mCurrentBehindAlpha = state.getBehindAlpha(mNotificationDensity); + mCurrentBehindAlpha = state.getBehindAlpha(); applyExpansionToAlpha(); // Scrim might acquire focus when user is navigating with a D-pad or a keyboard. @@ -275,9 +270,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo holdWakeLock(); } - // AOD wallpapers should fade away after a while - if (mWallpaperSupportsAmbientMode && mDozeParameters.getAlwaysOn() - && mState == ScrimState.AOD) { + // AOD wallpapers should fade away after a while. + // Docking pulses may take a long time, wallpapers should also fade away after a while. + if (mWallpaperSupportsAmbientMode && ( + mDozeParameters.getAlwaysOn() && mState == ScrimState.AOD + || mState == ScrimState.PULSING && mCallback != null)) { if (!mWallpaperVisibilityTimedOut) { mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(), AlarmTimeout.MODE_IGNORE_IF_SCHEDULED); @@ -329,7 +326,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo @VisibleForTesting protected void onHideWallpaperTimeout() { - if (mState != ScrimState.AOD) { + if (mState != ScrimState.AOD && mState != ScrimState.PULSING) { return; } @@ -364,7 +361,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo mExpansionFraction = fraction; final boolean keyguardOrUnlocked = mState == ScrimState.UNLOCKED - || mState == ScrimState.KEYGUARD; + || mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING; if (!keyguardOrUnlocked || !mExpansionAffectsAlpha) { return; } @@ -409,11 +406,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo behindFraction = (float) Math.pow(behindFraction, 0.8f); mCurrentBehindAlpha = behindFraction * GRADIENT_SCRIM_ALPHA_BUSY; mCurrentInFrontAlpha = 0; - } else if (mState == ScrimState.KEYGUARD) { + } else if (mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING) { // Either darken of make the scrim transparent when you // pull down the shade float interpolatedFract = getInterpolatedFraction(); - float alphaBehind = mState.getBehindAlpha(mNotificationDensity); + float alphaBehind = mState.getBehindAlpha(); if (mDarkenWhileDragging) { mCurrentBehindAlpha = MathUtils.lerp(GRADIENT_SCRIM_ALPHA_BUSY, alphaBehind, interpolatedFract); @@ -427,24 +424,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo } /** - * Keyguard and shade scrim opacity varies according to how many notifications are visible. - * @param notificationCount Number of visible notifications. - */ - public void setNotificationCount(int notificationCount) { - final float maxNotificationDensity = 3; - float notificationDensity = Math.min(notificationCount / maxNotificationDensity, 1f); - if (mNotificationDensity == notificationDensity) { - return; - } - mNotificationDensity = notificationDensity; - - if (mState == ScrimState.KEYGUARD) { - applyExpansionToAlpha(); - scheduleUpdate(); - } - } - - /** * Sets the given drawable as the background of the scrim that shows up behind the * notifications. */ @@ -504,7 +483,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo // We want to override the back scrim opacity for the AOD state // when it's time to fade the wallpaper away. - boolean aodWallpaperTimeout = mState == ScrimState.AOD && mWallpaperVisibilityTimedOut; + boolean aodWallpaperTimeout = (mState == ScrimState.AOD || mState == ScrimState.PULSING) + && mWallpaperVisibilityTimedOut; // We also want to hide FLAG_SHOW_WHEN_LOCKED activities under the scrim. boolean occludedKeyguard = (mState == ScrimState.PULSING || mState == ScrimState.AOD) && mKeyguardOccluded; @@ -562,8 +542,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo if (alpha == 0f) { scrim.setClickable(false); } else { - // Eat touch events (unless dozing or pulsing). - scrim.setClickable(mState != ScrimState.AOD && mState != ScrimState.PULSING); + // Eat touch events (unless dozing). + scrim.setClickable(mState != ScrimState.AOD); } updateScrim(scrim, alpha); } @@ -608,9 +588,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo anim.setStartDelay(mAnimationDelay); anim.setDuration(mAnimationDuration); anim.addListener(new AnimatorListenerAdapter() { + private Callback lastCallback = mCallback; + @Override public void onAnimationEnd(Animator animation) { - onFinished(); + onFinished(lastCallback); scrim.setTag(TAG_KEY_ANIM, null); dispatchScrimsVisible(); @@ -668,14 +650,23 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo } private void onFinished() { + onFinished(mCallback); + } + + private void onFinished(Callback callback) { if (mWakeLockHeld) { mWakeLock.release(); mWakeLockHeld = false; } - if (mCallback != null) { - mCallback.onFinished(); - mCallback = null; + + if (callback != null) { + callback.onFinished(); + + if (callback == mCallback) { + mCallback = null; + } } + // When unlocking with fingerprint, we'll fade the scrims from black to transparent. // At the end of the animation we need to remove the tint. if (mState == ScrimState.UNLOCKED) { @@ -894,7 +885,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo // Backdrop event may arrive after state was already applied, // in this case, back-scrim needs to be re-evaluated if (mState == ScrimState.AOD || mState == ScrimState.PULSING) { - float newBehindAlpha = mState.getBehindAlpha(mNotificationDensity); + float newBehindAlpha = mState.getBehindAlpha(); if (mCurrentBehindAlpha != newBehindAlpha) { mCurrentBehindAlpha = newBehindAlpha; updateScrims(); @@ -908,6 +899,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo } } + public void setPulseReason(int pulseReason) { + ScrimState.PULSING.setPulseReason(pulseReason); + } + public interface Callback { default void onStart() { } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index fb3c4aa16ef5..11a2d32c9dd6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -18,8 +18,8 @@ package com.android.systemui.statusbar.phone; import android.graphics.Color; import android.os.Trace; -import android.util.MathUtils; +import com.android.systemui.doze.DozeLog; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; @@ -57,13 +57,6 @@ public enum ScrimState { mCurrentBehindAlpha = mScrimBehindAlphaKeyguard; mCurrentInFrontAlpha = 0; } - - @Override - public float getBehindAlpha(float busynessFactor) { - return MathUtils.map(0 /* start */, 1 /* stop */, - mScrimBehindAlphaKeyguard, ScrimController.GRADIENT_SCRIM_ALPHA_BUSY, - busynessFactor); - } }, /** @@ -117,7 +110,7 @@ public enum ScrimState { } @Override - public float getBehindAlpha(float busyness) { + public float getBehindAlpha() { return mWallpaperSupportsAmbientMode && !mHasBackdrop ? 0f : 1f; } @@ -133,17 +126,17 @@ public enum ScrimState { PULSING(5) { @Override public void prepare(ScrimState previousState) { - mCurrentInFrontAlpha = 0; - mCurrentInFrontTint = Color.BLACK; - mCurrentBehindTint = Color.BLACK; + mCurrentInFrontAlpha = 0f; + if (mPulseReason == DozeLog.PULSE_REASON_NOTIFICATION + || mPulseReason == DozeLog.PULSE_REASON_DOCKING) { + mCurrentBehindAlpha = previousState.getBehindAlpha(); + mCurrentBehindTint = Color.BLACK; + } else { + mCurrentBehindAlpha = mScrimBehindAlphaKeyguard; + mCurrentBehindTint = Color.TRANSPARENT; + } mBlankScreen = mDisplayRequiresBlanking; } - - @Override - public float getBehindAlpha(float busyness) { - return mWallpaperSupportsAmbientMode && !mHasBackdrop ? 0f - : ScrimController.PULSING_WALLPAPER_SCRIM_ALPHA; - } }, /** @@ -204,6 +197,7 @@ public enum ScrimState { int mIndex; boolean mHasBackdrop; boolean mLaunchingAffordanceWithPreview; + int mPulseReason; ScrimState(int index) { mIndex = index; @@ -219,6 +213,14 @@ public enum ScrimState { public void prepare(ScrimState previousState) { } + /** + * Check if lockscreen wallpaper or music album art exists. + * @return true if lockscreen wallpaper or music album art exists. + */ + public boolean hasBackdrop() { + return mHasBackdrop; + } + public int getIndex() { return mIndex; } @@ -227,7 +229,7 @@ public enum ScrimState { return mCurrentInFrontAlpha; } - public float getBehindAlpha(float busyness) { + public float getBehindAlpha() { return mCurrentBehindAlpha; } @@ -268,6 +270,10 @@ public enum ScrimState { mAodFrontScrimAlpha = aodFrontScrimAlpha; } + public void setPulseReason(int pulseReason) { + mPulseReason = pulseReason; + } + public void setScrimBehindAlphaKeyguard(float scrimBehindAlphaKeyguard) { mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 977e33688831..1470d0f44266 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -70,6 +70,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.content.res.Resources; +import android.database.ContentObserver; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; @@ -98,6 +99,7 @@ import android.service.dreams.IDreamManager; import android.service.notification.StatusBarNotification; import android.util.DisplayMetrics; import android.util.EventLog; +import android.util.FeatureFlagUtils; import android.util.Log; import android.util.Slog; import android.view.Display; @@ -192,12 +194,12 @@ import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationAlertingManager; import com.android.systemui.statusbar.notification.NotificationClicker; -import com.android.systemui.statusbar.notification.NotificationData; -import com.android.systemui.statusbar.notification.NotificationData.Entry; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; +import com.android.systemui.statusbar.notification.NotificationListController; import com.android.systemui.statusbar.notification.NotificationRowBinder; import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -386,6 +388,7 @@ public class StatusBar extends SystemUI implements DemoMode, private NotificationGutsManager mGutsManager; protected NotificationLogger mNotificationLogger; protected NotificationEntryManager mEntryManager; + private NotificationListController mNotificationListController; private NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; private NotificationRowBinder mNotificationRowBinder; protected NotificationViewHierarchyManager mViewHierarchyManager; @@ -460,13 +463,6 @@ public class StatusBar extends SystemUI implements DemoMode, private NotificationMediaManager mMediaManager; protected NotificationLockscreenUserManager mLockscreenUserManager; protected NotificationRemoteInputManager mRemoteInputManager; - protected BubbleController mBubbleController; - private final BubbleController.BubbleExpandListener mBubbleExpandListener = - (isExpanding, amount) -> { - if (amount == 1) { - updateScrimController(); - } - }; private final BroadcastReceiver mWallpaperChangedReceiver = new BroadcastReceiver() { @Override @@ -479,8 +475,13 @@ public class StatusBar extends SystemUI implements DemoMode, WallpaperInfo info = wallpaperManager.getWallpaperInfo(UserHandle.USER_CURRENT); final boolean deviceSupportsAodWallpaper = mContext.getResources().getBoolean( com.android.internal.R.bool.config_dozeSupportsAodWallpaper); + final boolean aodImageWallpaperEnabled = FeatureFlagUtils.isEnabled(mContext, + FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED); + updateAodMaskVisibility(deviceSupportsAodWallpaper && aodImageWallpaperEnabled); + // If WallpaperInfo is null, it must be ImageWallpaper. final boolean supportsAmbientMode = deviceSupportsAodWallpaper - && info != null && info.supportsAmbientMode(); + && (info == null && aodImageWallpaperEnabled + || info != null && info.supportsAmbientMode()); mStatusBarWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode); mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode); @@ -489,7 +490,7 @@ public class StatusBar extends SystemUI implements DemoMode, private Runnable mLaunchTransitionEndRunnable; protected boolean mLaunchTransitionFadingAway; - private NotificationData.Entry mDraggedDownEntry; + private NotificationEntry mDraggedDownEntry; private boolean mLaunchCameraOnScreenTurningOn; private boolean mLaunchCameraOnFinishedGoingToSleep; private int mLastCameraLaunchSource; @@ -582,12 +583,19 @@ public class StatusBar extends SystemUI implements DemoMode, protected NotificationPresenter mPresenter; private NotificationActivityStarter mNotificationActivityStarter; private boolean mPulsing; + private ContentObserver mFeatureFlagObserver; + protected BubbleController mBubbleController; + private final BubbleController.BubbleExpandListener mBubbleExpandListener = + (isExpanding, key) -> { + mEntryManager.updateNotifications(); + updateScrimController(); + }; @Override public void onActiveStateChanged(int code, int uid, String packageName, boolean active) { mForegroundServiceController.onAppOpChanged(code, uid, packageName, active); Dependency.get(Dependency.MAIN_HANDLER).post(() -> { - mEntryManager.updateNotificationsForAppOp(code, uid, packageName, active); + mNotificationListController.updateNotificationsForAppOp(code, uid, packageName, active); }); } @@ -633,7 +641,7 @@ public class StatusBar extends SystemUI implements DemoMode, mBubbleController.setExpandListener(mBubbleExpandListener); KeyguardSliceProvider sliceProvider = KeyguardSliceProvider.getAttachedInstance(); if (sliceProvider != null) { - sliceProvider.initDependencies(); + sliceProvider.initDependencies(mMediaManager, mStatusBarStateController); } else { Log.w(TAG, "Cannot init KeyguardSliceProvider dependencies"); } @@ -698,6 +706,9 @@ public class StatusBar extends SystemUI implements DemoMode, mContext.registerReceiverAsUser(mWallpaperChangedReceiver, UserHandle.ALL, wallpaperChangedFilter, null /* broadcastPermission */, null /* scheduler */); mWallpaperChangedReceiver.onReceive(mContext, null); + mFeatureFlagObserver = new FeatureFlagObserver( + FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED /* feature */, + () -> mWallpaperChangedReceiver.onReceive(mContext, null) /* callback */); // Set up the initial notification state. This needs to happen before CommandQueue.disable() setUpPresenter(); @@ -790,7 +801,7 @@ public class StatusBar extends SystemUI implements DemoMode, mNotificationLogger.setUpWithContainer(notifListContainer); mNotificationIconAreaController = SystemUIFactory.getInstance() - .createNotificationIconAreaController(context, this); + .createNotificationIconAreaController(context, this, mStatusBarStateController); inflateShelf(); mNotificationIconAreaController.setupShelf(mNotificationShelf); @@ -1035,6 +1046,13 @@ public class StatusBar extends SystemUI implements DemoMode, mScrimController, mActivityLaunchAnimator, mStatusBarKeyguardViewManager, mNotificationAlertingManager); + mNotificationListController = + new NotificationListController( + mEntryManager, + (NotificationListContainer) mStackScroller, + mForegroundServiceController, + mDeviceProvisionedController); + mAppOpsController.addCallback(APP_OPS, this); mNotificationListener.setUpWithPresenter(mPresenter); mNotificationShelf.setOnActivatedListener(mPresenter); @@ -1047,6 +1065,7 @@ public class StatusBar extends SystemUI implements DemoMode, this, Dependency.get(BubbleController.class), mNotificationActivityStarter)); mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager); + mNotificationListController.bind(); } /** @@ -1510,21 +1529,21 @@ public class StatusBar extends SystemUI implements DemoMode, } @Override - public void onHeadsUpPinned(NotificationData.Entry entry) { + public void onHeadsUpPinned(NotificationEntry entry) { dismissVolumeDialog(); } @Override - public void onHeadsUpUnPinned(NotificationData.Entry entry) { + public void onHeadsUpUnPinned(NotificationEntry entry) { } @Override - public void onHeadsUpStateChanged(Entry entry, boolean isHeadsUp) { + public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) { mEntryManager.updateNotificationRanking(null /* rankingMap */); } @Override - public void onAmbientStateChanged(Entry entry, boolean isAmbient) { + public void onAmbientStateChanged(NotificationEntry entry, boolean isAmbient) { mEntryManager.updateNotificationRanking(null); if (isAmbient) { mDozeServiceHost.fireNotificationPulse(); @@ -2551,11 +2570,11 @@ public class StatusBar extends SystemUI implements DemoMode, }; public void resetUserExpandedStates() { - ArrayList<Entry> activeNotifications = mEntryManager.getNotificationData() + ArrayList<NotificationEntry> activeNotifications = mEntryManager.getNotificationData() .getActiveNotifications(); final int notificationCount = activeNotifications.size(); for (int i = 0; i < notificationCount; i++) { - NotificationData.Entry entry = activeNotifications.get(i); + NotificationEntry entry = activeNotifications.get(i); entry.resetUserExpansion(); } } @@ -2822,7 +2841,7 @@ public class StatusBar extends SystemUI implements DemoMode, } catch (RemoteException e) { // Ignore. } - mEntryManager.destroy(); + mNotificationListController.destroy(); // End old BaseStatusBar.destroy(). if (mStatusBarWindow != null) { mWindowManager.removeViewImmediate(mStatusBarWindow); @@ -3506,7 +3525,7 @@ public class StatusBar extends SystemUI implements DemoMode, int userId = mLockscreenUserManager.getCurrentUserId(); ExpandableNotificationRow row = null; - NotificationData.Entry entry = null; + NotificationEntry entry = null; if (expandView instanceof ExpandableNotificationRow) { entry = ((ExpandableNotificationRow) expandView).getEntry(); entry.setUserExpanded(true /* userExpanded */, true /* allowChildExpansion */); @@ -3574,10 +3593,7 @@ public class StatusBar extends SystemUI implements DemoMode, mVisualStabilityManager.setScreenOn(false); updateVisibleToUser(); - // We need to disable touch events because these might - // collapse the panel after we expanded it, and thus we would end up with a blank - // Keyguard. - mNotificationPanel.setTouchAndAnimationDisabled(true); + updateNotificationPanelTouchState(); mStatusBarWindow.cancelCurrentTouch(); if (mLaunchCameraOnFinishedGoingToSleep) { mLaunchCameraOnFinishedGoingToSleep = false; @@ -3600,13 +3616,22 @@ public class StatusBar extends SystemUI implements DemoMode, mDeviceInteractive = true; mAmbientPulseManager.releaseAllImmediately(); mVisualStabilityManager.setScreenOn(true); - mNotificationPanel.setTouchAndAnimationDisabled(false); + updateNotificationPanelTouchState(); updateVisibleToUser(); updateIsKeyguard(); mDozeServiceHost.stopDozing(); } }; + /** + * We need to disable touch events because these might + * collapse the panel after we expanded it, and thus we would end up with a blank + * Keyguard. + */ + private void updateNotificationPanelTouchState() { + mNotificationPanel.setTouchAndAnimationDisabled(!mDeviceInteractive && !mPulsing); + } + final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() { @Override public void onScreenTurningOn() { @@ -3858,6 +3883,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void pulseWhileDozing(@NonNull PulseCallback callback, int reason) { + mScrimController.setPulseReason(reason); if (reason == DozeLog.PULSE_REASON_SENSOR_LONG_PRESS) { mPowerManager.wakeUp(SystemClock.uptimeMillis(), "com.android.systemui:NODOZE"); startAssist(new Bundle()); @@ -3872,17 +3898,15 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onPulseStarted() { callback.onPulseStarted(); - if (mAmbientPulseManager.hasNotifications()) { - // Only pulse the stack scroller if there's actually something to show. - // Otherwise just show the always-on screen. - setPulsing(true); - } + updateNotificationPanelTouchState(); + setPulsing(true); } @Override public void onPulseFinished() { mPulsing = false; callback.onPulseFinished(); + updateNotificationPanelTouchState(); setPulsing(false); } @@ -3986,7 +4010,7 @@ public class StatusBar extends SystemUI implements DemoMode, } @Override - public void onDoubleTap(float screenX, float screenY) { + public void onSlpiTap(float screenX, float screenY) { if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null && mAmbientIndicationContainer.getVisibility() == View.VISIBLE) { mAmbientIndicationContainer.getLocationOnScreen(mTmpInt2); @@ -4412,4 +4436,33 @@ public class StatusBar extends SystemUI implements DemoMode, public @TransitionMode int getStatusBarMode() { return mStatusBarMode; } + + private void updateAodMaskVisibility(boolean supportsAodWallpaper) { + View mask = mStatusBarWindow.findViewById(R.id.aod_mask); + if (mask != null) { + mask.setVisibility(supportsAodWallpaper ? View.VISIBLE : View.INVISIBLE); + } + } + + private final class FeatureFlagObserver extends ContentObserver { + private final Runnable mCallback; + + FeatureFlagObserver(String feature, Runnable callback) { + this(null, feature, callback); + } + + private FeatureFlagObserver(Handler handler, String feature, Runnable callback) { + super(handler); + mCallback = callback; + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(feature), false, this); + } + + @Override + public void onChange(boolean selfChange) { + if (mCallback != null) { + mStatusBarWindow.post(mCallback); + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java index db7589d0f333..f846036248d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java @@ -36,7 +36,6 @@ import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconStat import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; -import com.android.systemui.statusbar.policy.IconLogger; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; @@ -61,7 +60,6 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu private final ArrayList<IconManager> mIconGroups = new ArrayList<>(); private final ArraySet<String> mIconBlacklist = new ArraySet<>(); - private final IconLogger mIconLogger = Dependency.get(IconLogger.class); // Points to light or dark context depending on the... context? private Context mContext; @@ -147,7 +145,6 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu int viewIndex = getViewIndex(index, holder.getTag()); boolean blocked = mIconBlacklist.contains(slot); - mIconLogger.onIconVisibility(getSlotName(index), holder.isVisible()); mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, blocked, holder)); } @@ -281,8 +278,6 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu return; } - mIconLogger.onIconHidden(slotName); - int slotIndex = getSlotIndex(slotName); List<StatusBarIconHolder> iconsToRemove = slot.getHolderListInViewOrder(); for (StatusBarIconHolder holder : iconsToRemove) { @@ -297,7 +292,6 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu if (getIcon(index, tag) == null) { return; } - mIconLogger.onIconHidden(getSlotName(index)); super.removeIcon(index, tag); int viewIndex = getViewIndex(index, 0); mIconGroups.forEach(l -> l.onRemoveIcon(viewIndex)); @@ -305,7 +299,6 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu private void handleSet(int index, StatusBarIconHolder holder) { int viewIndex = getViewIndex(index, holder.getTag()); - mIconLogger.onIconVisibility(getSlotName(index), holder.isVisible()); mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder)); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 0f8970f1069f..bb23608799f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -187,7 +187,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN); } else if (bouncerNeedsScrimming()) { mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE); - } else if (mShowing && !mDozing) { + } else if (mShowing) { if (!isWakeAndUnlocking() && !mStatusBar.isInLaunchTransition()) { mBouncer.setExpansion(expansion); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index 8d1b911b101f..4f61009095c7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -60,10 +60,10 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.NotificationActivityStarter; -import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.policy.HeadsUpUtil; import com.android.systemui.statusbar.policy.KeyguardMonitor; @@ -131,7 +131,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit mEntryManager.addNotificationEntryListener(new NotificationEntryListener() { @Override - public void onPendingEntryAdded(NotificationData.Entry entry) { + public void onPendingEntryAdded(NotificationEntry entry) { handleFullScreenIntent(entry); } }); @@ -267,7 +267,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit } } Intent fillInIntent = null; - NotificationData.Entry entry = row.getEntry(); + NotificationEntry entry = row.getEntry(); CharSequence remoteInputText = null; if (!TextUtils.isEmpty(entry.remoteInputText)) { remoteInputText = entry.remoteInputText; @@ -345,7 +345,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit }, null, false /* afterKeyguardGone */); } - private void handleFullScreenIntent(NotificationData.Entry entry) { + private void handleFullScreenIntent(NotificationEntry entry) { boolean isHeadsUped = mNotificationInterruptionStateProvider.shouldHeadsUp(entry); if (!isHeadsUped && entry.notification.getNotification().fullScreenIntent != null) { if (shouldSuppressFullScreenIntent(entry)) { @@ -413,7 +413,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit || !mActivityLaunchAnimator.isAnimationPending(); } - private boolean shouldSuppressFullScreenIntent(NotificationData.Entry entry) { + private boolean shouldSuppressFullScreenIntent(NotificationEntry entry) { if (mPresenter.isDeviceInVrMode()) { return true; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index b9372e8935ad..df7f53b3b63a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -60,12 +60,12 @@ import com.android.systemui.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.AboveShelfObserver; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.NotificationAlertingManager; -import com.android.systemui.statusbar.notification.NotificationData.Entry; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.NotificationRowBinder; import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -104,6 +104,8 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, Dependency.get(NotificationMediaManager.class); private final VisualStabilityManager mVisualStabilityManager = Dependency.get(VisualStabilityManager.class); + private final NotificationGutsManager mGutsManager = + Dependency.get(NotificationGutsManager.class); protected AmbientPulseManager mAmbientPulseManager = Dependency.get(AmbientPulseManager.class); private final NotificationPanelView mNotificationPanel; @@ -183,41 +185,35 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, Dependency.get(InitController.class).addPostInitTask(() -> { NotificationEntryListener notificationEntryListener = new NotificationEntryListener() { @Override - public void onNotificationAdded(Entry entry) { + public void onNotificationAdded(NotificationEntry entry) { // Recalculate the position of the sliding windows and the titles. mShadeController.updateAreThereNotifications(); } @Override - public void onEntryUpdated(Entry entry) { + public void onPostEntryUpdated(NotificationEntry entry) { mShadeController.updateAreThereNotifications(); } @Override public void onEntryRemoved( - @Nullable Entry entry, - String key, - StatusBarNotification old, + @Nullable NotificationEntry entry, NotificationVisibility visibility, - boolean lifetimeExtended, boolean removedByUser) { - if (!lifetimeExtended) { - StatusBarNotificationPresenter.this.onNotificationRemoved(key, old); - } + StatusBarNotificationPresenter.this.onNotificationRemoved( + entry.key, entry.notification); if (removedByUser) { maybeEndAmbientPulse(); } } }; - NotificationGutsManager gutsManager = Dependency.get(NotificationGutsManager.class); - mViewHierarchyManager.setUpWithPresenter(this, notifListContainer); mEntryManager.setUpWithPresenter(this, notifListContainer, mHeadsUpManager); mEntryManager.addNotificationEntryListener(notificationEntryListener); mEntryManager.addNotificationLifetimeExtender(mHeadsUpManager); mEntryManager.addNotificationLifetimeExtender(mAmbientPulseManager); - mEntryManager.addNotificationLifetimeExtender(gutsManager); + mEntryManager.addNotificationLifetimeExtender(mGutsManager); mEntryManager.addNotificationLifetimeExtenders( remoteInputManager.getLifetimeExtenders()); mNotificationRowBinder.setUpWithPresenter(this, notifListContainer, mHeadsUpManager, @@ -227,7 +223,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, mLockscreenUserManager.setUpWithPresenter(this); mMediaManager.setUpWithPresenter(this); mVisualStabilityManager.setUpWithPresenter(this); - gutsManager.setUpWithPresenter(this, + mGutsManager.setUpWithPresenter(this, notifListContainer, mCheckSaveListener, mOnSettingsClickListener); // ForegroundServiceControllerListener adds its listener in its constructor // but we need to request it here in order for it to be instantiated. @@ -246,7 +242,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, MessagingMessage.dropCache(); MessagingGroup.dropCache(); if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) { - mEntryManager.updateNotificationsOnDensityOrFontScaleChanged(); + updateNotificationsOnDensityOrFontScaleChanged(); } else { mReinflateNotificationsOnUserSwitched = true; } @@ -262,10 +258,10 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, } private void updateNotificationOnUiModeChanged() { - ArrayList<Entry> userNotifications + ArrayList<NotificationEntry> userNotifications = mEntryManager.getNotificationData().getNotificationsForCurrentUser(); for (int i = 0; i < userNotifications.size(); i++) { - Entry entry = userNotifications.get(i); + NotificationEntry entry = userNotifications.get(i); ExpandableNotificationRow row = entry.getRow(); if (row != null) { row.onUiModeChanged(); @@ -273,6 +269,19 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, } } + private void updateNotificationsOnDensityOrFontScaleChanged() { + ArrayList<NotificationEntry> userNotifications = + mEntryManager.getNotificationData().getNotificationsForCurrentUser(); + for (int i = 0; i < userNotifications.size(); i++) { + NotificationEntry entry = userNotifications.get(i); + entry.onDensityOrFontScaleChanged(); + boolean exposedGuts = entry.areGutsExposed(); + if (exposedGuts) { + mGutsManager.onDensityOrFontScaleChanged(entry); + } + } + } + @Override public boolean isCollapsing() { return mNotificationPanel.isCollapsing() @@ -327,7 +336,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty(); } - public boolean canHeadsUp(Entry entry, StatusBarNotification sbn) { + public boolean canHeadsUp(NotificationEntry entry, StatusBarNotification sbn) { if (mShadeController.isDozing()) { return false; } @@ -371,7 +380,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId); mCommandQueue.animateCollapsePanels(); if (mReinflateNotificationsOnUserSwitched) { - mEntryManager.updateNotificationsOnDensityOrFontScaleChanged(); + updateNotificationsOnDensityOrFontScaleChanged(); mReinflateNotificationsOnUserSwitched = false; } if (mDispatchUiModeChangeOnUserSwitched) { @@ -385,7 +394,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, } @Override - public void onBindRow(Entry entry, PackageManager pmUser, + public void onBindRow(NotificationEntry entry, PackageManager pmUser, StatusBarNotification sbn, ExpandableNotificationRow row) { row.setAboveShelfChangedListener(mAboveShelfObserver); row.setSecureStateProvider(mUnlockMethodCache::canSkipBouncer); @@ -443,7 +452,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, } @Override - public void onExpandClicked(Entry clickedEntry, boolean nowExpanded) { + public void onExpandClicked(NotificationEntry clickedEntry, boolean nowExpanded) { mHeadsUpManager.setExpanded(clickedEntry, nowExpanded); if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD && nowExpanded) { mShadeController.goToLockedShade(clickedEntry.getRow()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java index 3ddfc0c81c60..8d5841088591 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java @@ -53,7 +53,8 @@ import javax.inject.Singleton; /** */ @Singleton -public class StatusBarRemoteInputCallback implements Callback, Callbacks { +public class StatusBarRemoteInputCallback implements Callback, Callbacks, + StatusBarStateController.StateListener { private final KeyguardMonitor mKeyguardMonitor = Dependency.get(KeyguardMonitor.class); private final StatusBarStateController mStatusBarStateController @@ -63,7 +64,6 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks { private final ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class); private final Context mContext; private View mPendingWorkRemoteInputView; - private final StatusBarStateController.StateListener mStateListener = this::setStatusBarState; private View mPendingRemoteInputView; private final ShadeController mShadeController = Dependency.get(ShadeController.class); private KeyguardManager mKeyguardManager; @@ -78,13 +78,14 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks { mContext = context; mContext.registerReceiverAsUser(mChallengeReceiver, UserHandle.ALL, new IntentFilter(ACTION_DEVICE_LOCKED_CHANGED), null, null); - mStatusBarStateController.addCallback(mStateListener); + mStatusBarStateController.addCallback(this); mKeyguardManager = context.getSystemService(KeyguardManager.class); mCommandQueue = getComponent(context, CommandQueue.class); mCommandQueue.addCallback(this); } - private void setStatusBarState(int state) { + @Override + public void onStateChanged(int state) { if (state == StatusBarState.SHADE && mStatusBarStateController.leaveOpenOnKeyguardHide()) { if (!mStatusBarStateController.isKeyguardRequested()) { if (mPendingRemoteInputView != null diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java index 53e461db3dd1..8b25c3469abe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java @@ -339,7 +339,7 @@ public class StatusBarWindowView extends FrameLayout { @Override public boolean onInterceptTouchEvent(MotionEvent ev) { NotificationStackScrollLayout stackScrollLayout = getStackScrollLayout(); - if (mService.isDozing() && !stackScrollLayout.hasPulsingNotifications()) { + if (mService.isDozing() && !mService.isPulsing()) { // Capture all touch events in always-on. return true; } @@ -347,8 +347,7 @@ public class StatusBarWindowView extends FrameLayout { if (mNotificationPanel.isFullyExpanded() && stackScrollLayout.getVisibility() == View.VISIBLE && mStatusBarStateController.getState() == StatusBarState.KEYGUARD - && !mService.isBouncerShowing() - && !mService.isDozing()) { + && !mService.isBouncerShowing()) { intercept = mDragDownHelper.onInterceptTouchEvent(ev); } if (!intercept) { @@ -369,7 +368,7 @@ public class StatusBarWindowView extends FrameLayout { boolean handled = false; if (mService.isDozing()) { mDoubleTapHelper.onTouchEvent(ev); - handled = true; + handled = !mService.isPulsing(); } if ((mStatusBarStateController.getState() == StatusBarState.KEYGUARD && !handled) || mDragDownHelper.isDraggingDown()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java index f65f8261dcfb..5e94152e7eab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java @@ -48,18 +48,36 @@ public interface BatteryController extends DemoMode, Dumpable, } /** - * A listener that will be notified whenever a change in battery level or power save mode - * has occurred. + * A listener that will be notified whenever a change in battery level or power save mode has + * occurred. */ interface BatteryStateChangeCallback { - default void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {} - default void onPowerSaveChanged(boolean isPowerSave) {} + + default void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { + } + + default void onPowerSaveChanged(boolean isPowerSave) { + } } /** - * If available, get the estimated battery time remaining as a string + * If available, get the estimated battery time remaining as a string. + * + * @param completion A lambda that will be called with the result of fetching the estimate. The + * first time this method is called may need to be dispatched to a background thread. The + * completion is called on the main thread + */ + default void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) {} + + /** + * Callback called when the estimated time remaining text is fetched. */ - default String getEstimatedTimeRemainingString() { - return null; + public interface EstimateFetchCompletion { + + /** + * The callback + * @param estimate the estimate + */ + void onBatteryRemainingEstimateRetrieved(String estimate); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java index 6190c8fff8cc..af3c96f73642 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java @@ -27,9 +27,12 @@ import android.os.PowerManager; import android.os.PowerSaveState; import android.util.Log; +import androidx.annotation.Nullable; + import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.fuelgauge.BatterySaverUtils; import com.android.settingslib.utils.PowerUtil; +import com.android.systemui.Dependency; import com.android.systemui.power.EnhancedEstimates; import com.android.systemui.power.Estimate; @@ -56,6 +59,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC private final EnhancedEstimates mEstimates; private final ArrayList<BatteryController.BatteryStateChangeCallback> mChangeCallbacks = new ArrayList<>(); + private final ArrayList<EstimateFetchCompletion> mFetchCallbacks = new ArrayList<>(); private final PowerManager mPowerManager; private final Handler mHandler; private final Context mContext; @@ -70,6 +74,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC private boolean mHasReceivedBattery = false; private Estimate mEstimate; private long mLastEstimateTimestamp = -1; + private boolean mFetchingEstimate = false; @Inject public BatteryControllerImpl(Context context, EnhancedEstimates enhancedEstimates) { @@ -197,20 +202,61 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC } @Override - public String getEstimatedTimeRemainingString() { - if (mEstimate == null - || System.currentTimeMillis() > mLastEstimateTimestamp + UPDATE_GRANULARITY_MSEC) { - updateEstimate(); + public void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) { + if (mEstimate != null + && mLastEstimateTimestamp > System.currentTimeMillis() - UPDATE_GRANULARITY_MSEC) { + String percentage = generateTimeRemainingString(); + completion.onBatteryRemainingEstimateRetrieved(percentage); + return; + } + + // Need to fetch or refresh the estimate, but it may involve binder calls so offload the + // work + synchronized (mFetchCallbacks) { + mFetchCallbacks.add(completion); } - // Estimates may not exist yet even if we've checked + updateEstimateInBackground(); + } + + @Nullable + private String generateTimeRemainingString() { if (mEstimate == null) { return null; } - final String percentage = NumberFormat.getPercentInstance().format((double) mLevel / 100.0); + + String percentage = NumberFormat.getPercentInstance().format((double) mLevel / 100.0); return PowerUtil.getBatteryRemainingShortStringFormatted( mContext, mEstimate.estimateMillis); } + private void updateEstimateInBackground() { + if (mFetchingEstimate) { + // Already dispatched a fetch. It will notify all listeners when finished + return; + } + + mFetchingEstimate = true; + Dependency.get(Dependency.BG_HANDLER).post(() -> { + mEstimate = mEstimates.getEstimate(); + mLastEstimateTimestamp = System.currentTimeMillis(); + mFetchingEstimate = false; + + Dependency.get(Dependency.MAIN_HANDLER).post(this::notifyEstimateFetchCallbacks); + }); + } + + private void notifyEstimateFetchCallbacks() { + String estimate = generateTimeRemainingString(); + + synchronized (mFetchCallbacks) { + for (EstimateFetchCompletion completion : mFetchCallbacks) { + completion.onBatteryRemainingEstimateRetrieved(estimate); + } + + mFetchCallbacks.clear(); + } + } + private void updateEstimate() { mEstimate = mEstimates.getEstimate(); mLastEstimateTimestamp = System.currentTimeMillis(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java index de7ef3ba645d..4299af7daf66 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java @@ -274,7 +274,6 @@ public class Clock extends TextView implements DemoMode, Tunable, CommandQueue.C private void updateClockVisibility() { boolean visible = shouldBeVisible(); - Dependency.get(IconLogger.class).onIconVisibility("clock", visible); int visibility = visible ? View.VISIBLE : View.GONE; super.setVisibility(visibility); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java index a02c9d51fecf..fd3f680e5e77 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -30,7 +30,7 @@ import android.util.Log; import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; import com.android.systemui.statusbar.AlertingNotificationManager; -import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag; import java.io.FileDescriptor; @@ -108,11 +108,11 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { } } - protected boolean shouldHeadsUpBecomePinned(@NonNull NotificationData.Entry entry) { + protected boolean shouldHeadsUpBecomePinned(@NonNull NotificationEntry entry) { return hasFullScreenIntent(entry); } - protected boolean hasFullScreenIntent(@NonNull NotificationData.Entry entry) { + protected boolean hasFullScreenIntent(@NonNull NotificationEntry entry) { return entry.notification.getNotification().fullScreenIntent != null; } @@ -121,7 +121,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "setEntryPinned: " + isPinned); } - NotificationData.Entry entry = headsUpEntry.mEntry; + NotificationEntry entry = headsUpEntry.mEntry; if (entry.isRowPinned() != isPinned) { entry.setRowPinned(isPinned); updatePinnedMode(); @@ -141,7 +141,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { @Override protected void onAlertEntryAdded(AlertEntry alertEntry) { - NotificationData.Entry entry = alertEntry.mEntry; + NotificationEntry entry = alertEntry.mEntry; entry.setHeadsUp(true); setEntryPinned((HeadsUpEntry) alertEntry, shouldHeadsUpBecomePinned(entry)); for (OnHeadsUpChangedListener listener : mListeners) { @@ -151,7 +151,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { @Override protected void onAlertEntryRemoved(AlertEntry alertEntry) { - NotificationData.Entry entry = alertEntry.mEntry; + NotificationEntry entry = alertEntry.mEntry; entry.setHeadsUp(false); setEntryPinned((HeadsUpEntry) alertEntry, false /* isPinned */); for (OnHeadsUpChangedListener listener : mListeners) { @@ -222,7 +222,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { * Returns the top Heads Up Notification, which appears to show at first. */ @Nullable - public NotificationData.Entry getTopEntry() { + public NotificationEntry getTopEntry() { HeadsUpEntry topEntry = getTopHeadsUpEntry(); return (topEntry != null) ? topEntry.mEntry : null; } @@ -323,7 +323,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { * @return -1 if the first argument should be ranked higher than the second, 1 if the second * one should be ranked higher and 0 if they are equal. */ - public int compare(@NonNull NotificationData.Entry a, @NonNull NotificationData.Entry b) { + public int compare(@NonNull NotificationEntry a, @NonNull NotificationEntry b) { AlertEntry aEntry = getHeadsUpEntry(a.key); AlertEntry bEntry = getHeadsUpEntry(b.key); if (aEntry == null || bEntry == null) { @@ -336,7 +336,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { * Set an entry to be expanded and therefore stick in the heads up area if it's pinned * until it's collapsed again. */ - public void setExpanded(@NonNull NotificationData.Entry entry, boolean expanded) { + public void setExpanded(@NonNull NotificationEntry entry, boolean expanded) { HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.key); if (headsUpEntry != null && entry.isRowPinned()) { headsUpEntry.setExpanded(expanded); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLogger.java deleted file mode 100644 index 710e1df61404..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLogger.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2017 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.policy; - -public interface IconLogger { - - void onIconShown(String tag); - void onIconHidden(String tag); - - default void onIconVisibility(String tag, boolean visible) { - if (visible) { - onIconShown(tag); - } else { - onIconHidden(tag); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLoggerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLoggerImpl.java deleted file mode 100644 index ba6369e2c90e..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLoggerImpl.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2017 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.policy; - -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_NUM_STATUS_ICONS; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_STATUS_ICONS; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.STATUS_BAR_ICONS_CHANGED; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION; -import static com.android.systemui.Dependency.BG_LOOPER_NAME; - -import android.content.Context; -import android.metrics.LogMaker; -import android.os.Handler; -import android.os.Looper; -import android.util.ArraySet; - -import androidx.annotation.VisibleForTesting; - -import com.android.internal.logging.MetricsLogger; - -import java.util.Arrays; -import java.util.List; - -import javax.inject.Inject; -import javax.inject.Named; - -public class IconLoggerImpl implements IconLogger { - - // Minimum ms between log statements. - // NonFinalForTesting - @VisibleForTesting - protected static long MIN_LOG_INTERVAL = 1000; - - private final Context mContext; - private final Handler mHandler; - private final MetricsLogger mLogger; - private final ArraySet<String> mIcons = new ArraySet<>(); - private final List<String> mIconIndex; - private long mLastLog = System.currentTimeMillis(); - - @Inject - public IconLoggerImpl(Context context, @Named(BG_LOOPER_NAME) Looper bgLooper, - MetricsLogger logger) { - mContext = context; - mHandler = new Handler(bgLooper); - mLogger = logger; - String[] icons = mContext.getResources().getStringArray( - com.android.internal.R.array.config_statusBarIcons); - mIconIndex = Arrays.asList(icons); - doLog(); - } - - @Override - public void onIconShown(String tag) { - synchronized (mIcons) { - if (mIcons.contains(tag)) return; - mIcons.add(tag); - } - if (!mHandler.hasCallbacks(mLog)) { - mHandler.postDelayed(mLog, MIN_LOG_INTERVAL); - } - } - - @Override - public void onIconHidden(String tag) { - synchronized (mIcons) { - if (!mIcons.contains(tag)) return; - mIcons.remove(tag); - } - if (!mHandler.hasCallbacks(mLog)) { - mHandler.postDelayed(mLog, MIN_LOG_INTERVAL); - } - } - - private void doLog() { - long time = System.currentTimeMillis(); - long timeSinceLastLog = time - mLastLog; - mLastLog = time; - - ArraySet<String> icons; - synchronized (mIcons) { - icons = new ArraySet<>(mIcons); - } - mLogger.write(new LogMaker(STATUS_BAR_ICONS_CHANGED) - .setType(TYPE_ACTION) - .setLatency(timeSinceLastLog) - .addTaggedData(FIELD_NUM_STATUS_ICONS, icons.size()) - .addTaggedData(FIELD_STATUS_ICONS, getBitField(icons))); - } - - private int getBitField(ArraySet<String> icons) { - int iconsVisible = 0; - for (String icon : icons) { - int index = mIconIndex.indexOf(icon); - if (index >= 0) { - iconsVisible |= (1 << index); - } - } - return iconsVisible; - } - - private final Runnable mLog = this::doLog; -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index 2b0e708cf290..77c80dd3db57 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -545,7 +545,8 @@ public class NetworkControllerImpl extends BroadcastReceiver @VisibleForTesting void doUpdateMobileControllers() { - List<SubscriptionInfo> subscriptions = mSubscriptionManager.getActiveSubscriptionInfoList(); + List<SubscriptionInfo> subscriptions = mSubscriptionManager + .getActiveSubscriptionInfoList(true); if (subscriptions == null) { subscriptions = Collections.emptyList(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java index 7ad547afdb53..438226ae082d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java @@ -16,8 +16,7 @@ package com.android.systemui.statusbar.policy; -import com.android.systemui.statusbar.notification.NotificationData; -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; /** * A listener to heads up changes @@ -33,12 +32,12 @@ public interface OnHeadsUpChangedListener { /** * A notification was just pinned to the top. */ - default void onHeadsUpPinned(NotificationData.Entry entry) {} + default void onHeadsUpPinned(NotificationEntry entry) {} /** * A notification was just unpinned from the top. */ - default void onHeadsUpUnPinned(NotificationData.Entry entry) {} + default void onHeadsUpUnPinned(NotificationEntry entry) {} /** * A notification just became a heads up or turned back to its normal state. @@ -46,5 +45,5 @@ public interface OnHeadsUpChangedListener { * @param entry the entry of the changed notification * @param isHeadsUp whether the notification is now a headsUp notification */ - default void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {} + default void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {} } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index 866015e84af0..9c4db34b4f76 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.policy; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.app.ActivityManager; import android.app.Notification; import android.app.PendingIntent; import android.app.RemoteInput; @@ -28,6 +29,7 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.SystemClock; +import android.os.UserHandle; import android.text.Editable; import android.text.SpannedString; import android.text.TextWatcher; @@ -56,7 +58,7 @@ import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.statusbar.RemoteInputController; -import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; @@ -83,7 +85,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene private RemoteInputController mController; private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler; - private NotificationData.Entry mEntry; + private NotificationEntry mEntry; private boolean mRemoved; @@ -180,7 +182,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene } public static RemoteInputView inflate(Context context, ViewGroup root, - NotificationData.Entry entry, + NotificationEntry entry, RemoteInputController controller) { RemoteInputView v = (RemoteInputView) LayoutInflater.from(context).inflate(R.layout.remote_input, root, false); @@ -283,6 +285,11 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene focus(); } + private static UserHandle computeTextOperationUser(UserHandle notificationUser) { + return UserHandle.ALL.equals(notificationUser) + ? UserHandle.of(ActivityManager.getCurrentUser()) : notificationUser; + } + public void focus() { MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_REMOTE_INPUT_OPEN, mEntry.notification.getPackageName()); @@ -291,6 +298,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene if (mWrapper != null) { mWrapper.setRemoteInputVisible(true); } + mEditText.setTextOperationUser(computeTextOperationUser(mEntry.notification.getUser())); mEditText.setInnerFocusable(true); mEditText.mShowImeOnInputConnection = true; mEditText.setText(mEntry.remoteInputText); @@ -320,6 +328,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene mResetting = true; mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText()); + mEditText.setTextOperationUser(null); mEditText.getText().clear(); mEditText.setEnabled(true); mSendButton.setVisibility(VISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java index 6193159ec0a5..3bd0d456dbd3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.policy; import static com.android.systemui.Dependency.MAIN_HANDLER_NAME; +import android.app.RemoteInput; import android.content.Context; import android.content.res.Resources; import android.database.ContentObserver; @@ -42,14 +43,21 @@ public final class SmartReplyConstants extends ContentObserver { private static final String KEY_REQUIRES_TARGETING_P = "requires_targeting_p"; private static final String KEY_MAX_SQUEEZE_REMEASURE_ATTEMPTS = "max_squeeze_remeasure_attempts"; + private static final String KEY_EDIT_CHOICES_BEFORE_SENDING = + "edit_choices_before_sending"; + private static final String KEY_SHOW_IN_HEADS_UP = "show_in_heads_up"; private final boolean mDefaultEnabled; private final boolean mDefaultRequiresP; private final int mDefaultMaxSqueezeRemeasureAttempts; + private final boolean mDefaultEditChoicesBeforeSending; + private final boolean mDefaultShowInHeadsUp; private boolean mEnabled; private boolean mRequiresTargetingP; private int mMaxSqueezeRemeasureAttempts; + private boolean mEditChoicesBeforeSending; + private boolean mShowInHeadsUp; private final Context mContext; private final KeyValueListParser mParser = new KeyValueListParser(','); @@ -66,6 +74,10 @@ public final class SmartReplyConstants extends ContentObserver { R.bool.config_smart_replies_in_notifications_requires_targeting_p); mDefaultMaxSqueezeRemeasureAttempts = resources.getInteger( R.integer.config_smart_replies_in_notifications_max_squeeze_remeasure_attempts); + mDefaultEditChoicesBeforeSending = resources.getBoolean( + R.bool.config_smart_replies_in_notifications_edit_choices_before_sending); + mDefaultShowInHeadsUp = resources.getBoolean( + R.bool.config_smart_replies_in_notifications_show_in_heads_up); mContext.getContentResolver().registerContentObserver( Settings.Global.getUriFor(Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS), @@ -90,6 +102,9 @@ public final class SmartReplyConstants extends ContentObserver { mRequiresTargetingP = mParser.getBoolean(KEY_REQUIRES_TARGETING_P, mDefaultRequiresP); mMaxSqueezeRemeasureAttempts = mParser.getInt( KEY_MAX_SQUEEZE_REMEASURE_ATTEMPTS, mDefaultMaxSqueezeRemeasureAttempts); + mEditChoicesBeforeSending = mParser.getBoolean( + KEY_EDIT_CHOICES_BEFORE_SENDING, mDefaultEditChoicesBeforeSending); + mShowInHeadsUp = mParser.getBoolean(KEY_SHOW_IN_HEADS_UP, mDefaultShowInHeadsUp); } } @@ -113,4 +128,31 @@ public final class SmartReplyConstants extends ContentObserver { public int getMaxSqueezeRemeasureAttempts() { return mMaxSqueezeRemeasureAttempts; } + + /** + * Returns whether by tapping on a choice should let the user edit the input before it + * is sent to the app. + * + * @param remoteInputEditChoicesBeforeSending The value from + * {@link RemoteInput#getEditChoicesBeforeSending()} + */ + public boolean getEffectiveEditChoicesBeforeSending( + @RemoteInput.EditChoicesBeforeSending int remoteInputEditChoicesBeforeSending) { + switch (remoteInputEditChoicesBeforeSending) { + case RemoteInput.EDIT_CHOICES_BEFORE_SENDING_DISABLED: + return false; + case RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED: + return true; + case RemoteInput.EDIT_CHOICES_BEFORE_SENDING_AUTO: + default: + return mEditChoicesBeforeSending; + } + } + + /** + * Returns whether smart suggestions should be enabled in heads-up notifications. + */ + public boolean getShowInHeadsUp() { + return mShowInHeadsUp; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java index f85d8038ab3f..d6eff941ed70 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java @@ -36,8 +36,8 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.SmartReplyController; -import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationUtils; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.phone.KeyguardDismissUtil; import java.text.BreakIterator; @@ -194,7 +194,7 @@ public class SmartReplyView extends ViewGroup { */ public void addRepliesFromRemoteInput( SmartReplies smartReplies, - SmartReplyController smartReplyController, NotificationData.Entry entry) { + SmartReplyController smartReplyController, NotificationEntry entry) { if (smartReplies.remoteInput != null && smartReplies.pendingIntent != null) { if (smartReplies.choices != null) { for (int i = 0; i < smartReplies.choices.length; ++i) { @@ -212,7 +212,7 @@ public class SmartReplyView extends ViewGroup { * notification are shown. */ public void addSmartActions(SmartActions smartActions, - SmartReplyController smartReplyController, NotificationData.Entry entry, + SmartReplyController smartReplyController, NotificationEntry entry, HeadsUpManager headsUpManager) { int numSmartActions = smartActions.actions.size(); for (int n = 0; n < numSmartActions; n++) { @@ -235,16 +235,15 @@ public class SmartReplyView extends ViewGroup { @VisibleForTesting Button inflateReplyButton(Context context, ViewGroup root, int replyIndex, SmartReplies smartReplies, SmartReplyController smartReplyController, - NotificationData.Entry entry) { + NotificationEntry entry) { Button b = (Button) LayoutInflater.from(context).inflate( R.layout.smart_reply_button, root, false); CharSequence choice = smartReplies.choices[replyIndex]; b.setText(choice); OnDismissAction action = () -> { - // TODO(b/111437455): Also for EDIT_CHOICES_BEFORE_SENDING_AUTO, depending on flags. - if (smartReplies.remoteInput.getEditChoicesBeforeSending() - == RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED) { + if (mConstants.getEffectiveEditChoicesBeforeSending( + smartReplies.remoteInput.getEditChoicesBeforeSending())) { entry.remoteInputText = choice; mRemoteInputManager.activateRemoteInput(b, new RemoteInput[] { smartReplies.remoteInput }, smartReplies.remoteInput, @@ -289,7 +288,7 @@ public class SmartReplyView extends ViewGroup { @VisibleForTesting Button inflateActionButton(Context context, ViewGroup root, int actionIndex, SmartActions smartActions, SmartReplyController smartReplyController, - NotificationData.Entry entry, HeadsUpManager headsUpManager) { + NotificationEntry entry, HeadsUpManager headsUpManager) { Notification.Action action = smartActions.actions.get(actionIndex); Button button = (Button) LayoutInflater.from(context).inflate( R.layout.smart_action_button, root, false); @@ -311,8 +310,6 @@ public class SmartReplyView extends ViewGroup { headsUpManager.removeNotification(entry.key, true); })); - // TODO(b/119010281): handle accessibility - // Mark this as an Action button final LayoutParams lp = (LayoutParams) button.getLayoutParams(); lp.buttonType = SmartButtonType.ACTION; @@ -428,9 +425,9 @@ public class SmartReplyView extends ViewGroup { markButtonsWithPendingSqueezeStatusAs( LayoutParams.SQUEEZE_STATUS_FAILED, coveredSuggestions); - // The current button doesn't fit, so there's no point in measuring further - // buttons. - break; + // The current button doesn't fit, keep on adding lower-priority buttons in case + // any of those fit. + continue; } // The current button fits, so mark all squeezed buttons as "successfully squeezed" diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/AodMaskView.java b/packages/SystemUI/src/com/android/systemui/wallpaper/AodMaskView.java new file mode 100644 index 000000000000..52cabe278e2d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/wallpaper/AodMaskView.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2018 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.wallpaper; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.app.WallpaperManager; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.RectF; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManager.DisplayListener; +import android.util.AttributeSet; +import android.util.FeatureFlagUtils; +import android.util.Log; +import android.view.Display; +import android.view.DisplayInfo; +import android.widget.ImageView; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.Dependency; +import com.android.systemui.R; +import com.android.systemui.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.notification.AnimatableProperty; +import com.android.systemui.statusbar.notification.PropertyAnimator; +import com.android.systemui.statusbar.notification.stack.AnimationProperties; +import com.android.systemui.statusbar.phone.ScrimState; + +/** + * A view that draws mask upon either image wallpaper or music album art in AOD. + */ +public class AodMaskView extends ImageView implements StatusBarStateController.StateListener, + ImageWallpaperTransformer.TransformationListener { + private static final String TAG = AodMaskView.class.getSimpleName(); + private static final int TRANSITION_DURATION = 1000; + + private static final AnimatableProperty TRANSITION_PROGRESS = AnimatableProperty.from( + "transition_progress", + AodMaskView::setTransitionAmount, + AodMaskView::getTransitionAmount, + R.id.aod_mask_transition_progress_tag, + R.id.aod_mask_transition_progress_start_tag, + R.id.aod_mask_transition_progress_end_tag + ); + + private final AnimationProperties mTransitionProperties = new AnimationProperties(); + private final ImageWallpaperTransformer mTransformer; + private final RectF mBounds = new RectF(); + private boolean mChangingStates; + private boolean mNeedMask; + private float mTransitionAmount; + private final WallpaperManager mWallpaperManager; + private final DisplayManager mDisplayManager; + private DisplayListener mDisplayListener = new DisplayListener() { + @Override + public void onDisplayAdded(int displayId) { + } + + @Override + public void onDisplayRemoved(int displayId) { + } + + @Override + public void onDisplayChanged(int displayId) { + // We just support DEFAULT_DISPLAY currently. + if (displayId == Display.DEFAULT_DISPLAY) { + mTransformer.updateDisplayInfo(getDisplayInfo(displayId)); + } + } + }; + + public AodMaskView(Context context) { + this(context, null); + } + + public AodMaskView(Context context, AttributeSet attrs) { + this(context, attrs, null); + } + + @VisibleForTesting + public AodMaskView(Context context, AttributeSet attrs, ImageWallpaperTransformer transformer) { + super(context, attrs); + setClickable(false); + + StatusBarStateController controller = Dependency.get(StatusBarStateController.class); + if (controller != null) { + controller.addCallback(this); + } else { + Log.d(TAG, "Can not get StatusBarStateController!"); + } + + mDisplayManager = (DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE); + mDisplayManager.registerDisplayListener(mDisplayListener, null); + mWallpaperManager = + (WallpaperManager) getContext().getSystemService(Context.WALLPAPER_SERVICE); + + if (transformer == null) { + mTransformer = new ImageWallpaperTransformer(this); + mTransformer.addFilter(new ScrimFilter()); + mTransformer.addFilter(new VignetteFilter()); + mTransformer.updateOffsets(); + mTransformer.updateDisplayInfo(getDisplayInfo(Display.DEFAULT_DISPLAY)); + + mTransitionProperties.setDuration(TRANSITION_DURATION); + mTransitionProperties.setAnimationFinishListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mTransformer.setIsTransiting(false); + } + + @Override + public void onAnimationStart(Animator animation) { + mTransformer.setIsTransiting(true); + } + }); + } else { + // This part should only be hit by test cases. + mTransformer = transformer; + } + } + + private DisplayInfo getDisplayInfo(int displayId) { + DisplayInfo displayInfo = new DisplayInfo(); + mDisplayManager.getDisplay(displayId).getDisplayInfo(displayInfo); + return displayInfo; + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + mBounds.set(0, 0, w, h); + mTransformer.updateOffsets(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (mNeedMask) { + mTransformer.drawTransformedImage(canvas, null /* target */, null /* src */, mBounds); + } + } + + private boolean checkIfNeedMask() { + // We need mask for ImageWallpaper / LockScreen Wallpaper (Music album art). + return mWallpaperManager.getWallpaperInfo() == null || ScrimState.AOD.hasBackdrop(); + } + + @Override + public void onStatePreChange(int oldState, int newState) { + mChangingStates = oldState != newState; + mNeedMask = checkIfNeedMask(); + } + + @Override + public void onStatePostChange() { + mChangingStates = false; + } + + @Override + public void onStateChanged(int newState) { + } + + @Override + public void onDozingChanged(boolean isDozing) { + if (!mNeedMask) { + return; + } + + boolean enabled = checkFeatureIsEnabled(); + mTransformer.updateAmbientModeState(enabled && isDozing); + + if (enabled && !mChangingStates) { + setAnimatorProperty(isDozing); + } else { + invalidate(); + } + } + + private boolean checkFeatureIsEnabled() { + return FeatureFlagUtils.isEnabled( + getContext(), FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED); + } + + @VisibleForTesting + void setAnimatorProperty(boolean isDozing) { + PropertyAnimator.setProperty( + this, + TRANSITION_PROGRESS, + isDozing ? 1f : 0f /* newEndValue */, + mTransitionProperties, + true /* animated */); + } + + @Override + public void onTransformationUpdated() { + invalidate(); + } + + private void setTransitionAmount(float amount) { + mTransitionAmount = amount; + mTransformer.updateTransitionAmount(amount); + } + + private float getTransitionAmount() { + return mTransitionAmount; + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperFilter.java b/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperFilter.java new file mode 100644 index 000000000000..d457dac3e14f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperFilter.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2018 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.wallpaper; + +import android.animation.ValueAnimator; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.RectF; + +/** + * Abstract filter used by static image wallpaper. + */ +abstract class ImageWallpaperFilter { + protected static final boolean DEBUG = false; + + private ImageWallpaperTransformer mTransformer; + + /** + * Apply this filter to the bitmap before drawing on canvas. + * @param c The canvas that will draw to. + * @param bitmap The bitmap to apply this filter. + * @param src The subset of the bitmap to be drawn. + * @param dest The rectangle that the bitmap will be scaled/translated to fit into. + */ + public abstract void apply(@NonNull Canvas c, @Nullable Bitmap bitmap, + @Nullable Rect src, @NonNull RectF dest); + + /** + * Notifies the occurrence of built-in transition of the animation. + * @param animator The animator which was animated. + */ + public abstract void onAnimatorUpdate(ValueAnimator animator); + + /** + * Notifies the occurrence of another transition of the animation. + * @param amount The transition amount. + */ + public abstract void onTransitionAmountUpdate(float amount); + + /** + * To set the associated transformer. + * @param transformer The transformer that is associated with this filter. + */ + public void setTransformer(ImageWallpaperTransformer transformer) { + if (transformer != null) { + mTransformer = transformer; + } + } + + protected ImageWallpaperTransformer getTransformer() { + return mTransformer; + } + + /** + * Notifies the changing of the offset value of the ImageWallpaper. + * @param force True to force re-evaluate offsets. + * @param xOffset X offset of the ImageWallpaper in percentage. + * @param yOffset Y offset of the ImageWallpaper in percentage. + */ + public void onOffsetsUpdate(boolean force, float xOffset, float yOffset) { + // No-op + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperTransformer.java b/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperTransformer.java new file mode 100644 index 000000000000..25b0b0aaf60b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperTransformer.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2018 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.wallpaper; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.RectF; +import android.view.DisplayInfo; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class is used to manage the filters that will be applied. + */ +public class ImageWallpaperTransformer { + private static final String TAG = ImageWallpaperTransformer.class.getSimpleName(); + + private DisplayInfo mDisplayInfo; + private final List<ImageWallpaperFilter> mFilters; + private final TransformationListener mListener; + private boolean mIsInAmbientMode; + private boolean mIsTransiting; + + /** + * Constructor. + * @param listener A listener to inform you the transformation has updated. + */ + public ImageWallpaperTransformer(TransformationListener listener) { + mFilters = new ArrayList<>(); + mListener = listener; + } + + /** + * Claim that we want to use the specified filter. + * @param filter The filter will be used. + */ + public void addFilter(ImageWallpaperFilter filter) { + if (filter != null) { + filter.setTransformer(this); + mFilters.add(filter); + } + } + + /** + * Check if any transition is running. + * @return True if the transition is running, false otherwise. + */ + boolean isTransiting() { + return mIsTransiting; + } + + /** + * Indicate if any transition is running. <br/> + * @param isTransiting True if the transition is running. + */ + void setIsTransiting(boolean isTransiting) { + mIsTransiting = isTransiting; + } + + /** + * Check if the device is in ambient mode. + * @return True if the device is in ambient mode, false otherwise. + */ + public boolean isInAmbientMode() { + return mIsInAmbientMode; + } + + /** + * Update current state of ambient mode. + * @param isInAmbientMode Current ambient mode state. + */ + public void updateAmbientModeState(boolean isInAmbientMode) { + mIsInAmbientMode = isInAmbientMode; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + int idx = 0; + for (ImageWallpaperFilter filter : mFilters) { + sb.append(idx++).append(": ").append(filter.getClass().getSimpleName()).append("\n"); + } + if (sb.length() == 0) { + sb.append("No filters applied"); + } + return sb.toString(); + } + + /** + * Set a new display info. + * @param displayInfo New display info. + */ + public void updateDisplayInfo(DisplayInfo displayInfo) { + mDisplayInfo = displayInfo; + } + + /** + * To get current display info. + * @return Current display info. + */ + public DisplayInfo getDisplayInfo() { + return mDisplayInfo; + } + + /** + * Update the offsets with default value. + */ + public void updateOffsets() { + this.updateOffsets(true, 0f, .5f); + } + + /** + * To notify the filters that the offset of the ImageWallpaper changes. + * @param force True to force re-evaluate offsets. + * @param offsetX X offset of the ImageWallpaper in percentage. + * @param offsetY Y offset of the ImageWallpaper in percentage. + */ + public void updateOffsets(boolean force, float offsetX, float offsetY) { + mFilters.forEach(filter -> filter.onOffsetsUpdate(force, offsetX, offsetY)); + } + + /** + * Apply all specified filters to the bitmap then draw to the canvas. + * @param c The canvas that will draw to. + * @param target The bitmap to apply filters. + * @param src The subset of the bitmap to be drawn + * @param dest The rectangle that the bitmap will be scaled/translated to fit into. + */ + void drawTransformedImage(@NonNull Canvas c, @Nullable Bitmap target, + @Nullable Rect src, @NonNull RectF dest) { + mFilters.forEach(filter -> filter.apply(c, target, src, dest)); + } + + /** + * Update the transition amount. <br/> + * Must invoke this to update transition amount if not running built-in transition. + * @param amount The transition amount. + */ + void updateTransitionAmount(float amount) { + mFilters.forEach(filter -> filter.onTransitionAmountUpdate(amount)); + if (mListener != null) { + mListener.onTransformationUpdated(); + } + } + + /** + * An interface that informs the transformation status. + */ + public interface TransformationListener { + /** + * Notifies the update of the transformation. + */ + void onTransformationUpdated(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/ScrimFilter.java b/packages/SystemUI/src/com/android/systemui/wallpaper/ScrimFilter.java new file mode 100644 index 000000000000..637e48e67de6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/wallpaper/ScrimFilter.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2018 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.wallpaper; + +import android.animation.ValueAnimator; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.RectF; + +/** + * A filter that implements 70% black scrim effect. + */ +public class ScrimFilter extends ImageWallpaperFilter { + private static final int MAX_ALPHA = (int) (255 * .7f); + private static final int MIN_ALPHA = 0; + + private final Paint mPaint; + + public ScrimFilter() { + mPaint = new Paint(); + mPaint.setColor(Color.BLACK); + mPaint.setAlpha(MAX_ALPHA); + } + + @Override + public void apply(Canvas c, Bitmap bitmap, Rect src, RectF dest) { + ImageWallpaperTransformer transformer = getTransformer(); + + // If it is not in the transition, we need to set the property according to aod state. + if (!transformer.isTransiting()) { + mPaint.setAlpha(transformer.isInAmbientMode() ? MAX_ALPHA : MIN_ALPHA); + } + + c.drawRect(dest, mPaint); + } + + @Override + public void onAnimatorUpdate(ValueAnimator animator) { + ImageWallpaperTransformer transformer = getTransformer(); + float fraction = animator.getAnimatedFraction(); + float factor = transformer.isInAmbientMode() ? fraction : 1f - fraction; + mPaint.setAlpha((int) (factor * MAX_ALPHA)); + } + + @Override + public void onTransitionAmountUpdate(float amount) { + mPaint.setAlpha((int) (amount * MAX_ALPHA)); + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/VignetteFilter.java b/packages/SystemUI/src/com/android/systemui/wallpaper/VignetteFilter.java new file mode 100644 index 000000000000..ad0b98b67e68 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/wallpaper/VignetteFilter.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2018 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.wallpaper; + +import android.animation.ValueAnimator; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PointF; +import android.graphics.RadialGradient; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Shader; +import android.util.Log; +import android.view.DisplayInfo; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * A filter that implements vignette effect. + */ +public class VignetteFilter extends ImageWallpaperFilter { + private static final String TAG = VignetteFilter.class.getSimpleName(); + private static final int MAX_ALPHA = 255; + private static final int MIN_ALPHA = 0; + + private final Paint mPaint; + private final Matrix mMatrix; + private final Shader mShader; + + private float mXOffset; + private float mYOffset; + private float mCenterX; + private float mCenterY; + private float mStretchX; + private float mStretchY; + private boolean mCalculateOffsetNeeded; + + public VignetteFilter() { + mPaint = new Paint(); + mMatrix = new Matrix(); + mShader = new RadialGradient(0, 0, 1, + Color.TRANSPARENT, Color.BLACK, Shader.TileMode.CLAMP); + } + + @Override + public void apply(Canvas c, Bitmap bitmap, Rect src, RectF dest) { + DisplayInfo info = getTransformer().getDisplayInfo(); + + if (mCalculateOffsetNeeded) { + int lw = info.logicalWidth; + int lh = info.logicalHeight; + mCenterX = lw / 2 + (dest.width() - lw) * mXOffset; + mCenterY = lh / 2 + (dest.height() - lh) * mYOffset; + mStretchX = info.logicalWidth / 2; + mStretchY = info.logicalHeight / 2; + mCalculateOffsetNeeded = false; + } + + if (DEBUG) { + Log.d(TAG, "apply: lw=" + info.logicalWidth + ", lh=" + info.logicalHeight + + ", center=(" + mCenterX + "," + mCenterY + ")" + + ", stretch=(" + mStretchX + "," + mStretchY + ")"); + } + + mMatrix.reset(); + mMatrix.postTranslate(mCenterX, mCenterY); + mMatrix.postScale(mStretchX, mStretchY, mCenterX, mCenterY); + mShader.setLocalMatrix(mMatrix); + mPaint.setShader(mShader); + + ImageWallpaperTransformer transformer = getTransformer(); + + // If it is not in the transition, we need to set the property according to aod state. + if (!transformer.isTransiting()) { + mPaint.setAlpha(transformer.isInAmbientMode() ? MAX_ALPHA : MIN_ALPHA); + } + + c.drawRect(dest, mPaint); + } + + @Override + public void onAnimatorUpdate(ValueAnimator animator) { + ImageWallpaperTransformer transformer = getTransformer(); + float fraction = animator.getAnimatedFraction(); + float factor = transformer.isInAmbientMode() ? fraction : 1f - fraction; + mPaint.setAlpha((int) (factor * MAX_ALPHA)); + } + + @Override + public void onTransitionAmountUpdate(float amount) { + mPaint.setAlpha((int) (amount * MAX_ALPHA)); + } + + @Override + public void onOffsetsUpdate(boolean force, float xOffset, float yOffset) { + if (force || mXOffset != xOffset || mYOffset != yOffset) { + mXOffset = xOffset; + mYOffset = yOffset; + mCalculateOffsetNeeded = true; + } + } + + @VisibleForTesting + public PointF getCenterPoint() { + return new PointF(mCenterX, mCenterY); + } +} |