summaryrefslogtreecommitdiff
path: root/packages/SystemUI/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/SystemUI/src')
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java51
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java32
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/OverviewProxyService.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/Prefs.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIFactory.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIService.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java41
-rw-r--r--packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java69
-rw-r--r--packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.java101
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeService.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/PowerUI.java56
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/CellTileView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java114
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/TileLayout.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/touch/OverScroll.java57
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/touch/SwipeDetector.java356
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java (renamed from packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java)53
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java161
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java114
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java407
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java108
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java107
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java190
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java134
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java50
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java446
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java116
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java90
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java309
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java50
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java113
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java110
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java517
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java88
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java582
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java47
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java95
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java462
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java83
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java62
85 files changed, 3680 insertions, 2320 deletions
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index e440731dcd47..3b5f34cd84cc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -16,13 +16,16 @@
package com.android.keyguard;
+import android.app.ActivityManager;
import android.app.AlarmManager;
+import android.app.IActivityManager;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.Handler;
import android.os.Looper;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.support.v4.graphics.ColorUtils;
import android.text.TextUtils;
@@ -51,9 +54,11 @@ public class KeyguardStatusView extends GridLayout {
private final LockPatternUtils mLockPatternUtils;
private final AlarmManager mAlarmManager;
+ private final IActivityManager mIActivityManager;
private final float mSmallClockScale;
private final float mWidgetPadding;
+ private TextView mLogoutView;
private TextClock mClockView;
private View mClockSeparator;
private TextView mOwnerInfo;
@@ -80,6 +85,7 @@ public class KeyguardStatusView extends GridLayout {
if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing);
refresh();
updateOwnerInfo();
+ updateLogoutView();
}
}
@@ -97,6 +103,12 @@ public class KeyguardStatusView extends GridLayout {
public void onUserSwitchComplete(int userId) {
refresh();
updateOwnerInfo();
+ updateLogoutView();
+ }
+
+ @Override
+ public void onLogoutEnabledChanged() {
+ updateLogoutView();
}
};
@@ -111,6 +123,7 @@ public class KeyguardStatusView extends GridLayout {
public KeyguardStatusView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ mIActivityManager = ActivityManager.getService();
mLockPatternUtils = new LockPatternUtils(getContext());
mHandler = new Handler(Looper.myLooper());
mSmallClockScale = getResources().getDimension(R.dimen.widget_small_font_size)
@@ -145,6 +158,9 @@ public class KeyguardStatusView extends GridLayout {
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+ mLogoutView = findViewById(R.id.logout);
+ mLogoutView.setOnClickListener(this::onLogoutClicked);
+
mClockContainer = findViewById(R.id.keyguard_clock_container);
mClockView = findViewById(R.id.clock_view);
mClockView.setShowCurrentUserTime(true);
@@ -164,6 +180,7 @@ public class KeyguardStatusView extends GridLayout {
setEnableMarquee(shouldMarquee);
refresh();
updateOwnerInfo();
+ updateLogoutView();
// Disable elegant text height because our fancy colon makes the ymin value huge for no
// reason.
@@ -213,14 +230,28 @@ public class KeyguardStatusView extends GridLayout {
}
public int getClockBottom() {
- return mKeyguardSlice.getVisibility() == VISIBLE ? mKeyguardSlice.getBottom()
- : mClockView.getBottom();
+ if (mOwnerInfo != null && mOwnerInfo.getVisibility() == VISIBLE) {
+ return mOwnerInfo.getBottom();
+ } else {
+ return mClockContainer.getBottom();
+ }
+ }
+
+ public int getLogoutButtonHeight() {
+ return mLogoutView.getVisibility() == VISIBLE ? mLogoutView.getHeight() : 0;
}
public float getClockTextSize() {
return mClockView.getTextSize();
}
+ private void updateLogoutView() {
+ mLogoutView.setVisibility(shouldShowLogout() ? VISIBLE : GONE);
+ // Logout button will stay in language of user 0 if we don't set that manually.
+ mLogoutView.setText(mContext.getResources().getString(
+ com.android.internal.R.string.global_action_logout));
+ }
+
private void updateOwnerInfo() {
if (mOwnerInfo == null) return;
String ownerInfo = getOwnerInfo();
@@ -309,6 +340,7 @@ public class KeyguardStatusView extends GridLayout {
mDarkAmount = darkAmount;
boolean dark = darkAmount == 1;
+ mLogoutView.setAlpha(dark ? 0 : 1);
final int N = mClockContainer.getChildCount();
for (int i = 0; i < N; i++) {
View child = mClockContainer.getChildAt(i);
@@ -340,4 +372,19 @@ public class KeyguardStatusView extends GridLayout {
child.setAlpha(mDarkAmount == 1 && mPulsing ? 0.8f : 1);
}
}
+
+ private boolean shouldShowLogout() {
+ return KeyguardUpdateMonitor.getInstance(mContext).isLogoutEnabled()
+ && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM;
+ }
+
+ private void onLogoutClicked(View view) {
+ int currentUserId = KeyguardUpdateMonitor.getCurrentUser();
+ try {
+ mIActivityManager.switchUser(UserHandle.USER_SYSTEM);
+ mIActivityManager.stopUser(currentUserId, true /*force*/, null);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Failed to logout user", re);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 9f5af0838b23..d7196bf68d36 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -141,6 +141,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
private static final int MSG_USER_UNLOCKED = 334;
private static final int MSG_ASSISTANT_STACK_CHANGED = 335;
private static final int MSG_FINGERPRINT_AUTHENTICATION_CONTINUE = 336;
+ private static final int MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED = 337;
private static final int MSG_LOCALE_CHANGED = 500;
/** Fingerprint state: Not listening to fingerprint. */
@@ -226,6 +227,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
private LockPatternUtils mLockPatternUtils;
private final IDreamManager mDreamManager;
private boolean mIsDreaming;
+ private final DevicePolicyManager mDevicePolicyManager;
+ private boolean mLogoutEnabled;
/**
* Short delay before restarting fingerprint authentication after a successful try
@@ -333,6 +336,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
break;
case MSG_LOCALE_CHANGED:
handleLocaleChanged();
+ break;
+ case MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED:
+ updateLogoutEnabled();
break;
}
}
@@ -801,6 +807,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
mHandler.obtainMessage(MSG_SERVICE_STATE_CHANGE, subId, 0, serviceState));
} else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
mHandler.sendEmptyMessage(MSG_LOCALE_CHANGED);
+ } else if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(
+ action)) {
+ mHandler.sendEmptyMessage(MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED);
}
}
};
@@ -1166,6 +1175,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
filter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
+ filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
context.registerReceiver(mBroadcastReceiver, filter);
final IntentFilter bootCompleteFilter = new IntentFilter();
@@ -1220,6 +1230,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
mUserManager = context.getSystemService(UserManager.class);
+ mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
+ mLogoutEnabled = mDevicePolicyManager.isLogoutEnabled();
}
private void updateFingerprintListeningState() {
@@ -1986,6 +1998,26 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
return null; // not found
}
+ /**
+ * @return a cached version of DevicePolicyManager.isLogoutEnabled()
+ */
+ public boolean isLogoutEnabled() {
+ return mLogoutEnabled;
+ }
+
+ private void updateLogoutEnabled() {
+ boolean logoutEnabled = mDevicePolicyManager.isLogoutEnabled();
+ if (mLogoutEnabled != logoutEnabled) {
+ mLogoutEnabled = logoutEnabled;
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onLogoutEnabledChanged();
+ }
+ }
+ }
+ }
+
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("KeyguardUpdateMonitor state:");
pw.println(" SIM States:");
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 6284000ab99d..730ccb998364 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -291,4 +291,11 @@ public class KeyguardUpdateMonitorCallback {
* @see KeyguardIndicationController#showTransientIndication(CharSequence)
*/
public void onTrustAgentErrorMessage(CharSequence message) { }
+
+
+ /**
+ * Called when a value of logout enabled is change.
+ */
+ public void onLogoutEnabledChanged() { }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
index b7e1d67a5b3a..1185f45469df 100644
--- a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
@@ -47,6 +47,8 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import static com.android.systemui.shared.system.NavigationBarCompat.InteractionType;
+
/**
* Class to send information from overview to launcher with a binder.
*/
@@ -66,6 +68,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
private IOverviewProxy mOverviewProxy;
private int mConnectionBackoffAttempts;
+ private CharSequence mOnboardingText;
+ private @InteractionType int mInteractionFlags;
private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
@@ -98,9 +102,27 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
public void onRecentsAnimationStarted() {
long token = Binder.clearCallingIdentity();
try {
- mHandler.post(() -> {
- notifyRecentsAnimationStarted();
- });
+ mHandler.post(OverviewProxyService.this::notifyRecentsAnimationStarted);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ public void setRecentsOnboardingText(CharSequence text) {
+ mOnboardingText = text;
+ }
+
+ public void setInteractionState(@InteractionType int flags) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ if (mInteractionFlags != flags) {
+ mInteractionFlags = flags;
+ mHandler.post(() -> {
+ for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
+ mConnectionCallbacks.get(i).onInteractionFlagsChanged(flags);
+ }
+ });
+ }
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -223,8 +245,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
return mOverviewProxy;
}
- public ComponentName getLauncherComponent() {
- return mLauncherComponentName;
+ public CharSequence getOnboardingText() {
+ return mOnboardingText;
+ }
+
+ public int getInteractionFlags() {
+ return mInteractionFlags;
}
private void disconnectFromLauncherService() {
@@ -260,5 +286,6 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
public interface OverviewProxyListener {
default void onConnectionChanged(boolean isConnected) {}
default void onRecentsAnimationStarted() {}
+ default void onInteractionFlagsChanged(@InteractionType int flags) {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index 9319bc60f9ef..adb4e33d1a19 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -49,7 +49,7 @@ public final class Prefs {
Key.QS_NIGHTDISPLAY_ADDED,
Key.SEEN_MULTI_USER,
Key.NUM_APPS_LAUNCHED,
- Key.HAS_SWIPED_UP_FOR_RECENTS,
+ Key.HAS_SEEN_RECENTS_ONBOARDING,
})
public @interface Key {
@Deprecated
@@ -78,7 +78,7 @@ public final class Prefs {
String QS_NIGHTDISPLAY_ADDED = "QsNightDisplayAdded";
String SEEN_MULTI_USER = "HasSeenMultiUser";
String NUM_APPS_LAUNCHED = "NumAppsLaunched";
- String HAS_SWIPED_UP_FOR_RECENTS = "HasSwipedUpForRecents";
+ String HAS_SEEN_RECENTS_ONBOARDING = "HasSeenRecentsOnboarding";
}
public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 0f3daf57e8c1..d1834e921d20 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -52,6 +52,7 @@ import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.SmartReplyConstants;
import java.util.function.Consumer;
@@ -130,6 +131,8 @@ public class SystemUIFactory {
providers.put(NotificationGutsManager.class, () -> new NotificationGutsManager(context));
providers.put(NotificationRemoteInputManager.class,
() -> new NotificationRemoteInputManager(context));
+ providers.put(SmartReplyConstants.class,
+ () -> new SmartReplyConstants(Dependency.get(Dependency.MAIN_HANDLER), context));
providers.put(NotificationListener.class, () -> new NotificationListener(context));
providers.put(NotificationLogger.class, NotificationLogger::new);
providers.put(NotificationViewHierarchyManager.class,
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index ddf0bd0cbab4..bb82a54c12f1 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -28,6 +28,8 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import com.android.internal.os.BinderInternal;
+import com.android.systemui.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManagerImpl;
public class SystemUIService extends Service {
@@ -70,6 +72,10 @@ public class SystemUIService extends Service {
pw.println("dumping service: " + ui.getClass().getName());
ui.dump(fd, pw, args);
}
+ if (Build.IS_DEBUGGABLE) {
+ pw.println("dumping plugins:");
+ ((PluginManagerImpl) Dependency.get(PluginManager.class)).dump(fd, pw, args);
+ }
} else {
String svc = args[0];
for (SystemUI ui: services) {
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
index 348855bb0440..afc9629ecede 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
@@ -19,7 +19,6 @@ package com.android.systemui.charging;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.os.Handler;
import android.os.Looper;
@@ -36,13 +35,18 @@ import android.view.WindowManager;
*/
public class WirelessChargingAnimation {
- public static final long DURATION = 1400;
+ public static final long DURATION = 1133;
private static final String TAG = "WirelessChargingView";
private static final boolean LOCAL_LOGV = false;
private final WirelessChargingView mCurrentWirelessChargingView;
private static WirelessChargingView mPreviousWirelessChargingView;
+ public interface Callback {
+ void onAnimationStarting();
+ void onAnimationEnded();
+ }
+
/**
* Constructs an empty WirelessChargingAnimation object. If looper is null,
* Looper.myLooper() is used. Must set
@@ -51,9 +55,9 @@ public class WirelessChargingAnimation {
* @hide
*/
public WirelessChargingAnimation(@NonNull Context context, @Nullable Looper looper, int
- batteryLevel) {
+ batteryLevel, Callback callback) {
mCurrentWirelessChargingView = new WirelessChargingView(context, looper,
- batteryLevel);
+ batteryLevel, callback);
}
/**
@@ -61,8 +65,8 @@ public class WirelessChargingAnimation {
* @hide
*/
public static WirelessChargingAnimation makeWirelessChargingAnimation(@NonNull Context context,
- @Nullable Looper looper, int batteryLevel) {
- return new WirelessChargingAnimation(context, looper, batteryLevel);
+ @Nullable Looper looper, int batteryLevel, Callback callback) {
+ return new WirelessChargingAnimation(context, looper, batteryLevel, callback);
}
/**
@@ -95,8 +99,11 @@ public class WirelessChargingAnimation {
private View mView;
private View mNextView;
private WindowManager mWM;
+ private Callback mCallback;
- public WirelessChargingView(Context context, @Nullable Looper looper, int batteryLevel) {
+ public WirelessChargingView(Context context, @Nullable Looper looper, int batteryLevel,
+ Callback callback) {
+ mCallback = callback;
mNextView = new WirelessChargingLayout(context, batteryLevel);
mGravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER;
@@ -149,6 +156,8 @@ public class WirelessChargingAnimation {
}
public void hide(long duration) {
+ mHandler.removeMessages(HIDE);
+
if (LOCAL_LOGV) Log.v(TAG, "HIDE: " + this);
mHandler.sendMessageDelayed(Message.obtain(mHandler, HIDE), duration);
}
@@ -169,18 +178,6 @@ public class WirelessChargingAnimation {
context = mView.getContext();
}
mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- // We can resolve the Gravity here by using the Locale for getting
- // the layout direction
- final Configuration config = mView.getContext().getResources().getConfiguration();
- final int gravity = Gravity.getAbsoluteGravity(mGravity,
- config.getLayoutDirection());
- mParams.gravity = gravity;
- if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
- mParams.horizontalWeight = 1.0f;
- }
- if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
- mParams.verticalWeight = 1.0f;
- }
mParams.packageName = packageName;
mParams.hideTimeoutMilliseconds = DURATION;
@@ -191,6 +188,9 @@ public class WirelessChargingAnimation {
if (LOCAL_LOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
try {
+ if (mCallback != null) {
+ mCallback.onAnimationStarting();
+ }
mWM.addView(mView, mParams);
} catch (WindowManager.BadTokenException e) {
Slog.d(TAG, "Unable to add wireless charging view. " + e);
@@ -203,6 +203,9 @@ public class WirelessChargingAnimation {
if (mView != null) {
if (mView.getParent() != null) {
if (LOCAL_LOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
+ if (mCallback != null) {
+ mCallback.onAnimationEnded();
+ }
mWM.removeViewImmediate(mView);
}
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index c78ea56524cb..8f87d647a0e3 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -16,13 +16,16 @@
package com.android.systemui.charging;
+import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.util.AttributeSet;
+import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
import android.widget.TextView;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import java.text.NumberFormat;
@@ -52,10 +55,9 @@ public class WirelessChargingLayout extends FrameLayout {
init(c, attrs, -1);
}
- private void init(Context c, AttributeSet attrs, int batteryLevel) {
+ private void init(Context context, AttributeSet attrs, int batteryLevel) {
final int mBatteryLevel = batteryLevel;
-
- inflate(c, R.layout.wireless_charging_layout, this);
+ inflate(context, R.layout.wireless_charging_layout, this);
// where the circle animation occurs:
final WirelessChargingView mChargingView = findViewById(R.id.wireless_charging_view);
@@ -68,14 +70,57 @@ public class WirelessChargingLayout extends FrameLayout {
if (batteryLevel != UNKNOWN_BATTERY_LEVEL) {
mPercentage.setText(NumberFormat.getPercentInstance().format(mBatteryLevel / 100f));
-
- ValueAnimator animator = ObjectAnimator.ofFloat(mPercentage, "textSize",
- getContext().getResources().getFloat(R.dimen.config_batteryLevelTextSizeStart),
- getContext().getResources().getFloat(R.dimen.config_batteryLevelTextSizeEnd));
-
- animator.setDuration((long) getContext().getResources().getInteger(
- R.integer.config_batteryLevelTextAnimationDuration));
- animator.start();
+ mPercentage.setAlpha(0);
}
+
+ final long chargingAnimationFadeStartOffset = (long) context.getResources().getInteger(
+ R.integer.wireless_charging_fade_offset);
+ final long chargingAnimationFadeDuration = (long) context.getResources().getInteger(
+ R.integer.wireless_charging_fade_duration);
+ final int batteryLevelTextSizeStart = context.getResources().getDimensionPixelSize(
+ R.dimen.wireless_charging_anim_battery_level_text_size_start);
+ final int batteryLevelTextSizeEnd = context.getResources().getDimensionPixelSize(
+ R.dimen.wireless_charging_anim_battery_level_text_size_end);
+
+ // Animation Scale: battery percentage text scales from 0% to 100%
+ ValueAnimator textSizeAnimator = ObjectAnimator.ofFloat(mPercentage, "textSize",
+ batteryLevelTextSizeStart, batteryLevelTextSizeEnd);
+ textSizeAnimator.setInterpolator(new PathInterpolator(0, 0, 0, 1));
+ textSizeAnimator.setDuration((long) context.getResources().getInteger(
+ R.integer.wireless_charging_battery_level_text_scale_animation_duration));
+
+ // Animation Opacity: battery percentage text transitions from 0 to 1 opacity
+ ValueAnimator textOpacityAnimator = ObjectAnimator.ofFloat(mPercentage, "alpha", 0, 1);
+ textOpacityAnimator.setInterpolator(Interpolators.LINEAR);
+ textOpacityAnimator.setDuration((long) context.getResources().getInteger(
+ R.integer.wireless_charging_battery_level_text_opacity_duration));
+ textOpacityAnimator.setStartDelay((long) context.getResources().getInteger(
+ R.integer.wireless_charging_anim_opacity_offset));
+
+ // Animation Opacity: battery percentage text fades from 1 to 0 opacity
+ ValueAnimator textFadeAnimator = ObjectAnimator.ofFloat(mPercentage, "alpha", 1, 0);
+ textFadeAnimator.setDuration(chargingAnimationFadeDuration);
+ textFadeAnimator.setInterpolator(Interpolators.LINEAR);
+ textFadeAnimator.setStartDelay(chargingAnimationFadeStartOffset);
+
+ // Animation Opacity: wireless charging circle animation fades from 1 to 0 opacity
+ ValueAnimator circleFadeAnimator = ObjectAnimator.ofFloat(mChargingView, "alpha",
+ 1, 0);
+ circleFadeAnimator.setDuration(chargingAnimationFadeDuration);
+ circleFadeAnimator.setInterpolator(Interpolators.LINEAR);
+ circleFadeAnimator.setStartDelay(chargingAnimationFadeStartOffset);
+
+ // Animation Opacity: secondary text animation fades from 1 to 0 opacity
+ ValueAnimator secondaryTextFadeAnimator = ObjectAnimator.ofFloat(mSecondaryText, "alpha",
+ 1, 0);
+ circleFadeAnimator.setDuration(chargingAnimationFadeDuration);
+ secondaryTextFadeAnimator.setInterpolator(Interpolators.LINEAR);
+ secondaryTextFadeAnimator.setStartDelay(chargingAnimationFadeStartOffset);
+
+ // play all animations together
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playTogether(textSizeAnimator, textOpacityAnimator, textFadeAnimator,
+ circleFadeAnimator, secondaryTextFadeAnimator);
+ animatorSet.start();
}
-}
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.java
index f5edf5216fa2..19c6dc1ceb1f 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.java
@@ -21,10 +21,10 @@ import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
-import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import com.android.settingslib.Utils;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
final class WirelessChargingView extends View {
@@ -33,21 +33,21 @@ final class WirelessChargingView extends View {
private float mPathGone;
private float mInterpolatedPathGone;
private long mAnimationStartTime;
- private long mStartSpinCircleAnimationTime;
- private long mAnimationOffset = 500;
- private long mTotalAnimationDuration = WirelessChargingAnimation.DURATION - mAnimationOffset;
- private long mExpandingCircle = (long) (mTotalAnimationDuration * .9);
- private long mSpinCircleAnimationTime = mTotalAnimationDuration - mExpandingCircle;
+ private long mScaleDotsDuration;
- private boolean mFinishedAnimatingSpinningCircles = false;
+ private boolean mFinishedAnimatingDots = false;
+ private int mNumDots;
- private int mStartAngle = -90;
- private int mNumSmallCircles = 20;
- private int mSmallCircleRadius = 10;
+ private double mAngleOffset;
+ private double mCurrAngleOffset;
- private int mMainCircleStartRadius = 100;
- private int mMainCircleEndRadius = 230;
- private int mMainCircleCurrentRadius = mMainCircleStartRadius;
+ private int mDotsRadiusStart;
+ private int mDotsRadiusEnd;
+ private int mCurrDotRadius;
+
+ private int mMainCircleStartRadius;
+ private int mMainCircleEndRadius;
+ private int mMainCircleCurrentRadius;
private int mCenterX;
private int mCenterY;
@@ -72,8 +72,25 @@ final class WirelessChargingView extends View {
public void init(Context context, AttributeSet attr) {
mContext = context;
+
+ mDotsRadiusStart = context.getResources().getDimensionPixelSize(
+ R.dimen.wireless_charging_dots_radius_start);
+ mDotsRadiusEnd = context.getResources().getDimensionPixelSize(
+ R.dimen.wireless_charging_dots_radius_end);
+
+ mMainCircleStartRadius = context.getResources().getDimensionPixelSize(
+ R.dimen.wireless_charging_circle_radius_start);
+ mMainCircleEndRadius = context.getResources().getDimensionPixelSize(
+ R.dimen.wireless_charging_circle_radius_end);
+ mMainCircleCurrentRadius = mMainCircleStartRadius;
+
+ mAngleOffset = context.getResources().getInteger(R.integer.wireless_charging_angle_offset);
+ mScaleDotsDuration = (long) context.getResources().getInteger(
+ R.integer.wireless_charging_scale_dots_duration);
+ mNumDots = context.getResources().getInteger(R.integer.wireless_charging_num_dots);
+
setupPaint();
- mInterpolator = new DecelerateInterpolator();
+ mInterpolator = Interpolators.LINEAR_OUT_SLOW_IN;
}
private void setupPaint() {
@@ -92,64 +109,62 @@ final class WirelessChargingView extends View {
updateDrawingParameters();
drawCircles(canvas);
- if (!mFinishedAnimatingSpinningCircles) {
+ if (!mFinishedAnimatingDots) {
invalidate();
}
}
/**
* Draws a larger circle of radius {@link WirelessChargingView#mMainCircleEndRadius} composed of
- * {@link WirelessChargingView#mNumSmallCircles} smaller circles
+ * {@link WirelessChargingView#mNumDots} smaller circles
* @param canvas
*/
private void drawCircles(Canvas canvas) {
mCenterX = canvas.getWidth() / 2;
mCenterY = canvas.getHeight() / 2;
- // angleOffset makes small circles look like they're moving around the main circle
- float angleOffset = mPathGone * 10;
-
- // draws mNumSmallCircles to compose a larger, main circle
- for (int circle = 0; circle < mNumSmallCircles; circle++) {
- double angle = ((mStartAngle + angleOffset) * Math.PI / 180) + (circle * ((2 * Math.PI)
- / mNumSmallCircles));
+ // draws mNumDots to compose a larger, main circle
+ for (int circle = 0; circle < mNumDots; circle++) {
+ double angle = ((mCurrAngleOffset) * Math.PI / 180) + (circle * ((2 * Math.PI)
+ / mNumDots));
int x = (int) (mCenterX + Math.cos(angle) * (mMainCircleCurrentRadius +
- mSmallCircleRadius));
+ mCurrDotRadius));
int y = (int) (mCenterY + Math.sin(angle) * (mMainCircleCurrentRadius +
- mSmallCircleRadius));
-
- canvas.drawCircle(x, y, mSmallCircleRadius, mPaint);
- }
+ mCurrDotRadius));
- if (mMainCircleCurrentRadius >= mMainCircleEndRadius && !isSpinCircleAnimationStarted()) {
- mStartSpinCircleAnimationTime = System.currentTimeMillis();
+ canvas.drawCircle(x, y, mCurrDotRadius, mPaint);
}
- if (isSpinAnimationFinished()) {
- mFinishedAnimatingSpinningCircles = true;
+ if (mMainCircleCurrentRadius >= mMainCircleEndRadius) {
+ mFinishedAnimatingDots = true;
}
}
- private boolean isSpinCircleAnimationStarted() {
- return mStartSpinCircleAnimationTime != 0;
- }
-
- private boolean isSpinAnimationFinished() {
- return isSpinCircleAnimationStarted() && System.currentTimeMillis() -
- mStartSpinCircleAnimationTime > mSpinCircleAnimationTime;
- }
-
private void updateDrawingParameters() {
- mPathGone = getPathGone(System.currentTimeMillis());
+ long now = System.currentTimeMillis();
+ long timeSinceStart = now - mAnimationStartTime;
+ mPathGone = getPathGone(now);
mInterpolatedPathGone = mInterpolator.getInterpolation(mPathGone);
+ // Position Dots: update main circle radius (that the dots compose)
if (mPathGone < 1.0f) {
mMainCircleCurrentRadius = mMainCircleStartRadius + (int) (mInterpolatedPathGone *
(mMainCircleEndRadius - mMainCircleStartRadius));
} else {
mMainCircleCurrentRadius = mMainCircleEndRadius;
}
+
+ // Scale Dots: update dot radius
+ if (timeSinceStart < mScaleDotsDuration) {
+ mCurrDotRadius = mDotsRadiusStart + (int) (mInterpolator.getInterpolation((float)
+ timeSinceStart / mScaleDotsDuration) * (mDotsRadiusEnd - mDotsRadiusStart));
+ } else {
+ mCurrDotRadius = mDotsRadiusEnd;
+ }
+
+ // Rotation Dot Group: dots rotate from 0 to 20 degrees
+ mCurrAngleOffset = mAngleOffset * mPathGone;
}
/**
@@ -158,6 +173,6 @@ final class WirelessChargingView extends View {
* For values > 1.0 the larger circle has been drawn and further animation can occur
*/
private float getPathGone(long now) {
- return (float) (now - mAnimationStartTime) / (mExpandingCircle);
+ return (float) (now - mAnimationStartTime) / (WirelessChargingAnimation.DURATION);
}
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index bfb3a6ea55f7..092f3d241d6c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -64,7 +64,7 @@ public class DozeFactory {
createDozeTriggers(context, sensorManager, host, alarmManager, config, params,
handler, wakeLock, machine),
createDozeUi(context, host, wakeLock, machine, handler, alarmManager, params),
- new DozeScreenState(wrappedService, handler),
+ new DozeScreenState(wrappedService, handler, params),
createDozeScreenBrightness(context, wrappedService, sensorManager, host, handler),
new DozeWallpaperState(context)
});
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index 8ec6afc326e1..6ff8e3db25e5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -24,6 +24,7 @@ import android.view.Display;
import com.android.internal.hardware.AmbientDisplayConfiguration;
import com.android.internal.util.Preconditions;
+import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.util.Assert;
import com.android.systemui.util.wakelock.WakeLock;
@@ -87,12 +88,11 @@ public class DozeMachine {
}
}
- int screenState() {
+ int screenState(DozeParameters parameters) {
switch (this) {
case UNINITIALIZED:
case INITIALIZED:
case DOZE:
- case DOZE_REQUEST_PULSE:
case DOZE_AOD_PAUSED:
return Display.STATE_OFF;
case DOZE_PULSING:
@@ -100,6 +100,9 @@ public class DozeMachine {
case DOZE_AOD:
case DOZE_AOD_PAUSING:
return Display.STATE_DOZE_SUSPEND;
+ case DOZE_REQUEST_PULSE:
+ return parameters.getDisplayNeedsBlanking() ? Display.STATE_OFF
+ : Display.STATE_ON;
default:
return Display.STATE_UNKNOWN;
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index bef9cb38180f..3053de366be5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -19,6 +19,8 @@ package com.android.systemui.doze;
import android.os.Handler;
import android.view.Display;
+import com.android.systemui.statusbar.phone.DozeParameters;
+
/**
* Controls the screen when dozing.
*/
@@ -26,17 +28,20 @@ public class DozeScreenState implements DozeMachine.Part {
private final DozeMachine.Service mDozeService;
private final Handler mHandler;
private final Runnable mApplyPendingScreenState = this::applyPendingScreenState;
+ private final DozeParameters mParameters;
private int mPendingScreenState = Display.STATE_UNKNOWN;
- public DozeScreenState(DozeMachine.Service service, Handler handler) {
+ public DozeScreenState(DozeMachine.Service service, Handler handler,
+ DozeParameters parameters) {
mDozeService = service;
mHandler = handler;
+ mParameters = parameters;
}
@Override
public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
- int screenState = newState.screenState();
+ int screenState = newState.screenState(mParameters);
if (newState == DozeMachine.State.FINISH) {
// Make sure not to apply the screen state after DozeService was destroyed.
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 34d392861d7c..aa264190f800 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -59,6 +59,12 @@ public class DozeService extends DreamService
}
@Override
+ public void onDestroy() {
+ Dependency.get(PluginManager.class).removePluginListener(this);
+ super.onDestroy();
+ }
+
+ @Override
public void onPluginConnected(DozeServicePlugin plugin, Context pluginContext) {
mDozePlugin = plugin;
mDozePlugin.setDozeRequester(this);
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index c28b7eed1614..259bff28d458 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -688,7 +688,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
}
private Action getLockdownAction() {
- return new SinglePressAction(R.drawable.ic_lock_lock,
+ return new SinglePressAction(com.android.systemui.R.drawable.ic_lock_lockdown,
R.string.global_action_lockdown) {
@Override
@@ -1369,6 +1369,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
mListView = findViewById(android.R.id.list);
mHardwareLayout = HardwareUiLayout.get(mListView);
mHardwareLayout.setOutsideTouchListener(view -> dismiss());
+ setTitle(R.string.global_actions);
}
private void updateList() {
@@ -1464,20 +1465,6 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
- for (int i = 0; i < mAdapter.getCount(); ++i) {
- CharSequence label =
- mAdapter.getItem(i).getLabelForAccessibility(getContext());
- if (label != null) {
- event.getText().add(label);
- }
- }
- }
- return super.dispatchPopulateAccessibilityEvent(event);
- }
-
- @Override
public void onColorsChanged(ColorExtractor extractor, int which) {
if (mKeyguardShowing) {
if ((WallpaperManager.FLAG_LOCK & which) != 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java
index 82c0128c10c0..d5541e9be17e 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java
@@ -168,6 +168,12 @@ public class PluginInstanceManager<T extends Plugin> {
return false;
}
+ @Override
+ public String toString() {
+ return String.format("%s@%s (action=%s)",
+ getClass().getSimpleName(), hashCode(), mAction);
+ }
+
private class MainHandler extends Handler {
private static final int PLUGIN_CONNECTED = 1;
private static final int PLUGIN_DISCONNECTED = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java
index 03747d50a6fa..2a17e35f00dd 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java
@@ -50,6 +50,8 @@ import com.android.systemui.plugins.annotations.ProvidesInterface;
import dalvik.system.PathClassLoader;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.Map;
@@ -303,6 +305,14 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
}
}
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println(String.format(" plugin map (%d):", mPluginMap.size()));
+ for (PluginListener listener: mPluginMap.keySet()) {
+ pw.println(String.format(" %s -> %s",
+ listener, mPluginMap.get(listener)));
+ }
+ }
+
@VisibleForTesting
public static class PluginInstanceManagerFactory {
public <T extends Plugin> PluginInstanceManager createPluginInstanceManager(Context context,
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index aa56694775fc..3a2b12f4da23 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -26,10 +26,6 @@ import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnDismissListener;
import android.content.Intent;
import android.content.IntentFilter;
-import android.icu.text.MeasureFormat;
-import android.icu.text.MeasureFormat.FormatWidth;
-import android.icu.util.Measure;
-import android.icu.util.MeasureUnit;
import android.media.AudioAttributes;
import android.os.AsyncTask;
import android.os.Handler;
@@ -37,11 +33,11 @@ import android.os.Looper;
import android.os.PowerManager;
import android.os.UserHandle;
import android.support.annotation.VisibleForTesting;
-import android.text.format.DateUtils;
import android.util.Slog;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.settingslib.Utils;
+import com.android.settingslib.utils.PowerUtil;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -49,8 +45,6 @@ import com.android.systemui.util.NotificationChannels;
import java.io.PrintWriter;
import java.text.NumberFormat;
-import java.util.Locale;
-import java.util.concurrent.TimeUnit;
public class PowerNotificationWarnings implements PowerUI.WarningsUI {
private static final String TAG = PowerUI.TAG + ".Notification";
@@ -200,12 +194,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
// override notification copy if hybrid notification enabled
if (mEstimate != null) {
title = mContext.getString(R.string.battery_low_title_hybrid);
- contentText = mContext.getString(
- mEstimate.isBasedOnUsage
- ? R.string.battery_low_percent_format_hybrid
- : R.string.battery_low_percent_format_hybrid_short,
- percentage,
- getTimeRemainingFormatted());
+ contentText = getHybridContentString(percentage);
}
final Notification.Builder nb =
@@ -239,21 +228,12 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
mNoMan.notifyAsUser(TAG_BATTERY, SystemMessage.NOTE_POWER_LOW, n, UserHandle.ALL);
}
- @VisibleForTesting
- String getTimeRemainingFormatted() {
- final Locale currentLocale = mContext.getResources().getConfiguration().getLocales().get(0);
- MeasureFormat frmt = MeasureFormat.getInstance(currentLocale, FormatWidth.NARROW);
-
- final long remainder = mEstimate.estimateMillis % DateUtils.HOUR_IN_MILLIS;
- final long hours = TimeUnit.MILLISECONDS.toHours(
- mEstimate.estimateMillis - remainder);
- // round down to the nearest 15 min for now to not appear overly precise
- final long minutes = TimeUnit.MILLISECONDS.toMinutes(
- remainder - (remainder % TimeUnit.MINUTES.toMillis(15)));
- final Measure hoursMeasure = new Measure(hours, MeasureUnit.HOUR);
- final Measure minutesMeasure = new Measure(minutes, MeasureUnit.MINUTE);
-
- return frmt.formatMeasures(hoursMeasure, minutesMeasure);
+ private String getHybridContentString(String percentage) {
+ return PowerUtil.getBatteryRemainingStringFormatted(
+ mContext,
+ mEstimate.estimateMillis,
+ percentage,
+ mEstimate.isBasedOnUsage);
}
private PendingIntent pendingBroadcast(String action) {
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index ea2a432ea6ca..ac86c8ae097d 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -53,7 +53,6 @@ import com.android.systemui.statusbar.phone.StatusBar;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Arrays;
-import java.util.concurrent.TimeUnit;
public class PowerUI extends SystemUI {
static final String TAG = "PowerUI";
@@ -72,10 +71,11 @@ public class PowerUI extends SystemUI {
private final Configuration mLastConfiguration = new Configuration();
private int mBatteryLevel = 100;
private long mTimeRemaining = Long.MAX_VALUE;
- private int mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN;
private int mPlugType = 0;
private int mInvalidCharger = 0;
private EnhancedEstimates mEnhancedEstimates;
+ private boolean mLowWarningShownThisChargeCycle;
+ private boolean mSevereWarningShownThisChargeCycle;
private int mLowBatteryAlertCloseLevel;
private final int[] mLowBatteryReminderLevels = new int[2];
@@ -88,6 +88,8 @@ public class PowerUI extends SystemUI {
private long mNextLogTime;
private IThermalService mThermalService;
+ @VisibleForTesting int mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN;
+
// by using the same instance (method references are not guaranteed to be the same object
// We create a method reference here so that we are guaranteed that we can remove a callback
// each time they are created).
@@ -218,6 +220,12 @@ public class PowerUI extends SystemUI {
final boolean plugged = mPlugType != 0;
final boolean oldPlugged = oldPlugType != 0;
+ // if we are now unplugged but we were previously plugged in we should allow the
+ // time based trigger again.
+ if (!plugged && plugged != oldPlugged) {
+ mLowWarningShownThisChargeCycle = false;
+ mSevereWarningShownThisChargeCycle = false;
+ }
int oldBucket = findBatteryLevelBucket(oldBatteryLevel);
int bucket = findBatteryLevelBucket(mBatteryLevel);
@@ -268,7 +276,6 @@ public class PowerUI extends SystemUI {
boolean isPowerSaver = mPowerManager.isPowerSaveMode();
// only play SFX when the dialog comes up or the bucket changes
final boolean playSound = bucket != oldBucket || oldPlugged;
- long oldTimeRemaining = mTimeRemaining;
if (mEnhancedEstimates.isHybridNotificationEnabled()) {
final Estimate estimate = mEnhancedEstimates.getEstimate();
// Turbo is not always booted once SysUI is running so we have ot make sure we actually
@@ -281,10 +288,18 @@ public class PowerUI extends SystemUI {
}
}
- if (shouldShowLowBatteryWarning(plugged, oldPlugged, oldBucket, bucket, oldTimeRemaining,
- mTimeRemaining,
- isPowerSaver, mBatteryStatus)) {
+ if (shouldShowLowBatteryWarning(plugged, oldPlugged, oldBucket, bucket,
+ mTimeRemaining, isPowerSaver, mBatteryStatus)) {
mWarnings.showLowBatteryWarning(playSound);
+
+ // mark if we've already shown a warning this cycle. This will prevent the time based
+ // trigger from spamming users since the time remaining can vary based on current
+ // device usage.
+ if (mTimeRemaining < mEnhancedEstimates.getSevereWarningThreshold()) {
+ mSevereWarningShownThisChargeCycle = true;
+ } else {
+ mLowWarningShownThisChargeCycle = true;
+ }
} else if (shouldDismissLowBatteryWarning(plugged, oldBucket, bucket, mTimeRemaining,
isPowerSaver)) {
mWarnings.dismissLowBatteryWarning();
@@ -295,22 +310,14 @@ public class PowerUI extends SystemUI {
@VisibleForTesting
boolean shouldShowLowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket,
- int bucket, long oldTimeRemaining, long timeRemaining,
- boolean isPowerSaver, int mBatteryStatus) {
+ int bucket, long timeRemaining, boolean isPowerSaver, int mBatteryStatus) {
return !plugged
&& !isPowerSaver
&& (((bucket < oldBucket || oldPlugged) && bucket < 0)
- || (mEnhancedEstimates.isHybridNotificationEnabled()
- && timeRemaining < mEnhancedEstimates.getLowWarningThreshold()
- && isHourLess(oldTimeRemaining, timeRemaining)))
+ || isTimeBasedTrigger(timeRemaining))
&& mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN;
}
- private boolean isHourLess(long oldTimeRemaining, long timeRemaining) {
- final long dif = oldTimeRemaining - timeRemaining;
- return dif >= TimeUnit.HOURS.toMillis(1);
- }
-
@VisibleForTesting
boolean shouldDismissLowBatteryWarning(boolean plugged, int oldBucket, int bucket,
long timeRemaining, boolean isPowerSaver) {
@@ -323,6 +330,23 @@ public class PowerUI extends SystemUI {
|| hybridWouldDismiss));
}
+ private boolean isTimeBasedTrigger(long timeRemaining) {
+ if (!mEnhancedEstimates.isHybridNotificationEnabled()) {
+ return false;
+ }
+
+ // Only show the time based warning once per charge cycle
+ final boolean canShowWarning = timeRemaining < mEnhancedEstimates.getLowWarningThreshold()
+ && !mLowWarningShownThisChargeCycle;
+
+ // Only show the severe time based warning once per charge cycle
+ final boolean canShowSevereWarning =
+ timeRemaining < mEnhancedEstimates.getSevereWarningThreshold()
+ && !mSevereWarningShownThisChargeCycle;
+
+ return canShowWarning || canShowSevereWarning;
+ }
+
private void initTemperatureWarning() {
ContentResolver resolver = mContext.getContentResolver();
Resources resources = mContext.getResources();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java b/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java
index 5f2609380085..e7eefe8d5e56 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java
@@ -19,12 +19,12 @@ import android.graphics.drawable.Drawable;
import android.service.quicksettings.Tile;
import android.widget.ImageView;
+import com.android.settingslib.graph.SignalDrawable;
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.plugins.qs.QSTile.Icon;
import com.android.systemui.plugins.qs.QSTile.State;
import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.statusbar.phone.SignalDrawable;
import java.util.Objects;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index fe3ffb926305..e24135775e79 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -174,7 +174,9 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
.addFloat(mDivider, "alpha", 0, 1)
.addFloat(mCarrierText, "alpha", 0, 1)
.addFloat(mActionsContainer, "alpha", 0, 1)
- .addFloat(mDragHandle, "translationY", 0, -mDragHandleExpandOffset)
+ .addFloat(mDragHandle, "translationY", mDragHandleExpandOffset, 0)
+ .addFloat(mDragHandle, "alpha", 1, 0)
+ .setStartDelay(0.15f)
.build();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java b/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java
index 9cda75c858e8..7b1509dcd173 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java
@@ -14,8 +14,11 @@
package com.android.systemui.qs;
+import android.animation.ObjectAnimator;
import android.content.Context;
+import android.graphics.Canvas;
import android.support.v4.widget.NestedScrollView;
+import android.util.Property;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
@@ -23,6 +26,8 @@ import android.view.ViewParent;
import android.widget.LinearLayout;
import com.android.systemui.R;
+import com.android.systemui.qs.touch.OverScroll;
+import com.android.systemui.qs.touch.SwipeDetector;
/**
* Quick setting scroll view containing the brightness slider and the QS tiles.
@@ -35,6 +40,9 @@ public class QSScrollLayout extends NestedScrollView {
private final int mTouchSlop;
private final int mFooterHeight;
private int mLastMotionY;
+ private final SwipeDetector mSwipeDetector;
+ private final OverScrollHelper mOverScrollHelper;
+ private float mContentTranslationY;
public QSScrollLayout(Context context, View... children) {
super(context);
@@ -49,6 +57,35 @@ public class QSScrollLayout extends NestedScrollView {
linearLayout.addView(view);
}
addView(linearLayout);
+ setOverScrollMode(OVER_SCROLL_NEVER);
+ mOverScrollHelper = new OverScrollHelper();
+ mSwipeDetector = new SwipeDetector(context, mOverScrollHelper, SwipeDetector.VERTICAL);
+ mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, true);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (!canScrollVertically(1) && !canScrollVertically(-1)) {
+ return false;
+ }
+ mSwipeDetector.onTouchEvent(ev);
+ return super.onInterceptTouchEvent(ev) || mOverScrollHelper.isInOverScroll();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (!canScrollVertically(1) && !canScrollVertically(-1)) {
+ return false;
+ }
+ mSwipeDetector.onTouchEvent(ev);
+ return super.onTouchEvent(ev);
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ canvas.translate(0, mContentTranslationY);
+ super.dispatchDraw(canvas);
+ canvas.translate(0, -mContentTranslationY);
}
public boolean shouldIntercept(MotionEvent ev) {
@@ -81,4 +118,81 @@ public class QSScrollLayout extends NestedScrollView {
parent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
+
+ private void setContentTranslationY(float contentTranslationY) {
+ mContentTranslationY = contentTranslationY;
+ invalidate();
+ }
+
+ private static final Property<QSScrollLayout, Float> CONTENT_TRANS_Y =
+ new Property<QSScrollLayout, Float>(Float.class, "qsScrollLayoutContentTransY") {
+ @Override
+ public Float get(QSScrollLayout qsScrollLayout) {
+ return qsScrollLayout.mContentTranslationY;
+ }
+
+ @Override
+ public void set(QSScrollLayout qsScrollLayout, Float y) {
+ qsScrollLayout.setContentTranslationY(y);
+ }
+ };
+
+ private class OverScrollHelper implements SwipeDetector.Listener {
+ private boolean mIsInOverScroll;
+
+ // We use this value to calculate the actual amount the user has overscrolled.
+ private float mFirstDisplacement = 0;
+
+ @Override
+ public void onDragStart(boolean start) {}
+
+ @Override
+ public boolean onDrag(float displacement, float velocity) {
+ // Only overscroll if the user is scrolling down when they're already at the bottom
+ // or scrolling up when they're already at the top.
+ boolean wasInOverScroll = mIsInOverScroll;
+ mIsInOverScroll = (!canScrollVertically(1) && displacement < 0) ||
+ (!canScrollVertically(-1) && displacement > 0);
+
+ if (wasInOverScroll && !mIsInOverScroll) {
+ // Exit overscroll. This can happen when the user is in overscroll and then
+ // scrolls the opposite way. Note that this causes the reset translation animation
+ // to run while the user is dragging, which feels a bit unnatural.
+ reset();
+ } else if (mIsInOverScroll) {
+ if (Float.compare(mFirstDisplacement, 0) == 0) {
+ // Because users can scroll before entering overscroll, we need to
+ // subtract the amount where the user was not in overscroll.
+ mFirstDisplacement = displacement;
+ }
+ float overscrollY = displacement - mFirstDisplacement;
+ setContentTranslationY(getDampedOverScroll(overscrollY));
+ }
+
+ return mIsInOverScroll;
+ }
+
+ @Override
+ public void onDragEnd(float velocity, boolean fling) {
+ reset();
+ }
+
+ private void reset() {
+ if (Float.compare(mContentTranslationY, 0) != 0) {
+ ObjectAnimator.ofFloat(QSScrollLayout.this, CONTENT_TRANS_Y, 0)
+ .setDuration(100)
+ .start();
+ }
+ mIsInOverScroll = false;
+ mFirstDisplacement = 0;
+ }
+
+ public boolean isInOverScroll() {
+ return mIsInOverScroll;
+ }
+
+ private float getDampedOverScroll(float y) {
+ return OverScroll.dampedScroll(y, getHeight());
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 3b9e7bcfb9b4..65135ab142d7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -104,6 +104,11 @@ public class TileLayout extends ViewGroup implements QSTileLayout {
setMeasuredDimension(width, height);
}
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
private static int exactly(int size) {
return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index 5a3081cd6664..3847040271e2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -171,8 +171,6 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
queryTiles();
mNotifQsContainer.setCustomizerAnimating(true);
mNotifQsContainer.setCustomizerShowing(true);
- announceForAccessibility(mContext.getString(
- R.string.accessibility_desc_quick_settings_edit));
Dependency.get(KeyguardMonitor.class).addCallback(mKeyguardCallback);
updateNavColors();
}
@@ -213,8 +211,6 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
mClipper.animateCircularClip(mX, mY, false, mCollapseAnimationListener);
mNotifQsContainer.setCustomizerAnimating(true);
mNotifQsContainer.setCustomizerShowing(false);
- announceForAccessibility(mContext.getString(
- R.string.accessibility_desc_quick_settings));
Dependency.get(KeyguardMonitor.class).removeCallback(mKeyguardCallback);
updateNavColors();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 2607ebbb72ea..2a9a3818a746 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -78,6 +78,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
@Override
public void handleSetListening(boolean listening) {
+ if (mController == null) return;
if (listening) {
mController.addCallback(mCallback);
} else {
@@ -131,21 +132,10 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
if (enabled) {
if (connected) {
- state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_connected);
+ state.icon = new BluetoothConnectedTileIcon();
state.contentDescription = mContext.getString(
R.string.accessibility_bluetooth_name, state.label);
- final CachedBluetoothDevice lastDevice = mController.getLastDevice();
- if (lastDevice != null) {
- final int batteryLevel = lastDevice.getBatteryLevel();
- if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
- state.icon = new BluetoothBatteryTileIcon(
- batteryLevel,
- mContext.getResources().getFraction(
- R.fraction.bt_battery_scale_fraction, 1, 1));
- }
- }
-
state.label = mController.getLastDeviceName();
} else if (state.isTransient) {
state.icon = ResourceIcon.get(R.drawable.ic_bluetooth_transient_animation);
@@ -281,6 +271,25 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
}
}
+
+ /**
+ * Bluetooth icon wrapper (when connected with no battery indicator) for Quick Settings. This is
+ * used instead of {@link com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon} in order to
+ * use a context that reflects dark/light theme attributes.
+ */
+ private class BluetoothConnectedTileIcon extends Icon {
+
+ BluetoothConnectedTileIcon() {
+ // Do nothing. Default constructor to limit visibility.
+ }
+
+ @Override
+ public Drawable getDrawable(Context context) {
+ // This method returns Pair<Drawable, String> - the first value is the drawable.
+ return context.getDrawable(R.drawable.ic_qs_bluetooth_connected);
+ }
+ }
+
protected class BluetoothDetailAdapter implements DetailAdapter, QSDetailItems.Callback {
// We probably won't ever have space in the UI for more than 20 devices, so don't
// get info for them.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 6205e9afcb03..2d31669db6c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -240,6 +240,7 @@ public class DndTile extends QSTileImpl<BooleanState> {
public void handleSetListening(boolean listening) {
if (mListening == listening) return;
mListening = listening;
+ if (mController == null) return;
if (mListening) {
mController.addCallback(mZenCallback);
Prefs.registerListener(mContext, mPrefListener);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
index b3ff4e5b890c..12daff1f12f9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
@@ -98,6 +98,8 @@ public class NfcTile extends QSTileImpl<BooleanState> {
protected void handleUpdateState(BooleanState state, Object arg) {
final Drawable mEnable = mContext.getDrawable(R.drawable.ic_qs_nfc_enabled);
final Drawable mDisable = mContext.getDrawable(R.drawable.ic_qs_nfc_disabled);
+
+ if (getAdapter() == null) return;
state.value = getAdapter().isEnabled();
state.label = mContext.getString(R.string.quick_settings_nfc_label);
state.icon = new DrawableIcon(state.value ? mEnable : mDisable);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index ea6e174d786e..3597929229e6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -21,6 +21,7 @@ import android.app.ActivityManager;
import android.content.Intent;
import android.provider.Settings;
import android.service.quicksettings.Tile;
+import android.support.annotation.StringRes;
import android.widget.Switch;
import com.android.internal.app.ColorDisplayController;
@@ -30,6 +31,7 @@ import com.android.systemui.qs.QSHost;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
public class NightDisplayTile extends QSTileImpl<BooleanState>
@@ -39,7 +41,9 @@ public class NightDisplayTile extends QSTileImpl<BooleanState>
* Pattern for {@link java.time.format.DateTimeFormatter} used to approximate the time to the
* nearest hour and add on the AM/PM indicator.
*/
- private static final String APPROXIMATE_HOUR_DATE_TIME_PATTERN = "h a";
+ private static final String HOUR_MINUTE_DATE_TIME_PATTERN = "h a";
+ private static final String APPROXIMATE_HOUR_DATE_TIME_PATTERN = "h:m a";
+
private ColorDisplayController mController;
private boolean mIsListening;
@@ -110,17 +114,26 @@ public class NightDisplayTile extends QSTileImpl<BooleanState>
case ColorDisplayController.AUTO_MODE_CUSTOM:
// User-specified time, approximated to the nearest hour.
- return isNightLightActivated
- ? mContext.getString(
- R.string.quick_settings_night_secondary_label_until,
- mController.getCustomEndTime().format(
- DateTimeFormatter.ofPattern(
- APPROXIMATE_HOUR_DATE_TIME_PATTERN)))
- : mContext.getString(
- R.string.quick_settings_night_secondary_label_on_at,
- mController.getCustomStartTime().format(
- DateTimeFormatter.ofPattern(
- APPROXIMATE_HOUR_DATE_TIME_PATTERN)));
+ final @StringRes int toggleTimeStringRes;
+ final LocalTime toggleTime;
+ final DateTimeFormatter toggleTimeFormat;
+
+ if (isNightLightActivated) {
+ toggleTime = mController.getCustomEndTime();
+ toggleTimeStringRes = R.string.quick_settings_night_secondary_label_until;
+ } else {
+ toggleTime = mController.getCustomStartTime();
+ toggleTimeStringRes = R.string.quick_settings_night_secondary_label_on_at;
+ }
+
+ // Choose between just showing the hour or also showing the minutes (based on the
+ // user-selected toggle time). This helps reduce how much space the label takes.
+ toggleTimeFormat = DateTimeFormatter.ofPattern(
+ toggleTime.getMinute() == 0
+ ? HOUR_MINUTE_DATE_TIME_PATTERN
+ : APPROXIMATE_HOUR_DATE_TIME_PATTERN);
+
+ return mContext.getString(toggleTimeStringRes, toggleTime.format(toggleTimeFormat));
default:
// No secondary label when auto mode is disabled.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/touch/OverScroll.java b/packages/SystemUI/src/com/android/systemui/qs/touch/OverScroll.java
new file mode 100644
index 000000000000..046488679725
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/touch/OverScroll.java
@@ -0,0 +1,57 @@
+/*
+ * 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.qs.touch;
+
+/**
+ * Utility methods for overscroll damping and related effect.
+ *
+ * Copied from packages/apps/Launcher3/src/com/android/launcher3/touch/OverScroll.java
+ */
+public class OverScroll {
+
+ private static final float OVERSCROLL_DAMP_FACTOR = 0.07f;
+
+ /**
+ * This curve determines how the effect of scrolling over the limits of the page diminishes
+ * as the user pulls further and further from the bounds
+ *
+ * @param f The percentage of how much the user has overscrolled.
+ * @return A transformed percentage based on the influence curve.
+ */
+ private static float overScrollInfluenceCurve(float f) {
+ f -= 1.0f;
+ return f * f * f + 1.0f;
+ }
+
+ /**
+ * @param amount The original amount overscrolled.
+ * @param max The maximum amount that the View can overscroll.
+ * @return The dampened overscroll amount.
+ */
+ public static int dampedScroll(float amount, int max) {
+ if (Float.compare(amount, 0) == 0) return 0;
+
+ float f = amount / max;
+ f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
+
+ // Clamp this factor, f, to -1 < f < 1
+ if (Math.abs(f) >= 1) {
+ f /= Math.abs(f);
+ }
+
+ return Math.round(OVERSCROLL_DAMP_FACTOR * f * max);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/touch/SwipeDetector.java b/packages/SystemUI/src/com/android/systemui/qs/touch/SwipeDetector.java
new file mode 100644
index 000000000000..252205201e5d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/touch/SwipeDetector.java
@@ -0,0 +1,356 @@
+/*
+ * 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.qs.touch;
+
+import static android.view.MotionEvent.INVALID_POINTER_ID;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+/**
+ * One dimensional scroll/drag/swipe gesture detector.
+ *
+ * Definition of swipe is different from android system in that this detector handles
+ * 'swipe to dismiss', 'swiping up/down a container' but also keeps scrolling state before
+ * swipe action happens
+ *
+ * Copied from packages/apps/Launcher3/src/com/android/launcher3/touch/SwipeDetector.java
+ */
+public class SwipeDetector {
+
+ private static final boolean DBG = false;
+ private static final String TAG = "SwipeDetector";
+
+ private int mScrollConditions;
+ public static final int DIRECTION_POSITIVE = 1 << 0;
+ public static final int DIRECTION_NEGATIVE = 1 << 1;
+ public static final int DIRECTION_BOTH = DIRECTION_NEGATIVE | DIRECTION_POSITIVE;
+
+ private static final float ANIMATION_DURATION = 1200;
+
+ protected int mActivePointerId = INVALID_POINTER_ID;
+
+ /**
+ * The minimum release velocity in pixels per millisecond that triggers fling..
+ */
+ public static final float RELEASE_VELOCITY_PX_MS = 1.0f;
+
+ /**
+ * The time constant used to calculate dampening in the low-pass filter of scroll velocity.
+ * Cutoff frequency is set at 10 Hz.
+ */
+ public static final float SCROLL_VELOCITY_DAMPENING_RC = 1000f / (2f * (float) Math.PI * 10);
+
+ /* Scroll state, this is set to true during dragging and animation. */
+ private ScrollState mState = ScrollState.IDLE;
+
+ enum ScrollState {
+ IDLE,
+ DRAGGING, // onDragStart, onDrag
+ SETTLING // onDragEnd
+ }
+
+ public static abstract class Direction {
+
+ abstract float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint);
+
+ /**
+ * Distance in pixels a touch can wander before we think the user is scrolling.
+ */
+ abstract float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos);
+ }
+
+ public static final Direction VERTICAL = new Direction() {
+
+ @Override
+ float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint) {
+ return ev.getY(pointerIndex) - refPoint.y;
+ }
+
+ @Override
+ float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) {
+ return Math.abs(ev.getX(pointerIndex) - downPos.x);
+ }
+ };
+
+ public static final Direction HORIZONTAL = new Direction() {
+
+ @Override
+ float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint) {
+ return ev.getX(pointerIndex) - refPoint.x;
+ }
+
+ @Override
+ float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) {
+ return Math.abs(ev.getY(pointerIndex) - downPos.y);
+ }
+ };
+
+ //------------------- ScrollState transition diagram -----------------------------------
+ //
+ // IDLE -> (mDisplacement > mTouchSlop) -> DRAGGING
+ // DRAGGING -> (MotionEvent#ACTION_UP, MotionEvent#ACTION_CANCEL) -> SETTLING
+ // SETTLING -> (MotionEvent#ACTION_DOWN) -> DRAGGING
+ // SETTLING -> (View settled) -> IDLE
+
+ private void setState(ScrollState newState) {
+ if (DBG) {
+ Log.d(TAG, "setState:" + mState + "->" + newState);
+ }
+ // onDragStart and onDragEnd is reported ONLY on state transition
+ if (newState == ScrollState.DRAGGING) {
+ initializeDragging();
+ if (mState == ScrollState.IDLE) {
+ reportDragStart(false /* recatch */);
+ } else if (mState == ScrollState.SETTLING) {
+ reportDragStart(true /* recatch */);
+ }
+ }
+ if (newState == ScrollState.SETTLING) {
+ reportDragEnd();
+ }
+
+ mState = newState;
+ }
+
+ public boolean isDraggingOrSettling() {
+ return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING;
+ }
+
+ /**
+ * There's no touch and there's no animation.
+ */
+ public boolean isIdleState() {
+ return mState == ScrollState.IDLE;
+ }
+
+ public boolean isSettlingState() {
+ return mState == ScrollState.SETTLING;
+ }
+
+ public boolean isDraggingState() {
+ return mState == ScrollState.DRAGGING;
+ }
+
+ private final PointF mDownPos = new PointF();
+ private final PointF mLastPos = new PointF();
+ private final Direction mDir;
+
+ private final float mTouchSlop;
+
+ /* Client of this gesture detector can register a callback. */
+ private final Listener mListener;
+
+ private long mCurrentMillis;
+
+ private float mVelocity;
+ private float mLastDisplacement;
+ private float mDisplacement;
+
+ private float mSubtractDisplacement;
+ private boolean mIgnoreSlopWhenSettling;
+
+ public interface Listener {
+ void onDragStart(boolean start);
+
+ boolean onDrag(float displacement, float velocity);
+
+ void onDragEnd(float velocity, boolean fling);
+ }
+
+ public SwipeDetector(@NonNull Context context, @NonNull Listener l, @NonNull Direction dir) {
+ this(ViewConfiguration.get(context).getScaledTouchSlop(), l, dir);
+ }
+
+ @VisibleForTesting
+ protected SwipeDetector(float touchSlope, @NonNull Listener l, @NonNull Direction dir) {
+ mTouchSlop = touchSlope;
+ mListener = l;
+ mDir = dir;
+ }
+
+ public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
+ mScrollConditions = scrollDirectionFlags;
+ mIgnoreSlopWhenSettling = ignoreSlop;
+ }
+
+ private boolean shouldScrollStart(MotionEvent ev, int pointerIndex) {
+ // reject cases where the angle or slop condition is not met.
+ if (Math.max(mDir.getActiveTouchSlop(ev, pointerIndex, mDownPos), mTouchSlop)
+ > Math.abs(mDisplacement)) {
+ return false;
+ }
+
+ // Check if the client is interested in scroll in current direction.
+ if (((mScrollConditions & DIRECTION_NEGATIVE) > 0 && mDisplacement > 0) ||
+ ((mScrollConditions & DIRECTION_POSITIVE) > 0 && mDisplacement < 0)) {
+ return true;
+ }
+ return false;
+ }
+
+ public boolean onTouchEvent(MotionEvent ev) {
+ switch (ev.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ mActivePointerId = ev.getPointerId(0);
+ mDownPos.set(ev.getX(), ev.getY());
+ mLastPos.set(mDownPos);
+ mLastDisplacement = 0;
+ mDisplacement = 0;
+ mVelocity = 0;
+
+ if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
+ setState(ScrollState.DRAGGING);
+ }
+ break;
+ //case MotionEvent.ACTION_POINTER_DOWN:
+ case MotionEvent.ACTION_POINTER_UP:
+ int ptrIdx = ev.getActionIndex();
+ int ptrId = ev.getPointerId(ptrIdx);
+ if (ptrId == mActivePointerId) {
+ final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
+ mDownPos.set(
+ ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
+ ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
+ mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
+ mActivePointerId = ev.getPointerId(newPointerIdx);
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ if (pointerIndex == INVALID_POINTER_ID) {
+ break;
+ }
+ mDisplacement = mDir.getDisplacement(ev, pointerIndex, mDownPos);
+ computeVelocity(mDir.getDisplacement(ev, pointerIndex, mLastPos),
+ ev.getEventTime());
+
+ // handle state and listener calls.
+ if (mState != ScrollState.DRAGGING && shouldScrollStart(ev, pointerIndex)) {
+ setState(ScrollState.DRAGGING);
+ }
+ if (mState == ScrollState.DRAGGING) {
+ reportDragging();
+ }
+ mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ // These are synthetic events and there is no need to update internal values.
+ if (mState == ScrollState.DRAGGING) {
+ setState(ScrollState.SETTLING);
+ }
+ break;
+ default:
+ break;
+ }
+ return true;
+ }
+
+ public void finishedScrolling() {
+ setState(ScrollState.IDLE);
+ }
+
+ private boolean reportDragStart(boolean recatch) {
+ mListener.onDragStart(!recatch);
+ if (DBG) {
+ Log.d(TAG, "onDragStart recatch:" + recatch);
+ }
+ return true;
+ }
+
+ private void initializeDragging() {
+ if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
+ mSubtractDisplacement = 0;
+ }
+ if (mDisplacement > 0) {
+ mSubtractDisplacement = mTouchSlop;
+ } else {
+ mSubtractDisplacement = -mTouchSlop;
+ }
+ }
+
+ private boolean reportDragging() {
+ if (mDisplacement != mLastDisplacement) {
+ if (DBG) {
+ Log.d(TAG, String.format("onDrag disp=%.1f, velocity=%.1f",
+ mDisplacement, mVelocity));
+ }
+
+ mLastDisplacement = mDisplacement;
+ return mListener.onDrag(mDisplacement - mSubtractDisplacement, mVelocity);
+ }
+ return true;
+ }
+
+ private void reportDragEnd() {
+ if (DBG) {
+ Log.d(TAG, String.format("onScrollEnd disp=%.1f, velocity=%.1f",
+ mDisplacement, mVelocity));
+ }
+ mListener.onDragEnd(mVelocity, Math.abs(mVelocity) > RELEASE_VELOCITY_PX_MS);
+
+ }
+
+ /**
+ * Computes the damped velocity.
+ */
+ public float computeVelocity(float delta, long currentMillis) {
+ long previousMillis = mCurrentMillis;
+ mCurrentMillis = currentMillis;
+
+ float deltaTimeMillis = mCurrentMillis - previousMillis;
+ float velocity = (deltaTimeMillis > 0) ? (delta / deltaTimeMillis) : 0;
+ if (Math.abs(mVelocity) < 0.001f) {
+ mVelocity = velocity;
+ } else {
+ float alpha = computeDampeningFactor(deltaTimeMillis);
+ mVelocity = interpolate(mVelocity, velocity, alpha);
+ }
+ return mVelocity;
+ }
+
+ /**
+ * Returns a time-dependent dampening factor using delta time.
+ */
+ private static float computeDampeningFactor(float deltaTime) {
+ return deltaTime / (SCROLL_VELOCITY_DAMPENING_RC + deltaTime);
+ }
+
+ /**
+ * Returns the linear interpolation between two values
+ */
+ private static float interpolate(float from, float to, float alpha) {
+ return (1.0f - alpha) * from + alpha * to;
+ }
+
+ public static long calculateDuration(float velocity, float progressNeeded) {
+ // TODO: make these values constants after tuning.
+ float velocityDivisor = Math.max(2f, Math.abs(0.5f * velocity));
+ float travelDistance = Math.max(0.2f, progressNeeded);
+ long duration = (long) Math.max(100, ANIMATION_DURATION / velocityDivisor * travelDistance);
+ if (DBG) {
+ Log.d(TAG, String.format("calculateDuration=%d, v=%f, d=%f", duration, velocity, progressNeeded));
+ }
+ return duration;
+ }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
index b2472bf73874..89cc50970989 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
@@ -16,13 +16,11 @@
package com.android.systemui.recents;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import android.annotation.TargetApi;
import android.app.ActivityManager;
-import android.content.ComponentName;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -31,6 +29,10 @@ import android.graphics.PorterDuff;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.RippleDrawable;
import android.os.Build;
+import android.os.SystemProperties;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -41,6 +43,7 @@ import android.view.animation.DecelerateInterpolator;
import android.widget.ImageView;
import android.widget.TextView;
+import com.android.systemui.OverviewProxyService;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
@@ -50,9 +53,9 @@ import com.android.systemui.shared.system.ActivityManagerWrapper;
* Shows onboarding for the new recents interaction in P (codenamed quickstep).
*/
@TargetApi(Build.VERSION_CODES.P)
-public class SwipeUpOnboarding {
+public class RecentsOnboarding {
- private static final String TAG = "SwipeUpOnboarding";
+ private static final String TAG = "RecentsOnboarding";
private static final boolean RESET_PREFS_FOR_DEBUG = false;
private static final long SHOW_DELAY_MS = 500;
private static final long SHOW_HIDE_DURATION_MS = 300;
@@ -61,6 +64,7 @@ public class SwipeUpOnboarding {
private final Context mContext;
private final WindowManager mWindowManager;
+ private final OverviewProxyService mOverviewProxyService;
private final View mLayout;
private final TextView mTextView;
private final ImageView mDismissView;
@@ -113,11 +117,12 @@ public class SwipeUpOnboarding {
}
};
- public SwipeUpOnboarding(Context context) {
+ public RecentsOnboarding(Context context, OverviewProxyService overviewProxyService) {
mContext = context;
+ mOverviewProxyService = overviewProxyService;
final Resources res = context.getResources();
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
- mLayout = LayoutInflater.from(mContext).inflate(R.layout.recents_swipe_up_onboarding, null);
+ mLayout = LayoutInflater.from(mContext).inflate(R.layout.recents_onboarding, null);
mTextView = mLayout.findViewById(R.id.onboarding_text);
mDismissView = mLayout.findViewById(R.id.dismiss);
mDarkBackgroundColor = res.getColor(android.R.color.background_dark);
@@ -135,25 +140,25 @@ public class SwipeUpOnboarding {
mDismissView.setOnClickListener(v -> hide(true));
if (RESET_PREFS_FOR_DEBUG) {
- Prefs.putBoolean(mContext, Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, false);
+ Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, false);
Prefs.putInt(mContext, Prefs.Key.NUM_APPS_LAUNCHED, 0);
}
}
public void onConnectedToLauncher() {
- boolean alreadyLearnedSwipeUpForRecents = Prefs.getBoolean(mContext,
- Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, false);
- if (!mTaskListenerRegistered && !alreadyLearnedSwipeUpForRecents) {
+ boolean alreadySeenRecentsOnboarding = Prefs.getBoolean(mContext,
+ Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, false);
+ if (!mTaskListenerRegistered && !alreadySeenRecentsOnboarding) {
ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskListener);
mTaskListenerRegistered = true;
}
}
public void onRecentsAnimationStarted() {
- boolean alreadyLearnedSwipeUpForRecents = Prefs.getBoolean(mContext,
- Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, false);
- if (!alreadyLearnedSwipeUpForRecents) {
- Prefs.putBoolean(mContext, Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, true);
+ boolean alreadySeenRecentsOnboarding = Prefs.getBoolean(mContext,
+ Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, false);
+ if (!alreadySeenRecentsOnboarding) {
+ Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, true);
onDisconnectedFromLauncher();
}
}
@@ -173,6 +178,15 @@ public class SwipeUpOnboarding {
}
public void show() {
+ if (!shouldShow()) {
+ return;
+ }
+ CharSequence onboardingText = mOverviewProxyService.getOnboardingText();
+ if (TextUtils.isEmpty(onboardingText)) {
+ Log.w(TAG, "Unable to get onboarding text");
+ return;
+ }
+ mTextView.setText(onboardingText);
// Only show in portrait.
int orientation = mContext.getResources().getConfiguration().orientation;
if (!mLayoutAttachedToWindow && orientation == Configuration.ORIENTATION_PORTRAIT) {
@@ -196,6 +210,14 @@ public class SwipeUpOnboarding {
}
}
+ /**
+ * @return True unless setprop has been set to false, or we're in demo mode.
+ */
+ private boolean shouldShow() {
+ return SystemProperties.getBoolean("persist.quickstep.onboarding.enabled",
+ !(mContext.getSystemService(UserManager.class)).isDemoUser());
+ }
+
public void hide(boolean animate) {
if (mLayoutAttachedToWindow) {
if (animate) {
@@ -208,6 +230,7 @@ public class SwipeUpOnboarding {
.withEndAction(() -> mWindowManager.removeView(mLayout))
.start();
} else {
+ mLayout.animate().cancel();
mWindowManager.removeView(mLayout);
}
}
@@ -239,7 +262,7 @@ public class SwipeUpOnboarding {
flags,
PixelFormat.TRANSLUCENT);
lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
- lp.setTitle("SwipeUpOnboarding");
+ lp.setTitle("RecentsOnboarding");
lp.gravity = Gravity.BOTTOM;
return lp;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 57f7818eae58..3dd6e353c924 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -36,6 +36,7 @@ import android.view.accessibility.AccessibilityManager;
import android.view.animation.DecelerateInterpolator;
import android.widget.Button;
import android.widget.FrameLayout;
+import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -255,6 +256,11 @@ public class ScreenPinningRequest implements View.OnClickListener {
: R.string.screen_pinning_description_recents_invisible;
}
+ ((ImageView) mLayout.findViewById(R.id.screen_pinning_back_icon))
+ .setImageDrawable(navigationBarView.getBackDrawable(mContext));
+ ((ImageView) mLayout.findViewById(R.id.screen_pinning_home_icon))
+ .setImageDrawable(navigationBarView.getHomeDrawable(mContext));
+
((TextView) mLayout.findViewById(R.id.screen_pinning_description))
.setText(descriptionStringResId);
final int backBgVisibility = touchExplorationEnabled ? View.INVISIBLE : View.VISIBLE;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index bf4a225a00ec..2acb1bb2613b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -38,10 +38,12 @@ import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.Canvas;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
+import android.graphics.Picture;
import android.graphics.PixelFormat;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -57,13 +59,10 @@ import android.provider.MediaStore;
import android.util.DisplayMetrics;
import android.util.Slog;
import android.view.Display;
-import android.view.DisplayListCanvas;
import android.view.LayoutInflater;
import android.view.MotionEvent;
-import android.view.RenderNode;
import android.view.Surface;
import android.view.SurfaceControl;
-import android.view.ThreadedRenderer;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
@@ -233,14 +232,12 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
*/
private Bitmap generateAdjustedHwBitmap(Bitmap bitmap, int width, int height, Matrix matrix,
Paint paint, int color) {
- RenderNode node = RenderNode.create("ScreenshotCanvas", null);
- node.setLeftTopRightBottom(0, 0, width, height);
- node.setClipToBounds(false);
- DisplayListCanvas canvas = node.start(width, height);
+ Picture picture = new Picture();
+ Canvas canvas = picture.beginRecording(width, height);
canvas.drawColor(color);
canvas.drawBitmap(bitmap, matrix, paint);
- node.end(canvas);
- return ThreadedRenderer.createHardwareBitmap(node, width, height);
+ picture.endRecording();
+ return Bitmap.createBitmap(picture);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index eb5619b11487..0876507465a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -308,6 +308,10 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
}
}
+ public void setRippleAllowed(boolean allowed) {
+ mBackgroundNormal.setPressedAllowed(allowed);
+ }
+
private boolean handleTouchEventDimmed(MotionEvent event) {
if (mNeedsDimming && !mDimmed) {
// We're actually dimmed, but our content isn't dimmable, let's ensure we have a ripple
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 11bdf6b3c72e..fa177f2bb26c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -156,7 +156,7 @@ public class CommandQueue extends IStatusBar.Stub {
default void handleShowGlobalActionsMenu() { }
default void handleShowShutdownUi(boolean isReboot, String reason) { }
- default void showChargingAnimation(int batteryLevel) { }
+ default void showWirelessChargingAnimation(int batteryLevel) { }
default void onRotationProposal(int rotation, boolean isValid) { }
@@ -497,7 +497,7 @@ public class CommandQueue extends IStatusBar.Stub {
}
@Override
- public void showChargingAnimation(int batteryLevel) {
+ public void showWirelessChargingAnimation(int batteryLevel) {
mHandler.removeMessages(MSG_SHOW_CHARGING_ANIMATION);
mHandler.obtainMessage(MSG_SHOW_CHARGING_ANIMATION, batteryLevel, 0)
.sendToTarget();
@@ -784,7 +784,7 @@ public class CommandQueue extends IStatusBar.Stub {
break;
case MSG_SHOW_CHARGING_ANIMATION:
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).showChargingAnimation(msg.arg1);
+ mCallbacks.get(i).showWirelessChargingAnimation(msg.arg1);
}
break;
case MSG_SHOW_PINNING_TOAST_ENTER_EXIT:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index b3f68d357083..2723df73aa52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -370,14 +370,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mNotificationInflater.inflateNotificationViews();
}
- @Override
- public void setPressed(boolean pressed) {
- if (isOnKeyguard() || mEntry.notification.getNotification().contentIntent == null) {
- // We're dropping the ripple if we have a collapse / launch animation
- super.setPressed(pressed);
- }
- }
-
public void onNotificationUpdated() {
for (NotificationContentView l : mLayouts) {
l.onNotificationUpdated(mEntry);
@@ -407,6 +399,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
showBlockingHelper(mEntry.userSentiment ==
NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE);
+ updateRippleAllowed();
}
@VisibleForTesting
@@ -1805,6 +1798,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
}
}
+ updateRippleAllowed();
+ }
+
+ private void updateRippleAllowed() {
+ boolean allowed = isOnKeyguard()
+ || mEntry.notification.getNotification().contentIntent == null;
+ setRippleAllowed(allowed);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index b7a15005b170..22e8909b4812 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -300,34 +300,10 @@ public class KeyguardIndicationController {
} else if (mPowerPluggedIn) {
String indication = computePowerIndication();
if (animate) {
- int yTranslation = mContext.getResources().getInteger(
- R.integer.wired_charging_aod_text_animation_distance);
- int animateUpDuration = mContext.getResources().getInteger(
- R.integer.wired_charging_aod_text_animation_duration_up);
- int animateDownDuration = mContext.getResources().getInteger(
- R.integer.wired_charging_aod_text_animation_duration_down);
- mTextView.animate()
- .translationYBy(yTranslation)
- .setInterpolator(Interpolators.LINEAR)
- .setDuration(animateUpDuration)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- mTextView.switchIndication(indication);
- }
- @Override
- public void onAnimationEnd(Animator animation) {
- mTextView.animate()
- .setDuration(animateDownDuration)
- .setInterpolator(Interpolators.BOUNCE)
- .translationYBy(-1 * yTranslation)
- .setListener(null);
- }
- });
+ animateText(mTextView, indication);
} else {
mTextView.switchIndication(indication);
}
-
} else {
String percentage = NumberFormat.getPercentInstance()
.format(mBatteryLevel / 100f);
@@ -355,8 +331,12 @@ public class KeyguardIndicationController {
if (DEBUG_CHARGING_SPEED) {
indication += ", " + (mChargingWattage / 1000) + " mW";
}
- mTextView.switchIndication(indication);
mTextView.setTextColor(mInitialTextColor);
+ if (animate) {
+ animateText(mTextView, indication);
+ } else {
+ mTextView.switchIndication(indication);
+ }
} else if (!TextUtils.isEmpty(trustManagedIndication)
&& updateMonitor.getUserTrustIsManaged(userId)
&& !updateMonitor.getUserHasTrust(userId)) {
@@ -369,6 +349,34 @@ public class KeyguardIndicationController {
}
}
+ // animates textView - textView moves up and bounces down
+ private void animateText(KeyguardIndicationTextView textView, String indication) {
+ int yTranslation = mContext.getResources().getInteger(
+ R.integer.wired_charging_keyguard_text_animation_distance);
+ int animateUpDuration = mContext.getResources().getInteger(
+ R.integer.wired_charging_keyguard_text_animation_duration_up);
+ int animateDownDuration = mContext.getResources().getInteger(
+ R.integer.wired_charging_keyguard_text_animation_duration_down);
+ textView.animate()
+ .translationYBy(yTranslation)
+ .setInterpolator(Interpolators.LINEAR)
+ .setDuration(animateUpDuration)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ textView.switchIndication(indication);
+ }
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ textView.animate()
+ .setDuration(animateDownDuration)
+ .setInterpolator(Interpolators.BOUNCE)
+ .translationYBy(-1 * yTranslation)
+ .setListener(null);
+ }
+ });
+ }
+
private String computePowerIndication() {
if (mPowerCharged) {
return mContext.getResources().getString(R.string.keyguard_charged);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
index d6beb7fb2699..0ff4dde580b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
@@ -28,6 +28,7 @@ import android.graphics.drawable.RippleDrawable;
import android.util.AttributeSet;
import android.view.View;
+import com.android.internal.util.ArrayUtils;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
@@ -50,6 +51,7 @@ public class NotificationBackgroundView extends View {
private boolean mExpandAnimationRunning;
private float mActualWidth;
private int mDrawableAlpha = 255;
+ private boolean mIsPressedAllowed;
public NotificationBackgroundView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -94,13 +96,7 @@ public class NotificationBackgroundView extends View {
@Override
protected void drawableStateChanged() {
- drawableStateChanged(mBackground);
- }
-
- private void drawableStateChanged(Drawable d) {
- if (d != null && d.isStateful()) {
- d.setState(getDrawableState());
- }
+ setState(getDrawableState());
}
@Override
@@ -177,7 +173,13 @@ public class NotificationBackgroundView extends View {
}
public void setState(int[] drawableState) {
- mBackground.setState(drawableState);
+ if (mBackground != null && mBackground.isStateful()) {
+ if (!mIsPressedAllowed) {
+ drawableState = ArrayUtils.removeInt(drawableState,
+ com.android.internal.R.attr.state_pressed);
+ }
+ mBackground.setState(drawableState);
+ }
}
public void setRippleColor(int color) {
@@ -249,10 +251,17 @@ public class NotificationBackgroundView extends View {
(GradientDrawable) ((LayerDrawable) mBackground).getDrawable(0);
gradientDrawable.setXfermode(
running ? new PorterDuffXfermode(PorterDuff.Mode.SRC) : null);
+ // Speed optimization: disable AA if transfer mode is not SRC_OVER. AA is not easy to
+ // spot during animation anyways.
+ gradientDrawable.setAntiAlias(!running);
}
if (!mExpandAnimationRunning) {
setDrawableAlpha(mDrawableAlpha);
}
invalidate();
}
+
+ public void setPressedAllowed(boolean allowed) {
+ mIsPressedAllowed = allowed;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index e811ed3d77ae..c4d0b79a69c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -22,7 +22,6 @@ import android.app.RemoteInput;
import android.content.Context;
import android.graphics.Rect;
import android.os.Build;
-import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
import android.util.Log;
@@ -36,6 +35,7 @@ import android.widget.LinearLayout;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.NotificationColorUtil;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.HybridGroupManager;
import com.android.systemui.statusbar.notification.HybridNotificationView;
@@ -44,6 +44,7 @@ import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.NotificationViewWrapper;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.policy.RemoteInputView;
+import com.android.systemui.statusbar.policy.SmartReplyConstants;
import com.android.systemui.statusbar.policy.SmartReplyView;
/**
@@ -75,6 +76,8 @@ public class NotificationContentView extends FrameLayout {
private RemoteInputView mExpandedRemoteInput;
private RemoteInputView mHeadsUpRemoteInput;
+
+ private SmartReplyConstants mSmartReplyConstants;
private SmartReplyView mExpandedSmartReplyView;
private NotificationViewWrapper mContractedWrapper;
@@ -145,6 +148,7 @@ public class NotificationContentView extends FrameLayout {
public NotificationContentView(Context context, AttributeSet attrs) {
super(context, attrs);
mHybridGroupManager = new HybridGroupManager(getContext(), this);
+ mSmartReplyConstants = Dependency.get(SmartReplyConstants.class);
initView();
}
@@ -1166,8 +1170,7 @@ public class NotificationContentView extends FrameLayout {
return;
}
- boolean enableSmartReplies = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.ENABLE_SMART_REPLIES_IN_NOTIFICATIONS, 0) != 0;
+ boolean enableSmartReplies = mSmartReplyConstants.isEnabled();
boolean hasRemoteInput = false;
RemoteInput remoteInputWithChoices = null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
index 1aaa3b2593d2..f730601ac919 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
@@ -24,6 +24,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
+import android.net.Uri;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -131,12 +132,13 @@ public class NotificationGutsManager implements Dumpable {
}
/**
- * Sends an intent to open the notification settings for a particular package and optional
+ * Sends an intent to open the app settings for a particular package and optional
* channel.
*/
private void startAppNotificationSettingsActivity(String packageName, final int appUid,
final NotificationChannel channel, ExpandableNotificationRow row) {
- final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
+ final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+ intent.setData(Uri.fromParts("package", packageName, null));
intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
intent.putExtra(Settings.EXTRA_APP_UID, appUid);
if (channel != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
index 735f4fd82dec..afe906c28ea5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
@@ -50,7 +50,6 @@ import com.android.settingslib.Utils;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import java.lang.IllegalArgumentException;
import java.util.List;
import java.util.Set;
@@ -274,7 +273,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
}
private void saveImportance() {
- if (mNonblockable || !hasImportanceChanged()) {
+ if (mNonblockable) {
return;
}
MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
@@ -409,7 +408,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
final int centerY = v.getHeight() / 2;
final int x = targetLoc[0] - parentLoc[0] + centerX;
final int y = targetLoc[1] - parentLoc[1] + centerY;
- mGutsContainer.closeControls(x, y, false /* save */, false /* force */);
+ mGutsContainer.closeControls(x, y, true /* save */, false /* force */);
}
@Override
@@ -429,7 +428,9 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
@Override
public boolean handleCloseControls(boolean save, boolean force) {
- if (save && hasImportanceChanged()) {
+ // Save regardless of the importance so we can lock the importance field if the user wants
+ // to keep getting notifications
+ if (save) {
if (mCheckSaveListener != null) {
mCheckSaveListener.checkSave(this::saveImportance, mSbn);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index f25379ab0b22..3c480d80dea8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -299,6 +299,11 @@ public class NotificationRemoteInputManager implements Dumpable {
}
}, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY);
}
+ try {
+ mBarService.onNotificationDirectReplied(entry.notification.getKey());
+ } catch (RemoteException e) {
+ // Nothing to do, system going down
+ }
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index 46b218f0de6b..13b13fd943f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -38,9 +38,9 @@ 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.statusbar.phone.SignalDrawable;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.DarkIconDispatcher;
import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java
new file mode 100644
index 000000000000..53101a5bd61f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java
@@ -0,0 +1,161 @@
+package com.android.systemui.statusbar.car;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import com.android.keyguard.AlphaOptimizedImageButton;
+import com.android.systemui.R;
+
+/**
+ * CarFacetButton is a ui component designed to be used as a shortcut for an app of a defined
+ * category. It can also render a indicator impling that there are more options of apps to launch
+ * using this component. This is done with a "More icon" currently an arrow as defined in the layout
+ * file. The class is to serve as an example.
+ * Usage example: A button that allows a user to select a music app and indicate that there are
+ * other music apps installed.
+ */
+public class CarFacetButton extends LinearLayout {
+ private static final float SELECTED_ALPHA = 1f;
+ private static final float UNSELECTED_ALPHA = 0.7f;
+
+ private static final String FACET_FILTER_DELIMITER = ";";
+ /**
+ * Extra information to be sent to a helper to make the decision of what app to launch when
+ * clicked.
+ */
+ private static final String EXTRA_FACET_CATEGORIES = "categories";
+ private static final String EXTRA_FACET_PACKAGES = "packages";
+ private static final String EXTRA_FACET_ID = "filter_id";
+ private static final String EXTRA_FACET_LAUNCH_PICKER = "launch_picker";
+
+ private Context mContext;
+ private AlphaOptimizedImageButton mIcon;
+ private AlphaOptimizedImageButton mMoreIcon;
+ private boolean mSelected = false;
+ /** App categories that are to be used with this widget */
+ private String[] mFacetCategories;
+ /** App packages that are allowed to be used with this widget */
+ private String[] mFacetPackages;
+
+
+ public CarFacetButton(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mContext = context;
+ View.inflate(context, R.layout.car_facet_button, this);
+
+ // extract custom attributes
+ TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CarFacetButton);
+ setupIntents(typedArray);
+ setupIcons(typedArray);
+ }
+
+ /**
+ * Reads the custom attributes to setup click handlers for this component.
+ */
+ private void setupIntents(TypedArray typedArray) {
+ String intentString = typedArray.getString(R.styleable.CarFacetButton_intent);
+ String longPressIntentString = typedArray.getString(R.styleable.CarFacetButton_longIntent);
+ String categoryString = typedArray.getString(R.styleable.CarFacetButton_categories);
+ String packageString = typedArray.getString(R.styleable.CarFacetButton_packages);
+ try {
+ final Intent intent = Intent.parseUri(intentString, Intent.URI_INTENT_SCHEME);
+ intent.putExtra(EXTRA_FACET_ID, Integer.toString(getId()));
+
+ if (packageString != null) {
+ mFacetPackages = packageString.split(FACET_FILTER_DELIMITER);
+ intent.putExtra(EXTRA_FACET_PACKAGES, mFacetPackages);
+ }
+ if (categoryString != null) {
+ mFacetCategories = categoryString.split(FACET_FILTER_DELIMITER);
+ intent.putExtra(EXTRA_FACET_CATEGORIES, mFacetCategories);
+ }
+
+ setOnClickListener(v -> {
+ intent.putExtra(EXTRA_FACET_LAUNCH_PICKER, mSelected);
+ mContext.startActivity(intent);
+ });
+
+ if (longPressIntentString != null) {
+ final Intent longPressIntent = Intent.parseUri(longPressIntentString,
+ Intent.URI_INTENT_SCHEME);
+ setOnLongClickListener(v -> {
+ mContext.startActivity(longPressIntent);
+ return true;
+ });
+ }
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to attach intent", e);
+ }
+ }
+
+
+ private void setupIcons(TypedArray styledAttributes) {
+ mIcon = findViewById(R.id.car_nav_button_icon);
+ mIcon.setScaleType(ImageView.ScaleType.CENTER);
+ mIcon.setClickable(false);
+ mIcon.setAlpha(UNSELECTED_ALPHA);
+ int iconResourceId = styledAttributes.getResourceId(R.styleable.CarFacetButton_icon, 0);
+ if (iconResourceId == 0) {
+ throw new RuntimeException("specified icon resource was not found and is required");
+ }
+ mIcon.setImageResource(iconResourceId);
+
+ mMoreIcon = findViewById(R.id.car_nav_button_more_icon);
+ mMoreIcon.setClickable(false);
+ mMoreIcon.setImageDrawable(getContext().getDrawable(R.drawable.car_ic_arrow));
+ mMoreIcon.setAlpha(UNSELECTED_ALPHA);
+ mMoreIcon.setVisibility(GONE);
+ }
+
+ /**
+ * @return The app categories the component represents
+ */
+ public String[] getCategories() {
+ if (mFacetCategories == null) {
+ return new String[0];
+ }
+ return mFacetCategories;
+ }
+
+ /**
+ * @return The valid packages that should be considered.
+ */
+ public String[] getFacetPackages() {
+ if (mFacetPackages == null) {
+ return new String[0];
+ }
+ return mFacetPackages;
+ }
+
+ /**
+ * Updates the alpha of the icons to "selected" and shows the "More icon"
+ * @param selected true if the view must be selected, false otherwise
+ */
+ public void setSelected(boolean selected) {
+ super.setSelected(selected);
+ setSelected(selected, selected);
+ }
+
+ /**
+ * Updates the visual state to let the user know if it's been selected.
+ * @param selected true if should update the alpha of the icon to selected, false otherwise
+ * @param showMoreIcon true if the "more icon" should be shown, false otherwise
+ */
+ public void setSelected(boolean selected, boolean showMoreIcon) {
+ mSelected = selected;
+ if (selected) {
+ mMoreIcon.setVisibility(showMoreIcon ? VISIBLE : GONE);
+ mMoreIcon.setAlpha(SELECTED_ALPHA);
+ mIcon.setAlpha(SELECTED_ALPHA);
+ } else {
+ mMoreIcon.setVisibility(GONE);
+ mIcon.setAlpha(UNSELECTED_ALPHA);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java
new file mode 100644
index 000000000000..e8c9a5e5693a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java
@@ -0,0 +1,114 @@
+package com.android.systemui.statusbar.car;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * CarFacetButtons placed on the nav bar are designed to have visual indication that the active
+ * application on screen is associated with it. This is basically a similar concept to a radio
+ * button group.
+ */
+public class CarFacetButtonController {
+
+ protected HashMap<String, CarFacetButton> mButtonsByCategory = new HashMap<>();
+ protected HashMap<String, CarFacetButton> mButtonsByPackage = new HashMap<>();
+ protected CarFacetButton mSelectedFacetButton;
+ protected Context mContext;
+
+ public CarFacetButtonController(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Goes through the supplied CarNavigationBarView and keeps track of all the CarFacetButtons
+ * such that it can select and unselect them based on running task chages
+ * @param bar that may contain CarFacetButtons
+ */
+ public void addCarNavigationBar(CarNavigationBarView bar) {
+ findFacets(bar);
+ }
+
+ private void findFacets(ViewGroup root) {
+ final int childCount = root.getChildCount();
+
+ for (int i = 0; i < childCount; ++i) {
+ final View v = root.getChildAt(i);
+ if (v instanceof CarFacetButton) {
+ CarFacetButton facetButton = (CarFacetButton) v;
+ String[] categories = facetButton.getCategories();
+ for (int j = 0; j < categories.length; j++) {
+ String category = categories[j];
+ mButtonsByCategory.put(category, facetButton);
+ }
+
+ String[] facetPackages = facetButton.getFacetPackages();
+ for (int j = 0; j < facetPackages.length; j++) {
+ String facetPackage = facetPackages[j];
+ mButtonsByPackage.put(facetPackage, facetButton);
+ }
+ } else if (v instanceof ViewGroup) {
+ findFacets((ViewGroup) v);
+ }
+ }
+ }
+
+
+ /**
+ * This will unselect the currently selected CarFacetButton and determine which one should be
+ * selected next. It does this by reading the properties on the CarFacetButton and seeing if
+ * they are a match with the supplied taskino.
+ * @param taskInfo of the currently running application
+ */
+ public void taskChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ if (taskInfo == null || taskInfo.baseActivity == null) {
+ return;
+ }
+ String packageName = taskInfo.baseActivity.getPackageName();
+
+ // If the package name belongs to a filter, then highlight appropriate button in
+ // the navigation bar.
+ if (mSelectedFacetButton != null) {
+ mSelectedFacetButton.setSelected(false);
+ }
+ CarFacetButton facetButton = mButtonsByPackage.get(packageName);
+ if (facetButton != null) {
+ facetButton.setSelected(true);
+ mSelectedFacetButton = facetButton;
+ } else {
+ String category = getPackageCategory(packageName);
+ if (category != null) {
+ facetButton = mButtonsByCategory.get(category);
+ facetButton.setSelected(true);
+ mSelectedFacetButton = facetButton;
+ }
+ }
+ }
+
+ protected String getPackageCategory(String packageName) {
+ PackageManager pm = mContext.getPackageManager();
+ Set<String> supportedCategories = mButtonsByCategory.keySet();
+ for (String category : supportedCategories) {
+ Intent intent = new Intent();
+ intent.setPackage(packageName);
+ intent.setAction(Intent.ACTION_MAIN);
+ intent.addCategory(category);
+ List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
+ if (list.size() > 0) {
+ // Cache this package name into facetPackageMap, so we won't have to query
+ // all categories next time this package name shows up.
+ mButtonsByPackage.put(packageName, mButtonsByCategory.get(category));
+ return category;
+ }
+ }
+ return null;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
deleted file mode 100644
index 64c52ed6d29f..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
+++ /dev/null
@@ -1,407 +0,0 @@
-/*
- * Copyright (C) 2015 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.car;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
-import android.support.v4.util.SimpleArrayMap;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.SparseBooleanArray;
-import android.view.View;
-import android.widget.LinearLayout;
-import com.android.systemui.R;
-
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A controller to populate data for CarNavigationBarView and handle user interactions.
- *
- * <p>Each button inside the navigation bar is defined by data in arrays_car.xml. OEMs can
- * customize the navigation buttons by updating arrays_car.xml appropriately in an overlay.
- */
-class CarNavigationBarController {
- private static final String TAG = "CarNavBarController";
-
- private static final String EXTRA_FACET_CATEGORIES = "categories";
- private static final String EXTRA_FACET_PACKAGES = "packages";
- private static final String EXTRA_FACET_ID = "filter_id";
- private static final String EXTRA_FACET_LAUNCH_PICKER = "launch_picker";
-
- /**
- * Each facet of the navigation bar maps to a set of package names or categories defined in
- * arrays_car.xml. Package names for a given facet are delimited by ";".
- */
- private static final String FACET_FILTER_DELIMITER = ";";
-
- private final Context mContext;
- private final CarNavigationBarView mNavBar;
- private final CarStatusBar mStatusBar;
-
- /**
- * Set of categories each facet will filter on.
- */
- private final List<String[]> mFacetCategories = new ArrayList<>();
-
- /**
- * Set of package names each facet will filter on.
- */
- private final List<String[]> mFacetPackages = new ArrayList<>();
-
- private final SimpleArrayMap<String, Integer> mFacetCategoryMap = new SimpleArrayMap<>();
- private final SimpleArrayMap<String, Integer> mFacetPackageMap = new SimpleArrayMap<>();
-
- private final List<CarNavigationButton> mNavButtons = new ArrayList<>();
-
- private final SparseBooleanArray mFacetHasMultipleAppsCache = new SparseBooleanArray();
-
- private int mCurrentFacetIndex;
- private Intent mPersistentTaskIntent;
-
- public CarNavigationBarController(Context context, CarNavigationBarView navBar,
- CarStatusBar activityStarter) {
- mContext = context;
- mNavBar = navBar;
- mStatusBar = activityStarter;
- bind();
-
- if (context.getResources().getBoolean(R.bool.config_enablePersistentDockedActivity)) {
- setupPersistentDockedTask();
- }
- }
-
- private void setupPersistentDockedTask() {
- try {
- mPersistentTaskIntent = Intent.parseUri(
- mContext.getString(R.string.config_persistentDockedActivityIntentUri),
- Intent.URI_INTENT_SCHEME);
- } catch (URISyntaxException e) {
- Log.e(TAG, "Malformed persistent task intent.");
- }
- }
-
- public void taskChanged(String packageName, ActivityManager.RunningTaskInfo taskInfo) {
- // If the package name belongs to a filter, then highlight appropriate button in
- // the navigation bar.
- if (mFacetPackageMap.containsKey(packageName)) {
- setCurrentFacet(mFacetPackageMap.get(packageName));
- }
-
- // Check if the package matches any of the categories for the facets
- String category = getPackageCategory(packageName);
- if (category != null) {
- setCurrentFacet(mFacetCategoryMap.get(category));
- }
-
- // Set up the persistent docked task if needed.
- boolean isHomeTask =
- taskInfo.configuration.windowConfiguration.getActivityType() == ACTIVITY_TYPE_HOME;
- if (mPersistentTaskIntent != null && !mStatusBar.hasDockedTask() && !isHomeTask) {
- mStatusBar.startActivityOnStack(mPersistentTaskIntent,
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
- }
- }
-
- public void onPackageChange(String packageName) {
- if (mFacetPackageMap.containsKey(packageName)) {
- int index = mFacetPackageMap.get(packageName);
- mFacetHasMultipleAppsCache.put(index, facetHasMultiplePackages(index));
- // No need to check categories because we've already refreshed the cache.
- return;
- }
-
- String category = getPackageCategory(packageName);
- if (mFacetCategoryMap.containsKey(category)) {
- int index = mFacetCategoryMap.get(category);
- mFacetHasMultipleAppsCache.put(index, facetHasMultiplePackages(index));
- }
- }
-
- /**
- * Iterates through the items in arrays_car.xml and sets up the facet bar buttons to
- * perform the task in that configuration file when clicked or long-pressed.
- */
- private void bind() {
- Resources res = mContext.getResources();
-
- TypedArray icons = res.obtainTypedArray(R.array.car_facet_icons);
- TypedArray intents = res.obtainTypedArray(R.array.car_facet_intent_uris);
- TypedArray longPressIntents = res.obtainTypedArray(R.array.car_facet_longpress_intent_uris);
- TypedArray facetPackageNames = res.obtainTypedArray(R.array.car_facet_package_filters);
- TypedArray facetCategories = res.obtainTypedArray(R.array.car_facet_category_filters);
-
- try {
- if (icons.length() != intents.length()
- || icons.length() != longPressIntents.length()
- || icons.length() != facetPackageNames.length()
- || icons.length() != facetCategories.length()) {
- throw new RuntimeException("car_facet array lengths do not match");
- }
-
- for (int i = 0, size = icons.length(); i < size; i++) {
- Drawable icon = icons.getDrawable(i);
- CarNavigationButton button = createNavButton(icon);
- initClickListeners(button, i, intents.getString(i), longPressIntents.getString(i));
-
- mNavButtons.add(button);
- mNavBar.addButton(button, createNavButton(icon) /* lightsOutButton */);
-
- initFacetFilterMaps(i, facetPackageNames.getString(i).split(FACET_FILTER_DELIMITER),
- facetCategories.getString(i).split(FACET_FILTER_DELIMITER));
- mFacetHasMultipleAppsCache.put(i, facetHasMultiplePackages(i));
- }
- } finally {
- // Clean up all the TypedArrays.
- icons.recycle();
- intents.recycle();
- longPressIntents.recycle();
- facetPackageNames.recycle();
- facetCategories.recycle();
- }
- }
-
- /**
- * Recreates each of the buttons on a density or font scale change. This manual process is
- * necessary since this class is not part of an activity that automatically gets recreated.
- */
- public void onDensityOrFontScaleChanged() {
- TypedArray icons = mContext.getResources().obtainTypedArray(R.array.car_facet_icons);
-
- try {
- int length = icons.length();
- if (length != mNavButtons.size()) {
- // This should not happen since the mNavButtons list is created from the length
- // of the icons array in bind().
- throw new RuntimeException("car_facet array lengths do not match number of "
- + "created buttons.");
- }
-
- for (int i = 0; i < length; i++) {
- Drawable icon = icons.getDrawable(i);
-
- // Setting a new icon will trigger a requestLayout() call if necessary.
- mNavButtons.get(i).setResources(icon);
- }
- } finally {
- icons.recycle();
- }
- }
-
- private void initFacetFilterMaps(int id, String[] packageNames, String[] categories) {
- mFacetCategories.add(categories);
- for (String category : categories) {
- mFacetCategoryMap.put(category, id);
- }
-
- mFacetPackages.add(packageNames);
- for (String packageName : packageNames) {
- mFacetPackageMap.put(packageName, id);
- }
- }
-
- private String getPackageCategory(String packageName) {
- PackageManager pm = mContext.getPackageManager();
- int size = mFacetCategories.size();
- // For each facet, check if the given package name matches one of its categories
- for (int i = 0; i < size; i++) {
- String[] categories = mFacetCategories.get(i);
- for (int j = 0; j < categories.length; j++) {
- String category = categories[j];
- Intent intent = new Intent();
- intent.setPackage(packageName);
- intent.setAction(Intent.ACTION_MAIN);
- intent.addCategory(category);
- List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
- if (list.size() > 0) {
- // Cache this package name into facetPackageMap, so we won't have to query
- // all categories next time this package name shows up.
- mFacetPackageMap.put(packageName, mFacetCategoryMap.get(category));
- return category;
- }
- }
- }
- return null;
- }
-
- /**
- * Helper method to check if a given facet has multiple packages associated with it. This can
- * be resource defined package names or package names filtered by facet category.
- *
- * @return {@code true} if the facet at the given index has more than one package.
- */
- private boolean facetHasMultiplePackages(int index) {
- PackageManager pm = mContext.getPackageManager();
-
- // Check if the packages defined for the filter actually exists on the device
- String[] packages = mFacetPackages.get(index);
- if (packages.length > 1) {
- int count = 0;
- for (int i = 0; i < packages.length; i++) {
- count += pm.getLaunchIntentForPackage(packages[i]) != null ? 1 : 0;
- if (count > 1) {
- return true;
- }
- }
- }
-
- // If there weren't multiple packages defined for the facet, check the categories
- // and see if they resolve to multiple package names
- String categories[] = mFacetCategories.get(index);
-
- int count = 0;
- for (int i = 0; i < categories.length; i++) {
- String category = categories[i];
- Intent intent = new Intent();
- intent.setAction(Intent.ACTION_MAIN);
- intent.addCategory(category);
- count += pm.queryIntentActivities(intent, 0).size();
- if (count > 1) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Sets the facet at the given index to be the facet that is currently active. The button will
- * be highlighted appropriately.
- */
- private void setCurrentFacet(int index) {
- if (index == mCurrentFacetIndex) {
- return;
- }
-
- if (mNavButtons.get(mCurrentFacetIndex) != null) {
- mNavButtons.get(mCurrentFacetIndex)
- .setSelected(false /* selected */, false /* showMoreIcon */);
- }
-
- if (mNavButtons.get(index) != null) {
- mNavButtons.get(index).setSelected(true /* selected */,
- mFacetHasMultipleAppsCache.get(index) /* showMoreIcon */);
- }
-
- mCurrentFacetIndex = index;
- }
-
- /**
- * Creates the View that is used for the buttons along the navigation bar.
- *
- * @param icon The icon to be used for the button.
- */
- private CarNavigationButton createNavButton(Drawable icon) {
- CarNavigationButton button = (CarNavigationButton) View.inflate(mContext,
- R.layout.car_navigation_button, null);
- button.setResources(icon);
- LinearLayout.LayoutParams lp =
- new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1);
- button.setLayoutParams(lp);
-
- return button;
- }
-
- /**
- * Initializes the click and long click listeners that correspond to the given command string.
- * The click listeners are attached to the given button.
- */
- private void initClickListeners(View button, int index, String clickString,
- String longPressString) {
- // Each button at least have an action when pressed.
- if (TextUtils.isEmpty(clickString)) {
- throw new RuntimeException("Facet at index " + index + " does not have click action.");
- }
-
- try {
- Intent intent = Intent.parseUri(clickString, Intent.URI_INTENT_SCHEME);
- button.setOnClickListener(v -> onFacetClicked(intent, index));
- } catch (URISyntaxException e) {
- throw new RuntimeException("Malformed intent uri", e);
- }
-
- if (TextUtils.isEmpty(longPressString)) {
- button.setLongClickable(false);
- return;
- }
-
- try {
- Intent intent = Intent.parseUri(longPressString, Intent.URI_INTENT_SCHEME);
- button.setOnLongClickListener(v -> {
- onFacetLongClicked(intent, index);
- return true;
- });
- } catch (URISyntaxException e) {
- throw new RuntimeException("Malformed long-press intent uri", e);
- }
- }
-
- /**
- * Handles a click on a facet. A click will trigger the given Intent.
- *
- * @param index The index of the facet that was clicked.
- */
- private void onFacetClicked(Intent intent, int index) {
- String packageName = intent.getPackage();
-
- if (packageName == null && !intent.getCategories().contains(Intent.CATEGORY_HOME)) {
- return;
- }
-
- intent.putExtra(EXTRA_FACET_CATEGORIES, mFacetCategories.get(index));
- intent.putExtra(EXTRA_FACET_PACKAGES, mFacetPackages.get(index));
- // The facet is identified by the index in which it was added to the nav bar.
- // This value can be used to determine which facet was selected
- intent.putExtra(EXTRA_FACET_ID, Integer.toString(index));
-
- // If the current facet is clicked, we want to launch the picker by default
- // rather than the "preferred/last run" app.
- intent.putExtra(EXTRA_FACET_LAUNCH_PICKER, index == mCurrentFacetIndex);
-
- int windowingMode = WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
- int activityType = ACTIVITY_TYPE_UNDEFINED;
- if (intent.getCategories().contains(Intent.CATEGORY_HOME)) {
- windowingMode = WINDOWING_MODE_UNDEFINED;
- activityType = ACTIVITY_TYPE_HOME;
- }
-
- setCurrentFacet(index);
- mStatusBar.startActivityOnStack(intent, windowingMode, activityType);
- }
-
- /**
- * Handles a long-press on a facet. The long-press will trigger the given Intent.
- *
- * @param index The index of the facet that was clicked.
- */
- private void onFacetLongClicked(Intent intent, int index) {
- setCurrentFacet(index);
- mStatusBar.startActivityOnStack(intent,
- WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_UNDEFINED);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
index e5a311d099d5..1d9ef616d98d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
@@ -16,17 +16,15 @@
package com.android.systemui.statusbar.car;
+import android.app.UiModeManager;
import android.content.Context;
-import android.graphics.Canvas;
import android.util.AttributeSet;
-import android.view.MotionEvent;
+import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
+import com.android.keyguard.AlphaOptimizedImageButton;
import com.android.systemui.R;
-import com.android.systemui.plugins.statusbar.phone.NavGesture;
-import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
-import com.android.systemui.statusbar.phone.NavigationBarView;
/**
* A custom navigation bar for the automotive use case.
@@ -34,9 +32,10 @@ import com.android.systemui.statusbar.phone.NavigationBarView;
* The navigation bar in the automotive use case is more like a list of shortcuts, rendered
* in a linear layout.
*/
-class CarNavigationBarView extends NavigationBarView {
+class CarNavigationBarView extends LinearLayout {
private LinearLayout mNavButtons;
- private LinearLayout mLightsOutButtons;
+ private AlphaOptimizedImageButton mNotificationsButton;
+ private CarStatusBar mCarStatusBar;
public CarNavigationBarView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -45,99 +44,16 @@ class CarNavigationBarView extends NavigationBarView {
@Override
public void onFinishInflate() {
mNavButtons = findViewById(R.id.nav_buttons);
- mLightsOutButtons = findViewById(R.id.lights_out);
- }
- public void addButton(CarNavigationButton button, CarNavigationButton lightsOutButton){
- mNavButtons.addView(button);
- mLightsOutButtons.addView(lightsOutButton);
+ mNotificationsButton = findViewById(R.id.notifications);
+ mNotificationsButton.setOnClickListener(this::onNotificationsClick);
}
- @Override
- public void setDisabledFlags(int disabledFlags, boolean force) {
- // TODO: Populate.
+ void setStatusBar(CarStatusBar carStatusBar) {
+ mCarStatusBar = carStatusBar;
}
- @Override
- public void reorient() {
- // We expect the car head unit to always have a fixed rotation so we ignore this. The super
- // class implentation expects mRotatedViews to be populated, so if you call into it, there
- // is a possibility of a NullPointerException.
- }
-
- @Override
- public View getCurrentView() {
- return this;
- }
-
- @Override
- public void setNavigationIconHints(int hints, boolean force) {
- // We do not need to set the navigation icon hints for a vehicle
- // Calling setNavigationIconHints in the base class will result in a NPE as the car
- // navigation bar does not have a back button.
- }
-
- @Override
- public void onPluginConnected(NavGesture plugin, Context context) {
- // set to null version of the plugin ignoring incoming arg.
- super.onPluginConnected(new NullNavGesture(), context);
- }
-
- @Override
- public void onPluginDisconnected(NavGesture plugin) {
- // reinstall the null nav gesture plugin
- super.onPluginConnected(new NullNavGesture(), getContext());
- }
-
- /**
- * Null object pattern to work around expectations of the base class.
- * This is a temporary solution to have the car system ui working.
- * Already underway is a refactor of they car sys ui as to not use this class
- * hierarchy.
- */
- private static class NullNavGesture implements NavGesture {
- @Override
- public GestureHelper getGestureHelper() {
- return new GestureHelper() {
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- return false;
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent event) {
- return false;
- }
-
- @Override
- public void setBarState(boolean vertical, boolean isRtl) {
- }
-
- @Override
- public void onDraw(Canvas canvas) {
- }
-
- @Override
- public void onDarkIntensityChange(float intensity) {
- }
-
- @Override
- public void onLayout(boolean changed, int left, int top, int right, int bottom) {
- }
- };
- }
-
- @Override
- public int getVersion() {
- return 0;
- }
-
- @Override
- public void onCreate(Context sysuiContext, Context pluginContext) {
- }
-
- @Override
- public void onDestroy() {
- }
+ protected void onNotificationsClick(View v) {
+ mCarStatusBar.togglePanel();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java
index 2de358f1c292..0cdaec1432c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java
@@ -1,72 +1,87 @@
-/*
- * Copyright (C) 2015 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.car;
import android.content.Context;
-import android.graphics.drawable.Drawable;
+import android.content.Intent;
+import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.widget.ImageView;
-import android.widget.RelativeLayout;
-import com.android.keyguard.AlphaOptimizedImageButton;
import com.android.systemui.R;
+import java.net.URISyntaxException;
+
/**
- * A wrapper view for a car navigation facet, which includes a button icon and a drop down icon.
+ * CarNavigationButton is an image button that allows for a bit more configuration at the
+ * xml file level. This allows for more control via overlays instead of having to update
+ * code.
*/
-public class CarNavigationButton extends RelativeLayout {
+public class CarNavigationButton extends com.android.keyguard.AlphaOptimizedImageButton {
+
private static final float SELECTED_ALPHA = 1;
private static final float UNSELECTED_ALPHA = 0.7f;
- private AlphaOptimizedImageButton mIcon;
- private AlphaOptimizedImageButton mMoreIcon;
+ private Context mContext;
+ private String mIntent = null;
+ private String mLongIntent = null;
+ private boolean mBroadcastIntent = false;
+ private boolean mSelected = false;
+
public CarNavigationButton(Context context, AttributeSet attrs) {
super(context, attrs);
+ mContext = context;
+ TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CarNavigationButton);
+ mIntent = typedArray.getString(R.styleable.CarNavigationButton_intent);
+ mLongIntent = typedArray.getString(R.styleable.CarNavigationButton_longIntent);
+ mBroadcastIntent = typedArray.getBoolean(R.styleable.CarNavigationButton_broadcast, false);
}
+
+ /**
+ * After the standard inflate this then adds the xml defined intents to click and long click
+ * actions if defined.
+ */
@Override
public void onFinishInflate() {
super.onFinishInflate();
- mIcon = findViewById(R.id.car_nav_button_icon);
- mIcon.setScaleType(ImageView.ScaleType.CENTER);
- mIcon.setClickable(false);
- mIcon.setBackgroundColor(android.R.color.transparent);
- mIcon.setAlpha(UNSELECTED_ALPHA);
-
- mMoreIcon = findViewById(R.id.car_nav_button_more_icon);
- mMoreIcon.setClickable(false);
- mMoreIcon.setBackgroundColor(android.R.color.transparent);
- mMoreIcon.setVisibility(INVISIBLE);
- mMoreIcon.setImageDrawable(getContext().getDrawable(R.drawable.car_ic_arrow));
- mMoreIcon.setAlpha(UNSELECTED_ALPHA);
- }
+ setScaleType(ImageView.ScaleType.CENTER);
+ setAlpha(UNSELECTED_ALPHA);
+ try {
+ if (mIntent != null) {
+ final Intent intent = Intent.parseUri(mIntent, Intent.URI_INTENT_SCHEME);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
+ setOnClickListener(v -> {
+ if (mBroadcastIntent) {
+ mContext.sendBroadcast(intent);
+ return;
+ }
+ mContext.startActivity(intent);
+ });
+ }
+ } catch (URISyntaxException e) {
+ throw new RuntimeException("Failed to attach intent", e);
+ }
- public void setResources(Drawable icon) {
- mIcon.setImageDrawable(icon);
+ try {
+ if (mLongIntent != null) {
+ final Intent intent = Intent.parseUri(mLongIntent, Intent.URI_INTENT_SCHEME);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
+ setOnLongClickListener(v -> {
+ mContext.startActivity(intent);
+ return true;
+ });
+ }
+ } catch (URISyntaxException e) {
+ throw new RuntimeException("Failed to attach long press intent", e);
+ }
}
- public void setSelected(boolean selected, boolean showMoreIcon) {
- if (selected) {
- mMoreIcon.setVisibility(showMoreIcon ? VISIBLE : INVISIBLE);
- mMoreIcon.setAlpha(SELECTED_ALPHA);
- mIcon.setAlpha(SELECTED_ALPHA);
- } else {
- mMoreIcon.setVisibility(INVISIBLE);
- mIcon.setAlpha(UNSELECTED_ALPHA);
- }
+ /**
+ * @param selected true if should indicate if this is a selected state, false otherwise
+ */
+ public void setSelected(boolean selected) {
+ super.setSelected(selected);
+ mSelected = selected;
+ setAlpha(mSelected ? SELECTED_ALPHA : UNSELECTED_ALPHA);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 3ebeb4d45c26..c15a01330534 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -18,17 +18,14 @@ package com.android.systemui.statusbar.car;
import android.app.ActivityManager;
import android.app.ActivityOptions;
-import android.content.BroadcastReceiver;
-import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
import android.util.Log;
+import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
@@ -46,10 +43,7 @@ import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
-import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.statusbar.ExpandableNotificationRow;
-import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.phone.NavigationBarView;
@@ -69,7 +63,6 @@ public class CarStatusBar extends StatusBar implements
private TaskStackListenerImpl mTaskStackListener;
- private CarNavigationBarController mController;
private FullscreenUserSwitcher mFullscreenUserSwitcher;
private CarBatteryController mCarBatteryController;
@@ -78,15 +71,23 @@ public class CarStatusBar extends StatusBar implements
private ConnectedDeviceSignalController mConnectedDeviceSignalController;
private ViewGroup mNavigationBarWindow;
+ private ViewGroup mLeftNavigationBarWindow;
+ private ViewGroup mRightNavigationBarWindow;
private CarNavigationBarView mNavigationBarView;
+ private CarNavigationBarView mLeftNavigationBarView;
+ private CarNavigationBarView mRightNavigationBarView;
private final Object mQueueLock = new Object();
+ private boolean mShowLeft;
+ private boolean mShowRight;
+ private boolean mShowBottom;
+ private CarFacetButtonController mCarFacetButtonController;
+
@Override
public void start() {
super.start();
mTaskStackListener = new TaskStackListenerImpl();
ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
- registerPackageChangeReceivers();
mStackScroller.setScrollingEnabled(true);
@@ -104,6 +105,16 @@ public class CarStatusBar extends StatusBar implements
mNavigationBarView = null;
}
+ if (mLeftNavigationBarWindow != null) {
+ mWindowManager.removeViewImmediate(mLeftNavigationBarWindow);
+ mLeftNavigationBarView = null;
+ }
+
+ if (mRightNavigationBarWindow != null) {
+ mWindowManager.removeViewImmediate(mRightNavigationBarWindow);
+ mRightNavigationBarView = null;
+ }
+
super.destroy();
}
@@ -153,10 +164,36 @@ public class CarStatusBar extends StatusBar implements
@Override
protected void createNavigationBar() {
+ mCarFacetButtonController = new CarFacetButtonController(mContext);
if (mNavigationBarView != null) {
return;
}
+ mShowBottom = mContext.getResources().getBoolean(R.bool.config_enableBottomNavigationBar);
+ if (mShowBottom) {
+ buildBottomBar();
+ }
+
+ int widthForSides = mContext.getResources().getDimensionPixelSize(
+ R.dimen.navigation_bar_height_car_mode);
+
+
+ mShowLeft = mContext.getResources().getBoolean(R.bool.config_enableLeftNavigationBar);
+
+ if (mShowLeft) {
+ buildLeft(widthForSides);
+ }
+
+ mShowRight = mContext.getResources().getBoolean(R.bool.config_enableRightNavigationBar);
+
+ if (mShowRight) {
+ buildRight(widthForSides);
+ }
+
+ }
+
+
+ private void buildBottomBar() {
// SystemUI requires that the navigation bar view have a parent. Since the regular
// StatusBar inflates navigation_bar_window as this parent view, use the same view for the
// CarNavigationBarView.
@@ -171,17 +208,15 @@ public class CarStatusBar extends StatusBar implements
mNavigationBarView = (CarNavigationBarView) mNavigationBarWindow.getChildAt(0);
if (mNavigationBarView == null) {
Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar");
+ throw new RuntimeException("Unable to build botom nav bar due to missing layout");
}
+ mNavigationBarView.setStatusBar(this);
- mController = new CarNavigationBarController(mContext, mNavigationBarView,
- this /* ActivityStarter*/);
- mNavigationBarView.getBarTransitions().setAlwaysOpaque(true);
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
- WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
@@ -189,9 +224,74 @@ public class CarStatusBar extends StatusBar implements
lp.setTitle("CarNavigationBar");
lp.windowAnimations = 0;
+
+ mCarFacetButtonController.addCarNavigationBar(mNavigationBarView);
mWindowManager.addView(mNavigationBarWindow, lp);
}
+ private void buildLeft(int widthForSides) {
+ mLeftNavigationBarWindow = (ViewGroup) View.inflate(mContext,
+ R.layout.navigation_bar_window, null);
+ if (mLeftNavigationBarWindow == null) {
+ Log.e(TAG, "CarStatusBar failed inflate for R.layout.navigation_bar_window");
+ }
+
+ View.inflate(mContext, R.layout.car_left_navigation_bar, mLeftNavigationBarWindow);
+ mLeftNavigationBarView = (CarNavigationBarView) mLeftNavigationBarWindow.getChildAt(0);
+ if (mLeftNavigationBarView == null) {
+ Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar");
+ throw new RuntimeException("Unable to build left nav bar due to missing layout");
+ }
+ mLeftNavigationBarView.setStatusBar(this);
+ mCarFacetButtonController.addCarNavigationBar(mLeftNavigationBarView);
+
+ WindowManager.LayoutParams leftlp = new WindowManager.LayoutParams(
+ widthForSides, LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
+ PixelFormat.TRANSLUCENT);
+ leftlp.setTitle("LeftCarNavigationBar");
+ leftlp.windowAnimations = 0;
+ leftlp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
+ leftlp.gravity = Gravity.LEFT;
+ mWindowManager.addView(mLeftNavigationBarWindow, leftlp);
+ }
+
+
+ private void buildRight(int widthForSides) {
+ mRightNavigationBarWindow = (ViewGroup) View.inflate(mContext,
+ R.layout.navigation_bar_window, null);
+ if (mRightNavigationBarWindow == null) {
+ Log.e(TAG, "CarStatusBar failed inflate for R.layout.navigation_bar_window");
+ }
+
+ View.inflate(mContext, R.layout.car_right_navigation_bar, mRightNavigationBarWindow);
+ mRightNavigationBarView = (CarNavigationBarView) mRightNavigationBarWindow.getChildAt(0);
+ if (mRightNavigationBarView == null) {
+ Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar");
+ throw new RuntimeException("Unable to build right nav bar due to missing layout");
+ }
+ mRightNavigationBarView.setStatusBar(this);
+ mCarFacetButtonController.addCarNavigationBar(mRightNavigationBarView);
+
+ WindowManager.LayoutParams rightlp = new WindowManager.LayoutParams(
+ widthForSides, LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
+ PixelFormat.TRANSLUCENT);
+ rightlp.setTitle("RightCarNavigationBar");
+ rightlp.windowAnimations = 0;
+ rightlp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
+ rightlp.gravity = Gravity.RIGHT;
+ mWindowManager.addView(mRightNavigationBarWindow, rightlp);
+ }
+
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
//When executing dump() funciton simultaneously, we need to serialize them
@@ -204,8 +304,8 @@ public class CarStatusBar extends StatusBar implements
}
pw.print(" mTaskStackListener="); pw.println(mTaskStackListener);
- pw.print(" mController=");
- pw.println(mController);
+ pw.print(" mCarFacetButtonController=");
+ pw.println(mCarFacetButtonController);
pw.print(" mFullscreenUserSwitcher="); pw.println(mFullscreenUserSwitcher);
pw.print(" mCarBatteryController=");
pw.println(mCarBatteryController);
@@ -229,10 +329,6 @@ public class CarStatusBar extends StatusBar implements
}
}
- @Override
- public NavigationBarView getNavigationBarView() {
- return mNavigationBarView;
- }
@Override
public View getNavigationBarWindow() {
@@ -269,24 +365,6 @@ public class CarStatusBar extends StatusBar implements
}
}
- private BroadcastReceiver mPackageChangeReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getData() == null || mController == null) {
- return;
- }
- String packageName = intent.getData().getSchemeSpecificPart();
- mController.onPackageChange(packageName);
- }
- };
-
- private void registerPackageChangeReceivers() {
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_PACKAGE_ADDED);
- filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- filter.addDataScheme("package");
- mContext.registerReceiver(mPackageChangeReceiver, filter);
- }
public boolean hasDockedTask() {
return Recents.getSystemServices().hasDockedTask();
@@ -301,10 +379,7 @@ public class CarStatusBar extends StatusBar implements
public void onTaskStackChanged() {
ActivityManager.RunningTaskInfo runningTaskInfo =
ActivityManagerWrapper.getInstance().getRunningTask();
- if (runningTaskInfo != null && runningTaskInfo.baseActivity != null) {
- mController.taskChanged(runningTaskInfo.baseActivity.getPackageName(),
- runningTaskInfo);
- }
+ mCarFacetButtonController.taskChanged(runningTaskInfo);
}
}
@@ -346,40 +421,13 @@ public class CarStatusBar extends StatusBar implements
// Do nothing, we don't want to display media art in the lock screen for a car.
}
- private int startActivityWithOptions(Intent intent, Bundle options) {
- int result = ActivityManager.START_CANCELED;
- try {
- result = ActivityManager.getService().startActivityAsUser(null /* caller */,
- mContext.getBasePackageName(),
- intent,
- intent.resolveTypeIfNeeded(mContext.getContentResolver()),
- null /* resultTo*/,
- null /* resultWho*/,
- 0 /* requestCode*/,
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP,
- null /* profilerInfo*/,
- options,
- UserHandle.CURRENT.getIdentifier());
- } catch (RemoteException e) {
- Log.w(TAG, "Unable to start activity", e);
- }
-
- return result;
- }
-
- public int startActivityOnStack(Intent intent, int windowingMode, int activityType) {
- final ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchWindowingMode(windowingMode);
- options.setLaunchActivityType(activityType);
- return startActivityWithOptions(intent, options.toBundle());
- }
@Override
public void animateExpandNotificationsPanel() {
// Because space is usually constrained in the auto use-case, there should not be a
// pinned notification when the shade has been expanded. Ensure this by removing all heads-
// up notifications.
- mHeadsUpManager.removeAllHeadsUpEntries();
+ mHeadsUpManager.releaseAllImmediately();
super.animateExpandNotificationsPanel();
}
@@ -390,8 +438,6 @@ public class CarStatusBar extends StatusBar implements
@Override
public void onDensityOrFontScaleChanged() {
super.onDensityOrFontScaleChanged();
- mController.onDensityOrFontScaleChanged();
-
// Need to update the background on density changed in case the change was due to night
// mode.
mNotificationPanelBackground = getDefaultWallpaper();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java
index 677fa81a12cd..0304086dbfda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java
@@ -16,10 +16,10 @@ import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.widget.ImageView;
+import com.android.settingslib.graph.SignalDrawable;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.ScalingDrawableWrapper;
-import com.android.systemui.statusbar.phone.SignalDrawable;
import com.android.systemui.statusbar.policy.BluetoothController;
import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
index 11d20b221051..ef44ad17e1c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
@@ -77,11 +77,11 @@ public class ActivityLaunchAnimator {
mStatusBar = statusBar;
}
- public ActivityOptions getLaunchAnimation(
- ExpandableNotificationRow sourceNofitication) {
- AnimationRunner animationRunner = new AnimationRunner(sourceNofitication);
- return ActivityOptions.makeRemoteAnimation(
- new RemoteAnimationAdapter(animationRunner, 1000 /* Duration */, 0 /* delay */));
+ public RemoteAnimationAdapter getLaunchAnimation(
+ ExpandableNotificationRow sourceNotification) {
+ AnimationRunner animationRunner = new AnimationRunner(sourceNotification);
+ return new RemoteAnimationAdapter(animationRunner, ANIMATION_DURATION,
+ 0 /* statusBarTransitionDelay */);
}
public boolean isAnimationPending() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
index 8227b7797706..d3a325d3b91a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
@@ -21,6 +21,8 @@ import android.util.Pools;
import android.view.View;
import android.widget.ImageView;
+import com.android.internal.widget.MessagingImageMessage;
+import com.android.internal.widget.MessagingMessage;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
@@ -117,13 +119,15 @@ public class ImageTransformState extends TransformState {
@Override
protected boolean transformScale(TransformState otherState) {
- return true;
+ return sameAs(otherState);
}
@Override
public void recycle() {
super.recycle();
- sInstancePool.release(this);
+ if (getClass() == ImageTransformState.class) {
+ sInstancePool.release(this);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java
new file mode 100644
index 000000000000..b97995dd5f93
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java
@@ -0,0 +1,134 @@
+/*
+ * 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.statusbar.notification;
+
+import android.util.Pools;
+import android.view.View;
+
+import com.android.internal.widget.MessagingImageMessage;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.ViewTransformationHelper;
+
+/**
+ * A transform state of a image view.
+*/
+public class MessagingImageTransformState extends ImageTransformState {
+ private static Pools.SimplePool<MessagingImageTransformState> sInstancePool
+ = new Pools.SimplePool<>(40);
+ private static final int START_ACTUAL_WIDTH = R.id.transformation_start_actual_width;
+ private static final int START_ACTUAL_HEIGHT = R.id.transformation_start_actual_height;
+ private MessagingImageMessage mImageMessage;
+
+ @Override
+ public void initFrom(View view, TransformInfo transformInfo) {
+ super.initFrom(view, transformInfo);
+ mImageMessage = (MessagingImageMessage) view;
+ }
+
+ @Override
+ protected boolean sameAs(TransformState otherState) {
+ if (super.sameAs(otherState)) {
+ return true;
+ }
+ if (otherState instanceof MessagingImageTransformState) {
+ MessagingImageTransformState otherMessage = (MessagingImageTransformState) otherState;
+ return mImageMessage.sameAs(otherMessage.mImageMessage);
+ }
+ return false;
+ }
+
+ public static MessagingImageTransformState obtain() {
+ MessagingImageTransformState instance = sInstancePool.acquire();
+ if (instance != null) {
+ return instance;
+ }
+ return new MessagingImageTransformState();
+ }
+
+ @Override
+ protected boolean transformScale(TransformState otherState) {
+ return false;
+ }
+
+ @Override
+ protected void transformViewFrom(TransformState otherState, int transformationFlags,
+ ViewTransformationHelper.CustomTransformation customTransformation,
+ float transformationAmount) {
+ super.transformViewFrom(otherState, transformationFlags, customTransformation,
+ transformationAmount);
+ float interpolatedValue = mDefaultInterpolator.getInterpolation(
+ transformationAmount);
+ if (otherState instanceof MessagingImageTransformState && sameAs(otherState)) {
+ MessagingImageMessage otherMessage
+ = ((MessagingImageTransformState) otherState).mImageMessage;
+ if (transformationAmount == 0.0f) {
+ setStartActualWidth(otherMessage.getActualWidth());
+ setStartActualHeight(otherMessage.getActualHeight());
+ }
+ float startActualWidth = getStartActualWidth();
+ mImageMessage.setActualWidth(
+ (int) NotificationUtils.interpolate(startActualWidth,
+ mImageMessage.getStaticWidth(),
+ interpolatedValue));
+ float startActualHeight = getStartActualHeight();
+ mImageMessage.setActualHeight(
+ (int) NotificationUtils.interpolate(startActualHeight,
+ mImageMessage.getHeight(),
+ interpolatedValue));
+ }
+ }
+
+ public int getStartActualWidth() {
+ Object tag = mTransformedView.getTag(START_ACTUAL_WIDTH);
+ return tag == null ? -1 : (int) tag;
+ }
+
+ public void setStartActualWidth(int actualWidth) {
+ mTransformedView.setTag(START_ACTUAL_WIDTH, actualWidth);
+ }
+
+ public int getStartActualHeight() {
+ Object tag = mTransformedView.getTag(START_ACTUAL_HEIGHT);
+ return tag == null ? -1 : (int) tag;
+ }
+
+ public void setStartActualHeight(int actualWidth) {
+ mTransformedView.setTag(START_ACTUAL_HEIGHT, actualWidth);
+ }
+
+ @Override
+ public void recycle() {
+ super.recycle();
+ if (getClass() == MessagingImageTransformState.class) {
+ sInstancePool.release(this);
+ }
+ }
+
+ @Override
+ protected void resetTransformedView() {
+ super.resetTransformedView();
+ mImageMessage.setActualWidth(mImageMessage.getStaticWidth());
+ mImageMessage.setActualHeight(mImageMessage.getHeight());
+ }
+
+ @Override
+ protected void reset() {
+ super.reset();
+ mImageMessage = null;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
index 113118a1c8c5..314a31d336fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
@@ -22,6 +22,7 @@ import android.view.View;
import android.view.ViewGroup;
import com.android.internal.widget.MessagingGroup;
+import com.android.internal.widget.MessagingImageMessage;
import com.android.internal.widget.MessagingLayout;
import com.android.internal.widget.MessagingLinearLayout;
import com.android.internal.widget.MessagingMessage;
@@ -30,6 +31,7 @@ import com.android.systemui.Interpolators;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
/**
* A transform state of the action list
@@ -156,6 +158,7 @@ public class MessagingLayoutTransformState extends TransformState {
}
appear(ownGroup.getAvatar(), transformationAmount);
appear(ownGroup.getSenderView(), transformationAmount);
+ appear(ownGroup.getIsolatedMessage(), transformationAmount);
setClippingDeactivated(ownGroup.getSenderView(), true);
setClippingDeactivated(ownGroup.getAvatar(), true);
}
@@ -187,12 +190,13 @@ public class MessagingLayoutTransformState extends TransformState {
}
disappear(ownGroup.getAvatar(), transformationAmount);
disappear(ownGroup.getSenderView(), transformationAmount);
+ disappear(ownGroup.getIsolatedMessage(), transformationAmount);
setClippingDeactivated(ownGroup.getSenderView(), true);
setClippingDeactivated(ownGroup.getAvatar(), true);
}
private void appear(View child, float transformationAmount) {
- if (child.getVisibility() == View.GONE) {
+ if (child == null || child.getVisibility() == View.GONE) {
return;
}
TransformState ownState = TransformState.createFrom(child, mTransformInfo);
@@ -201,7 +205,7 @@ public class MessagingLayoutTransformState extends TransformState {
}
private void disappear(View child, float transformationAmount) {
- if (child.getVisibility() == View.GONE) {
+ if (child == null || child.getVisibility() == View.GONE) {
return;
}
TransformState ownState = TransformState.createFrom(child, mTransformInfo);
@@ -224,22 +228,24 @@ public class MessagingLayoutTransformState extends TransformState {
private void transformGroups(MessagingGroup ownGroup, MessagingGroup otherGroup,
float transformationAmount, boolean to) {
+ boolean useLinearTransformation =
+ otherGroup.getIsolatedMessage() == null && !mTransformInfo.isAnimating();
transformView(transformationAmount, to, ownGroup.getSenderView(), otherGroup.getSenderView(),
- true /* sameAsAny */);
+ true /* sameAsAny */, useLinearTransformation);
transformView(transformationAmount, to, ownGroup.getAvatar(), otherGroup.getAvatar(),
- true /* sameAsAny */);
- MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
- MessagingLinearLayout otherMessages = otherGroup.getMessageContainer();
+ true /* sameAsAny */, useLinearTransformation);
+ List<MessagingMessage> ownMessages = ownGroup.getMessages();
+ List<MessagingMessage> otherMessages = otherGroup.getMessages();
float previousTranslation = 0;
- for (int i = 0; i < ownMessages.getChildCount(); i++) {
- View child = ownMessages.getChildAt(ownMessages.getChildCount() - 1 - i);
+ for (int i = 0; i < ownMessages.size(); i++) {
+ View child = ownMessages.get(ownMessages.size() - 1 - i).getView();
if (isGone(child)) {
continue;
}
- int otherIndex = otherMessages.getChildCount() - 1 - i;
+ int otherIndex = otherMessages.size() - 1 - i;
View otherChild = null;
if (otherIndex >= 0) {
- otherChild = otherMessages.getChildAt(otherIndex);
+ otherChild = otherMessages.get(otherIndex).getView();
if (isGone(otherChild)) {
otherChild = null;
}
@@ -252,7 +258,12 @@ public class MessagingLayoutTransformState extends TransformState {
transformationAmount = 1.0f - transformationAmount;
}
}
- transformView(transformationAmount, to, child, otherChild, false /* sameAsAny */);
+ transformView(transformationAmount, to, child, otherChild, false, /* sameAsAny */
+ useLinearTransformation);
+ if (transformationAmount == 0.0f
+ && otherGroup.getIsolatedMessage() == otherChild) {
+ ownGroup.setTransformingImages(true);
+ }
if (otherChild == null) {
child.setTranslationY(previousTranslation);
setClippingDeactivated(child, true);
@@ -264,12 +275,13 @@ public class MessagingLayoutTransformState extends TransformState {
previousTranslation = child.getTranslationY();
}
}
+ ownGroup.updateClipRect();
}
private void transformView(float transformationAmount, boolean to, View ownView,
- View otherView, boolean sameAsAny) {
+ View otherView, boolean sameAsAny, boolean useLinearTransformation) {
TransformState ownState = TransformState.createFrom(ownView, mTransformInfo);
- if (!mTransformInfo.isAnimating()) {
+ if (useLinearTransformation) {
ownState.setDefaultInterpolator(Interpolators.LINEAR);
}
ownState.setIsSameAsAnyView(sameAsAny);
@@ -339,11 +351,15 @@ public class MessagingLayoutTransformState extends TransformState {
if (!isGone(ownGroup)) {
MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
for (int j = 0; j < ownMessages.getChildCount(); j++) {
- MessagingMessage child = (MessagingMessage) ownMessages.getChildAt(j);
+ View child = ownMessages.getChildAt(j);
setVisible(child, visible, force);
}
setVisible(ownGroup.getAvatar(), visible, force);
setVisible(ownGroup.getSenderView(), visible, force);
+ MessagingImageMessage isolatedMessage = ownGroup.getIsolatedMessage();
+ if (isolatedMessage != null) {
+ setVisible(isolatedMessage, visible, force);
+ }
}
}
}
@@ -375,11 +391,17 @@ public class MessagingLayoutTransformState extends TransformState {
}
resetTransformedView(ownGroup.getAvatar());
resetTransformedView(ownGroup.getSenderView());
+ MessagingImageMessage isolatedMessage = ownGroup.getIsolatedMessage();
+ if (isolatedMessage != null) {
+ resetTransformedView(isolatedMessage);
+ }
setClippingDeactivated(ownGroup.getAvatar(), false);
setClippingDeactivated(ownGroup.getSenderView(), false);
ownGroup.setTranslationY(0);
ownGroup.getMessageContainer().setTranslationY(0);
}
+ ownGroup.setTransformingImages(false);
+ ownGroup.updateClipRect();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
index 918b6edc0c30..fc8ceb6620a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
@@ -26,6 +26,7 @@ import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
+import com.android.internal.widget.MessagingImageMessage;
import com.android.internal.widget.MessagingPropertyAnimator;
import com.android.internal.widget.ViewClippingUtil;
import com.android.systemui.Interpolators;
@@ -80,7 +81,7 @@ public class TransformState {
private boolean mSameAsAny;
private float mTransformationEndY = UNDEFINED;
private float mTransformationEndX = UNDEFINED;
- private Interpolator mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN;
+ protected Interpolator mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN;
public void initFrom(View view, TransformInfo transformInfo) {
mTransformedView = view;
@@ -131,7 +132,7 @@ public class TransformState {
transformViewFrom(otherState, TRANSFORM_Y, null, transformationAmount);
}
- private void transformViewFrom(TransformState otherState, int transformationFlags,
+ protected void transformViewFrom(TransformState otherState, int transformationFlags,
ViewTransformationHelper.CustomTransformation customTransformation,
float transformationAmount) {
final View transformedView = mTransformedView;
@@ -449,6 +450,11 @@ public class TransformState {
result.initFrom(view, transformInfo);
return result;
}
+ if (view instanceof MessagingImageMessage) {
+ MessagingImageTransformState result = MessagingImageTransformState.obtain();
+ result.initFrom(view, transformInfo);
+ return result;
+ }
if (view instanceof ImageView) {
ImageTransformState result = ImageTransformState.obtain();
result.initFrom(view, transformInfo);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
new file mode 100644
index 000000000000..aba5cdf0ca2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -0,0 +1,446 @@
+/*
+ * 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.statusbar.phone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.v4.util.ArraySet;
+import android.util.Log;
+import android.util.Pools;
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dumpable;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.Stack;
+
+/**
+ * A implementation of HeadsUpManager for phone and car.
+ */
+public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
+ ViewTreeObserver.OnComputeInternalInsetsListener, VisualStabilityManager.Callback,
+ OnHeadsUpChangedListener {
+ private static final String TAG = "HeadsUpManagerPhone";
+ private static final boolean DEBUG = false;
+
+ private final View mStatusBarWindowView;
+ private final int mStatusBarHeight;
+ private final NotificationGroupManager mGroupManager;
+ private final StatusBar mBar;
+ private final VisualStabilityManager mVisualStabilityManager;
+
+ private boolean mReleaseOnExpandFinish;
+ private boolean mTrackingHeadsUp;
+ private HashSet<String> mSwipedOutKeys = new HashSet<>();
+ private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>();
+ private ArraySet<NotificationData.Entry> mEntriesToRemoveWhenReorderingAllowed
+ = new ArraySet<>();
+ private boolean mIsExpanded;
+ private int[] mTmpTwoArray = new int[2];
+ private boolean mHeadsUpGoingAway;
+ private boolean mWaitingOnCollapseWhenGoingAway;
+ private boolean mIsObserving;
+ private int mStatusBarState;
+
+ private final Pools.Pool<HeadsUpEntryPhone> mEntryPool = new Pools.Pool<HeadsUpEntryPhone>() {
+ private Stack<HeadsUpEntryPhone> mPoolObjects = new Stack<>();
+
+ @Override
+ public HeadsUpEntryPhone acquire() {
+ if (!mPoolObjects.isEmpty()) {
+ return mPoolObjects.pop();
+ }
+ return new HeadsUpEntryPhone();
+ }
+
+ @Override
+ public boolean release(@NonNull HeadsUpEntryPhone instance) {
+ mPoolObjects.push(instance);
+ return true;
+ }
+ };
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Constructor:
+
+ public HeadsUpManagerPhone(@NonNull final Context context, @NonNull View statusBarWindowView,
+ @NonNull NotificationGroupManager groupManager, @NonNull StatusBar bar,
+ @NonNull VisualStabilityManager visualStabilityManager) {
+ super(context);
+
+ mStatusBarWindowView = statusBarWindowView;
+ mGroupManager = groupManager;
+ mBar = bar;
+ mVisualStabilityManager = visualStabilityManager;
+
+ Resources resources = mContext.getResources();
+ mStatusBarHeight = resources.getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_height);
+
+ addListener(new OnHeadsUpChangedListener() {
+ @Override
+ public void onHeadsUpPinnedModeChanged(boolean hasPinnedNotification) {
+ if (DEBUG) Log.w(TAG, "onHeadsUpPinnedModeChanged");
+ updateTouchableRegionListener();
+ }
+ });
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Public methods:
+
+ /**
+ * Decides whether a click is invalid for a notification, i.e it has not been shown long enough
+ * that a user might have consciously clicked on it.
+ *
+ * @param key the key of the touched notification
+ * @return whether the touch is invalid and should be discarded
+ */
+ public boolean shouldSwallowClick(@NonNull String key) {
+ HeadsUpManager.HeadsUpEntry entry = getHeadsUpEntry(key);
+ return entry != null && mClock.currentTimeMillis() < entry.postTime;
+ }
+
+ public void onExpandingFinished() {
+ if (mReleaseOnExpandFinish) {
+ releaseAllImmediately();
+ mReleaseOnExpandFinish = false;
+ } else {
+ for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) {
+ if (isHeadsUp(entry.key)) {
+ // Maybe the heads-up was removed already
+ removeHeadsUpEntry(entry);
+ }
+ }
+ }
+ mEntriesToRemoveAfterExpand.clear();
+ }
+
+ /**
+ * Sets the tracking-heads-up flag. If the flag is true, HeadsUpManager doesn't remove the entry
+ * from the list even after a Heads Up Notification is gone.
+ */
+ public void setTrackingHeadsUp(boolean trackingHeadsUp) {
+ mTrackingHeadsUp = trackingHeadsUp;
+ }
+
+ /**
+ * Notify that the status bar panel gets expanded or collapsed.
+ *
+ * @param isExpanded True to notify expanded, false to notify collapsed.
+ */
+ public void setIsPanelExpanded(boolean isExpanded) {
+ if (isExpanded != mIsExpanded) {
+ mIsExpanded = isExpanded;
+ if (isExpanded) {
+ // make sure our state is sane
+ mWaitingOnCollapseWhenGoingAway = false;
+ mHeadsUpGoingAway = false;
+ updateTouchableRegionListener();
+ }
+ }
+ }
+
+ /**
+ * Set the current state of the statusbar.
+ */
+ public void setStatusBarState(int statusBarState) {
+ mStatusBarState = statusBarState;
+ }
+
+ /**
+ * Set that we are exiting the headsUp pinned mode, but some notifications might still be
+ * animating out. This is used to keep the touchable regions in a sane state.
+ */
+ public void setHeadsUpGoingAway(boolean headsUpGoingAway) {
+ if (headsUpGoingAway != mHeadsUpGoingAway) {
+ mHeadsUpGoingAway = headsUpGoingAway;
+ if (!headsUpGoingAway) {
+ waitForStatusBarLayout();
+ }
+ updateTouchableRegionListener();
+ }
+ }
+
+ /**
+ * Notifies that a remote input textbox in notification gets active or inactive.
+ * @param entry The entry of the target notification.
+ * @param remoteInputActive True to notify active, False to notify inactive.
+ */
+ public void setRemoteInputActive(
+ @NonNull NotificationData.Entry entry, boolean remoteInputActive) {
+ HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(entry.key);
+ if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) {
+ headsUpEntry.remoteInputActive = remoteInputActive;
+ if (remoteInputActive) {
+ headsUpEntry.removeAutoRemovalCallbacks();
+ } else {
+ headsUpEntry.updateEntry(false /* updatePostTime */);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ public void removeMinimumDisplayTimeForTesting() {
+ mMinimumDisplayTime = 0;
+ mHeadsUpNotificationDecay = 0;
+ mTouchAcceptanceDelay = 0;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // HeadsUpManager public methods overrides:
+
+ @Override
+ public boolean isTrackingHeadsUp() {
+ return mTrackingHeadsUp;
+ }
+
+ @Override
+ public void snooze() {
+ super.snooze();
+ mReleaseOnExpandFinish = true;
+ }
+
+ /**
+ * React to the removal of the notification in the heads up.
+ *
+ * @return true if the notification was removed and false if it still needs to be kept around
+ * for a bit since it wasn't shown long enough
+ */
+ @Override
+ public boolean removeNotification(@NonNull String key, boolean ignoreEarliestRemovalTime) {
+ if (wasShownLongEnough(key) || ignoreEarliestRemovalTime) {
+ return super.removeNotification(key, ignoreEarliestRemovalTime);
+ } else {
+ HeadsUpEntryPhone entry = getHeadsUpEntryPhone(key);
+ entry.removeAsSoonAsPossible();
+ return false;
+ }
+ }
+
+ public void addSwipedOutNotification(@NonNull String key) {
+ mSwipedOutKeys.add(key);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Dumpable overrides:
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("HeadsUpManagerPhone state:");
+ dumpInternal(fd, pw, args);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // ViewTreeObserver.OnComputeInternalInsetsListener overrides:
+
+ /**
+ * Overridden from TreeObserver.
+ */
+ @Override
+ public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
+ if (mIsExpanded || mBar.isBouncerShowing()) {
+ // The touchable region is always the full area when expanded
+ return;
+ }
+ if (hasPinnedHeadsUp()) {
+ ExpandableNotificationRow topEntry = getTopEntry().row;
+ if (topEntry.isChildInGroup()) {
+ final ExpandableNotificationRow groupSummary
+ = mGroupManager.getGroupSummary(topEntry.getStatusBarNotification());
+ if (groupSummary != null) {
+ topEntry = groupSummary;
+ }
+ }
+ topEntry.getLocationOnScreen(mTmpTwoArray);
+ int minX = mTmpTwoArray[0];
+ int maxX = mTmpTwoArray[0] + topEntry.getWidth();
+ int maxY = topEntry.getIntrinsicHeight();
+
+ info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+ info.touchableRegion.set(minX, 0, maxX, maxY);
+ } else if (mHeadsUpGoingAway || mWaitingOnCollapseWhenGoingAway) {
+ info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+ info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight);
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // VisualStabilityManager.Callback overrides:
+
+ @Override
+ public void onReorderingAllowed() {
+ mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(false);
+ for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) {
+ if (isHeadsUp(entry.key)) {
+ // Maybe the heads-up was removed already
+ removeHeadsUpEntry(entry);
+ }
+ }
+ mEntriesToRemoveWhenReorderingAllowed.clear();
+ mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(true);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // HeadsUpManager utility (protected) methods overrides:
+
+ @Override
+ protected HeadsUpEntry createHeadsUpEntry() {
+ return mEntryPool.acquire();
+ }
+
+ @Override
+ protected void releaseHeadsUpEntry(HeadsUpEntry entry) {
+ entry.reset();
+ mEntryPool.release((HeadsUpEntryPhone) entry);
+ }
+
+ @Override
+ protected boolean shouldHeadsUpBecomePinned(NotificationData.Entry entry) {
+ return mStatusBarState != StatusBarState.KEYGUARD && !mIsExpanded
+ || super.shouldHeadsUpBecomePinned(entry);
+ }
+
+ @Override
+ protected void dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args) {
+ super.dumpInternal(fd, pw, args);
+ pw.print(" mStatusBarState="); pw.println(mStatusBarState);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Private utility methods:
+
+ @Nullable
+ private HeadsUpEntryPhone getHeadsUpEntryPhone(@NonNull String key) {
+ return (HeadsUpEntryPhone) getHeadsUpEntry(key);
+ }
+
+ @Nullable
+ private HeadsUpEntryPhone getTopHeadsUpEntryPhone() {
+ return (HeadsUpEntryPhone) getTopHeadsUpEntry();
+ }
+
+ private boolean wasShownLongEnough(@NonNull String key) {
+ if (mSwipedOutKeys.contains(key)) {
+ // We always instantly dismiss views being manually swiped out.
+ mSwipedOutKeys.remove(key);
+ return true;
+ }
+
+ HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(key);
+ HeadsUpEntryPhone topEntry = getTopHeadsUpEntryPhone();
+ return headsUpEntry != topEntry || headsUpEntry.wasShownLongEnough();
+ }
+
+ /**
+ * We need to wait on the whole panel to collapse, before we can remove the touchable region
+ * listener.
+ */
+ private void waitForStatusBarLayout() {
+ mWaitingOnCollapseWhenGoingAway = true;
+ mStatusBarWindowView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft,
+ int oldTop, int oldRight, int oldBottom) {
+ if (mStatusBarWindowView.getHeight() <= mStatusBarHeight) {
+ mStatusBarWindowView.removeOnLayoutChangeListener(this);
+ mWaitingOnCollapseWhenGoingAway = false;
+ updateTouchableRegionListener();
+ }
+ }
+ });
+ }
+
+ private void updateTouchableRegionListener() {
+ boolean shouldObserve = hasPinnedHeadsUp() || mHeadsUpGoingAway
+ || mWaitingOnCollapseWhenGoingAway;
+ if (shouldObserve == mIsObserving) {
+ return;
+ }
+ if (shouldObserve) {
+ mStatusBarWindowView.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+ mStatusBarWindowView.requestLayout();
+ } else {
+ mStatusBarWindowView.getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+ }
+ mIsObserving = shouldObserve;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // HeadsUpEntryPhone:
+
+ protected class HeadsUpEntryPhone extends HeadsUpManager.HeadsUpEntry {
+ public void setEntry(@NonNull final NotificationData.Entry entry) {
+ Runnable removeHeadsUpRunnable = () -> {
+ if (!mVisualStabilityManager.isReorderingAllowed()) {
+ mEntriesToRemoveWhenReorderingAllowed.add(entry);
+ mVisualStabilityManager.addReorderingAllowedCallback(
+ HeadsUpManagerPhone.this);
+ } else if (!mTrackingHeadsUp) {
+ removeHeadsUpEntry(entry);
+ } else {
+ mEntriesToRemoveAfterExpand.add(entry);
+ }
+ };
+
+ super.setEntry(entry, removeHeadsUpRunnable);
+ }
+
+ public boolean wasShownLongEnough() {
+ return earliestRemovaltime < mClock.currentTimeMillis();
+ }
+
+ @Override
+ public void updateEntry(boolean updatePostTime) {
+ super.updateEntry(updatePostTime);
+
+ if (mEntriesToRemoveAfterExpand.contains(entry)) {
+ mEntriesToRemoveAfterExpand.remove(entry);
+ }
+ if (mEntriesToRemoveWhenReorderingAllowed.contains(entry)) {
+ mEntriesToRemoveWhenReorderingAllowed.remove(entry);
+ }
+ }
+
+ @Override
+ public void expanded(boolean expanded) {
+ if (this.expanded == expanded) {
+ return;
+ }
+
+ this.expanded = expanded;
+ if (expanded) {
+ removeAutoRemovalCallbacks();
+ } else {
+ updateEntry(false /* updatePostTime */);
+ }
+ }
+ }
+}
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 c85571c1895d..2bfdefe39017 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -23,7 +23,7 @@ import android.view.ViewConfiguration;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
/**
@@ -31,7 +31,7 @@ import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
*/
public class HeadsUpTouchHelper implements Gefingerpoken {
- private HeadsUpManager mHeadsUpManager;
+ private HeadsUpManagerPhone mHeadsUpManager;
private NotificationStackScrollLayout mStackScroller;
private int mTrackingPointer;
private float mTouchSlop;
@@ -43,7 +43,7 @@ public class HeadsUpTouchHelper implements Gefingerpoken {
private NotificationPanelView mPanel;
private ExpandableNotificationRow mPickedChild;
- public HeadsUpTouchHelper(HeadsUpManager headsUpManager,
+ public HeadsUpTouchHelper(HeadsUpManagerPhone headsUpManager,
NotificationStackScrollLayout stackScroller,
NotificationPanelView notificationPanelView) {
mHeadsUpManager = headsUpManager;
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 1239a9ea0240..79c605e4ed23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -19,6 +19,7 @@ import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.app.StatusBarManager.windowStateToString;
+import static com.android.systemui.shared.system.NavigationBarCompat.InteractionType;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
import static com.android.systemui.statusbar.phone.StatusBar.DEBUG_WINDOW_STATE;
import static com.android.systemui.statusbar.phone.StatusBar.dumpBarTransitions;
@@ -71,7 +72,6 @@ import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
-import android.widget.Button;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -114,8 +114,6 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
/** Allow some time inbetween the long press for back and recents. */
private static final int LOCK_TO_APP_GESTURE_TOLERENCE = 200;
- private static final int BUTTON_FADE_IN_OUT_DURATION_MS = 100;
-
protected NavigationBarView mNavigationBarView = null;
protected AssistManager mAssistManager;
@@ -152,7 +150,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
private RotationLockController mRotationLockController;
private TaskStackListenerImpl mTaskStackListener;
- private final Runnable mRemoveRotationProposal = () -> setRotateSuggestionButtonState(false);
+ private final Runnable mRemoveRotationProposal = () -> safeSetRotationButtonState(false);
private Animator mRotateShowAnimator;
private Animator mRotateHideAnimator;
@@ -167,6 +165,11 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
public void onRecentsAnimationStarted() {
mNavigationBarView.setRecentsAnimationStarted(true);
}
+
+ @Override
+ public void onInteractionFlagsChanged(@InteractionType int flags) {
+ mNavigationBarView.updateStates();
+ }
};
// ----- Fragment Lifecycle Callbacks -----
@@ -361,22 +364,32 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
// rotate button if shown.
if (!isValid) {
- setRotateSuggestionButtonState(false);
+ safeSetRotationButtonState(false);
return;
}
if (rotation == mWindowManager.getDefaultDisplay().getRotation()) {
// Use this as a signal to remove any current suggestions
getView().getHandler().removeCallbacks(mRemoveRotationProposal);
- setRotateSuggestionButtonState(false);
+ safeSetRotationButtonState(false);
} else {
mLastRotationSuggestion = rotation; // Remember rotation for click
- setRotateSuggestionButtonState(true);
+ safeSetRotationButtonState(true);
rescheduleRotationTimeout(false);
mMetricsLogger.visible(MetricsEvent.ROTATION_SUGGESTION_SHOWN);
}
}
+ private void safeSetRotationButtonState(boolean vis) {
+ if (mNavigationBarView != null) mNavigationBarView.setRotateSuggestionButtonState(vis);
+ }
+
+ private void safeSetRotationButtonState(boolean vis, boolean force) {
+ if (mNavigationBarView != null) {
+ mNavigationBarView.setRotateSuggestionButtonState(vis, force);
+ }
+ }
+
private void rescheduleRotationTimeout(final boolean reasonHover) {
// May be called due to a new rotation proposal or a change in hover state
if (reasonHover) {
@@ -402,84 +415,6 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
return 6000;
}
- public void setRotateSuggestionButtonState(final boolean visible) {
- setRotateSuggestionButtonState(visible, false);
- }
-
- public void setRotateSuggestionButtonState(final boolean visible, final boolean skipAnim) {
- ButtonDispatcher rotBtn = mNavigationBarView.getRotateSuggestionButton();
- final boolean currentlyVisible = rotBtn.getVisibility() == View.VISIBLE;
-
- // Rerun a show animation to indicate change but don't rerun a hide animation
- if (!visible && !currentlyVisible) return;
-
- View currentView = rotBtn.getCurrentView();
- if (currentView == null) return;
-
- KeyButtonDrawable kbd = rotBtn.getImageDrawable();
- if (kbd == null) return;
-
- AnimatedVectorDrawable animIcon = null;
- if (kbd.getDrawable(0) instanceof AnimatedVectorDrawable) {
- animIcon = (AnimatedVectorDrawable) kbd.getDrawable(0);
- }
-
- if (visible) { // Appear and change
- rotBtn.setVisibility(View.VISIBLE);
- mNavigationBarView.notifySubtreeAccessibilityStateChangedIfNeeded();
-
- if (skipAnim) {
- currentView.setAlpha(1f);
- return;
- }
-
- // Start a new animation if running
- if (mRotateShowAnimator != null) mRotateShowAnimator.pause();
- if (mRotateHideAnimator != null) mRotateHideAnimator.pause();
-
- ObjectAnimator appearFade = ObjectAnimator.ofFloat(currentView, "alpha",
- 0f, 1f);
- appearFade.setDuration(BUTTON_FADE_IN_OUT_DURATION_MS);
- appearFade.setInterpolator(Interpolators.LINEAR);
- mRotateShowAnimator = appearFade;
- appearFade.start();
-
- // Run the rotate icon's animation if it has one
- if (animIcon != null) {
- animIcon.reset();
- animIcon.start();
- }
-
- } else { // Hide
-
- if (skipAnim) {
- rotBtn.setVisibility(View.INVISIBLE);
- mNavigationBarView.notifySubtreeAccessibilityStateChangedIfNeeded();
- return;
- }
-
- // Don't start any new hide animations if one is running
- if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return;
- // Pause any active show animations but don't reset the AVD to avoid jumps
- if (mRotateShowAnimator != null) mRotateShowAnimator.pause();
-
- ObjectAnimator fadeOut = ObjectAnimator.ofFloat(currentView, "alpha",
- 0f);
- fadeOut.setDuration(BUTTON_FADE_IN_OUT_DURATION_MS);
- fadeOut.setInterpolator(Interpolators.LINEAR);
- fadeOut.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- rotBtn.setVisibility(View.INVISIBLE);
- mNavigationBarView.notifySubtreeAccessibilityStateChangedIfNeeded();
- }
- });
-
- mRotateHideAnimator = fadeOut;
- fadeOut.start();
- }
- }
-
// Injected from StatusBar at creation.
public void setCurrentSysuiVisibility(int systemUiVisibility) {
mSystemUiVisibility = systemUiVisibility;
@@ -892,7 +827,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
if (shouldOverrideUserLockPrefs(rotation)) {
mRotationLockController.setRotationLockedAtAngle(true, rotation);
}
- setRotateSuggestionButtonState(false, true);
+ safeSetRotationButtonState(false, true);
}
if (mNavigationBarView != null
@@ -928,22 +863,22 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
@Override
public void onTaskStackChanged() {
- setRotateSuggestionButtonState(false);
+ safeSetRotationButtonState(false);
}
@Override
public void onTaskRemoved(int taskId) {
- setRotateSuggestionButtonState(false);
+ safeSetRotationButtonState(false);
}
@Override
public void onTaskMovedToFront(int taskId) {
- setRotateSuggestionButtonState(false);
+ safeSetRotationButtonState(false);
}
@Override
public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) {
- setRotateSuggestionButtonState(false);
+ safeSetRotationButtonState(false);
}
}
@@ -960,6 +895,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
PixelFormat.TRANSLUCENT);
lp.token = new Binder();
lp.setTitle("NavigationBar");
+ lp.accessibilityTitle = context.getString(R.string.nav_bar);
lp.windowAnimations = 0;
View navigationBarView = LayoutInflater.from(context).inflate(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
index 0d36efda9ece..4454ef9f411c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -16,6 +16,12 @@
package com.android.systemui.statusbar.phone;
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_TOP;
+import static com.android.systemui.OverviewProxyService.DEBUG_OVERVIEW_PROXY;
+import static com.android.systemui.OverviewProxyService.TAG_OPS;
+
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
@@ -27,26 +33,19 @@ import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
-
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
import com.android.systemui.Dependency;
import com.android.systemui.OverviewProxyService;
+import com.android.systemui.OverviewProxyService.OverviewProxyListener;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
import com.android.systemui.shared.recents.IOverviewProxy;
-import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.tuner.TunerService;
-import static android.view.WindowManager.DOCKED_INVALID;
-import static android.view.WindowManager.DOCKED_LEFT;
-import static android.view.WindowManager.DOCKED_TOP;
-import static com.android.systemui.OverviewProxyService.DEBUG_OVERVIEW_PROXY;
-import static com.android.systemui.OverviewProxyService.TAG_OPS;
-
/**
* Class to detect gestures on the navigation bar.
*/
@@ -84,8 +83,16 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture
private int mTouchDownY;
private boolean mDownOnRecents;
private VelocityTracker mVelocityTracker;
- private OverviewProxyService mOverviewEventSender = Dependency.get(OverviewProxyService.class);
+ private OverviewProxyService mOverviewProxyService = Dependency.get(OverviewProxyService.class);
+ private final OverviewProxyListener mOverviewProxyListener = new OverviewProxyListener() {
+ @Override
+ public void onRecentsAnimationStarted() {
+ mRecentsAnimationStarted = true;
+ mQuickScrubController.cancelQuickSwitch();
+ }
+ };
+ private boolean mRecentsAnimationStarted;
private boolean mDockWindowEnabled;
private boolean mDockWindowTouchSlopExceeded;
private int mDragMode;
@@ -97,10 +104,12 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture
mScrollTouchSlop = r.getDimensionPixelSize(R.dimen.navigation_bar_min_swipe_distance);
mQuickScrubController = new QuickScrubController(context);
Dependency.get(TunerService.class).addTunable(this, KEY_DOCK_WINDOW_GESTURE);
+ mOverviewProxyService.addCallback(mOverviewProxyListener);
}
public void destroy() {
Dependency.get(TunerService.class).removeTunable(this);
+ mOverviewProxyService.removeCallback(mOverviewProxyListener);
}
public void setComponents(RecentsComponent recentsComponent, Divider divider,
@@ -117,11 +126,14 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture
}
private boolean proxyMotionEvents(MotionEvent event) {
- final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy();
- if (overviewProxy != null) {
+ final IOverviewProxy overviewProxy = mOverviewProxyService.getProxy();
+ if (overviewProxy != null && mNavigationBarView.isQuickStepSwipeUpEnabled()) {
mNavigationBarView.requestUnbufferedDispatch(event);
event.transform(mTransformGlobalMatrix);
try {
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ overviewProxy.onPreMotionEvent(mNavigationBarView.getDownHitTarget());
+ }
overviewProxy.onMotionEvent(event);
if (DEBUG_OVERVIEW_PROXY) {
Log.d(TAG_OPS, "Send MotionEvent: " + event.toString());
@@ -137,8 +149,12 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture
}
public boolean onInterceptTouchEvent(MotionEvent event) {
- int action = event.getAction();
- switch (action & MotionEvent.ACTION_MASK) {
+ if (mNavigationBarView.inScreenPinning()) {
+ return false;
+ }
+
+ int action = event.getActionMasked();
+ switch (action) {
case MotionEvent.ACTION_DOWN: {
mTouchDownX = (int) event.getX();
mTouchDownY = (int) event.getY();
@@ -146,19 +162,45 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture
mTransformLocalMatrix.set(Matrix.IDENTITY_MATRIX);
mNavigationBarView.transformMatrixToGlobal(mTransformGlobalMatrix);
mNavigationBarView.transformMatrixToLocal(mTransformLocalMatrix);
+ mRecentsAnimationStarted = false;
break;
}
}
- if (mStatusBar.isPresenterFullyCollapsed()
- && !mQuickScrubController.onInterceptTouchEvent(event)) {
+ boolean handledByQuickscrub = mQuickScrubController.onInterceptTouchEvent(event);
+ if (mStatusBar.isPresenterFullyCollapsed() && !handledByQuickscrub) {
+ // Proxy motion events until we start intercepting for quickscrub
proxyMotionEvents(event);
+ }
+
+ boolean result = handledByQuickscrub;
+ result |= mRecentsAnimationStarted;
+ if (mDockWindowEnabled) {
+ result |= interceptDockWindowEvent(event);
+ }
+ return result;
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ if (mNavigationBarView.inScreenPinning()) {
return false;
}
- return (mDockWindowEnabled && interceptDockWindowEvent(event));
+
+ // The same down event was just sent on intercept and therefore can be ignored here
+ boolean ignoreProxyDownEvent = event.getAction() == MotionEvent.ACTION_DOWN
+ && mOverviewProxyService.getProxy() != null;
+ boolean result = mStatusBar.isPresenterFullyCollapsed()
+ && (mQuickScrubController.onTouchEvent(event)
+ || ignoreProxyDownEvent
+ || proxyMotionEvents(event));
+ result |= mRecentsAnimationStarted;
+ if (mDockWindowEnabled) {
+ result |= handleDockWindowEvent(event);
+ }
+ return result;
}
public void onDraw(Canvas canvas) {
- if (mOverviewEventSender.getProxy() != null) {
+ if (mNavigationBarView.isQuickScrubEnabled()) {
mQuickScrubController.onDraw(canvas);
}
}
@@ -307,20 +349,6 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture
return DRAG_MODE_RECENTS;
}
- public boolean onTouchEvent(MotionEvent event) {
- // The same down event was just sent on intercept and therefore can be ignored here
- boolean ignoreProxyDownEvent = event.getAction() == MotionEvent.ACTION_DOWN
- && mOverviewEventSender.getProxy() != null;
- boolean result = mStatusBar.isPresenterFullyCollapsed()
- && (mQuickScrubController.onTouchEvent(event)
- || ignoreProxyDownEvent
- || proxyMotionEvents(event));
- if (mDockWindowEnabled) {
- result |= handleDockWindowEvent(event);
- }
- return result;
- }
-
@Override
public void onTuningChanged(String key, String newValue) {
switch (key) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index 9d20e4e1bb87..989423530599 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -57,13 +57,12 @@ public class NavigationBarInflaterView extends FrameLayout
public static final String NAV_BAR_LEFT = "sysui_nav_bar_left";
public static final String NAV_BAR_RIGHT = "sysui_nav_bar_right";
- public static final String MENU_IME = "menu_ime";
+ public static final String MENU_IME_ROTATE = "menu_ime";
public static final String BACK = "back";
public static final String HOME = "home";
public static final String RECENT = "recent";
public static final String NAVSPACE = "space";
public static final String CLIPBOARD = "clipboard";
- public static final String ROTATE = "rotate";
public static final String KEY = "key";
public static final String LEFT = "left";
public static final String RIGHT = "right";
@@ -317,10 +316,10 @@ public class NavigationBarInflaterView extends FrameLayout
View v = null;
String button = extractButton(buttonSpec);
if (LEFT.equals(button)) {
- String s = Dependency.get(TunerService.class).getValue(NAV_BAR_LEFT, ROTATE);
+ String s = Dependency.get(TunerService.class).getValue(NAV_BAR_LEFT, NAVSPACE);
button = extractButton(s);
} else if (RIGHT.equals(button)) {
- String s = Dependency.get(TunerService.class).getValue(NAV_BAR_RIGHT, MENU_IME);
+ String s = Dependency.get(TunerService.class).getValue(NAV_BAR_RIGHT, MENU_IME_ROTATE);
button = extractButton(s);
}
// Let plugins go first so they can override a standard view if they want.
@@ -334,14 +333,12 @@ public class NavigationBarInflaterView extends FrameLayout
v = inflater.inflate(R.layout.back, parent, false);
} else if (RECENT.equals(button)) {
v = inflater.inflate(R.layout.recent_apps, parent, false);
- } else if (MENU_IME.equals(button)) {
+ } else if (MENU_IME_ROTATE.equals(button)) {
v = inflater.inflate(R.layout.menu_ime, parent, false);
} else if (NAVSPACE.equals(button)) {
v = inflater.inflate(R.layout.nav_key_space, parent, false);
} else if (CLIPBOARD.equals(button)) {
v = inflater.inflate(R.layout.clipboard, parent, false);
- } else if (ROTATE.equals(button)) {
- v = inflater.inflate(R.layout.rotate_suggestion, parent, false);
} else if (button.startsWith(KEY)) {
String uri = extractImage(button);
int code = extractKeycode(button);
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 c37dd55cc6ff..285980b39020 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -16,6 +16,13 @@
package com.android.systemui.statusbar.phone;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_BACK;
+import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_HOME;
+import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.LayoutTransition;
import android.animation.LayoutTransition.TransitionListener;
import android.animation.ObjectAnimator;
@@ -29,9 +36,11 @@ import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.drawable.AnimatedVectorDrawable;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.support.annotation.ColorInt;
import android.util.AttributeSet;
import android.util.Log;
@@ -49,15 +58,17 @@ import android.widget.FrameLayout;
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
import com.android.systemui.DockedStackExistsListener;
+import com.android.systemui.Interpolators;
import com.android.systemui.OverviewProxyService;
-import com.android.systemui.OverviewProxyService.OverviewProxyListener;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.statusbar.phone.NavGesture;
import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
-import com.android.systemui.recents.SwipeUpOnboarding;
+import com.android.systemui.recents.RecentsOnboarding;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.NavigationBarCompat;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.policy.DeadZone;
import com.android.systemui.statusbar.policy.KeyButtonDrawable;
@@ -67,10 +78,17 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.function.Consumer;
+import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_QUICK_SCRUB;
+import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_SWIPE_UP;
+import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_HIDE_BACK_BUTTON;
+import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON;
+
public class NavigationBarView extends FrameLayout implements PluginListener<NavGesture> {
final static boolean DEBUG = false;
final static String TAG = "StatusBar/NavBarView";
+ final static int BUTTON_FADE_IN_OUT_DURATION_MS = 100;
+
// slippery nav bar when everything is disabled, e.g. during setup
final static boolean SLIPPERY_WHEN_DISABLED = true;
@@ -86,9 +104,15 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
boolean mShowMenu;
boolean mShowAccessibilityButton;
boolean mLongClickableAccessibilityButton;
+ boolean mShowRotateButton;
int mDisabledFlags = 0;
int mNavigationIconHints = 0;
+ private @NavigationBarCompat.HitTarget int mDownHitTarget = HIT_TARGET_NONE;
+ private Rect mHomeButtonBounds = new Rect();
+ private Rect mBackButtonBounds = new Rect();
+ private int[] mTmpPosition = new int[2];
+
private KeyButtonDrawable mBackIcon, mBackLandIcon, mBackAltIcon, mBackAltLandIcon;
private KeyButtonDrawable mBackCarModeIcon, mBackLandCarModeIcon;
private KeyButtonDrawable mBackAltCarModeIcon, mBackAltLandCarModeIcon;
@@ -104,7 +128,6 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
private DeadZone mDeadZone;
private final NavigationBarTransitions mBarTransitions;
private final OverviewProxyService mOverviewProxyService;
- private boolean mRecentsAnimationStarted;
// workaround for LayoutTransitions leaving the nav buttons in a weird state (bug 5549288)
final static boolean WORKAROUND_INVALID_LAYOUT = true;
@@ -126,9 +149,11 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
private NavigationBarInflaterView mNavigationInflaterView;
private RecentsComponent mRecentsComponent;
private Divider mDivider;
- private SwipeUpOnboarding mSwipeUpOnboarding;
+ private RecentsOnboarding mRecentsOnboarding;
private NotificationPanelView mPanelView;
+ private Animator mRotateHideAnimator;
+
private class NavTransitionListener implements TransitionListener {
private boolean mBackTransitioning;
private boolean mHomeAppearing;
@@ -219,6 +244,9 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
mShowAccessibilityButton = false;
mLongClickableAccessibilityButton = false;
+ mOverviewProxyService = Dependency.get(OverviewProxyService.class);
+ mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService);
+
mConfiguration = new Configuration();
mConfiguration.updateFrom(context.getResources().getConfiguration());
updateIcons(context, Configuration.EMPTY, mConfiguration);
@@ -234,9 +262,6 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
new ButtonDispatcher(R.id.accessibility_button));
mButtonDispatchers.put(R.id.rotate_suggestion,
new ButtonDispatcher(R.id.rotate_suggestion));
-
- mOverviewProxyService = Dependency.get(OverviewProxyService.class);
- mSwipeUpOnboarding = new SwipeUpOnboarding(context);
}
public BarTransitions getBarTransitions() {
@@ -264,9 +289,8 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
}
public void setRecentsAnimationStarted(boolean started) {
- mRecentsAnimationStarted = started;
- if (mSwipeUpOnboarding != null) {
- mSwipeUpOnboarding.onRecentsAnimationStarted();
+ if (mRecentsOnboarding != null) {
+ mRecentsOnboarding.onRecentsAnimationStarted();
}
}
@@ -277,30 +301,32 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
}
@Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ switch (event.getActionMasked()) {
+ case ACTION_DOWN:
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+ mDownHitTarget = HIT_TARGET_NONE;
+ if (mBackButtonBounds.contains(x, y)) {
+ mDownHitTarget = HIT_TARGET_BACK;
+ } else if (mHomeButtonBounds.contains(x, y)) {
+ mDownHitTarget = HIT_TARGET_HOME;
+ }
+ break;
+ }
+ return mGestureHelper.onInterceptTouchEvent(event);
+ }
+
+ @Override
public boolean onTouchEvent(MotionEvent event) {
if (mGestureHelper.onTouchEvent(event)) {
return true;
}
- return mRecentsAnimationStarted || super.onTouchEvent(event);
+ return super.onTouchEvent(event);
}
- @Override
- public boolean onInterceptTouchEvent(MotionEvent event) {
- int action = event.getActionMasked();
- if (action == MotionEvent.ACTION_DOWN) {
- mRecentsAnimationStarted = false;
- } else if (action == MotionEvent.ACTION_UP) {
- // If the overview proxy service has not started the recents animation then clean up
- // after it to ensure that the nav bar buttons still work
- if (mOverviewProxyService.getProxy() != null && !mRecentsAnimationStarted) {
- try {
- ActivityManager.getService().cancelRecentsAnimation();
- } catch (RemoteException e) {
- Log.e(TAG, "Could not cancel recents animation");
- }
- }
- }
- return mGestureHelper.onInterceptTouchEvent(event) || mRecentsAnimationStarted;
+ public @NavigationBarCompat.HitTarget int getDownHitTarget() {
+ return mDownHitTarget;
}
public void abortCurrentGesture() {
@@ -353,6 +379,19 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
return getRecentsButton().getVisibility() == View.VISIBLE;
}
+ public boolean isQuickStepSwipeUpEnabled() {
+ return mOverviewProxyService.getProxy() != null
+ && ((mOverviewProxyService.getInteractionFlags()
+ & FLAG_DISABLE_SWIPE_UP) == 0);
+ }
+
+ public boolean isQuickScrubEnabled() {
+ return SystemProperties.getBoolean("persist.quickstep.scrub.enabled", true)
+ && mOverviewProxyService.getProxy() != null && !isRecentsButtonVisible()
+ && ((mOverviewProxyService.getInteractionFlags()
+ & FLAG_DISABLE_QUICK_SCRUB) == 0);
+ }
+
private void updateCarModeIcons(Context ctx) {
mBackCarModeIcon = getDrawable(ctx,
R.drawable.ic_sysbar_back_carmode, R.drawable.ic_sysbar_back_carmode);
@@ -369,17 +408,14 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
|| oldConfig.densityDpi != newConfig.densityDpi) {
mDockedIcon = getDrawable(ctx,
R.drawable.ic_sysbar_docked, R.drawable.ic_sysbar_docked_dark);
+ mHomeDefaultIcon = getHomeDrawable(ctx);
}
if (oldConfig.densityDpi != newConfig.densityDpi
|| oldConfig.getLayoutDirection() != newConfig.getLayoutDirection()) {
- mBackIcon = getDrawable(ctx, R.drawable.ic_sysbar_back, R.drawable.ic_sysbar_back_dark);
+ mBackIcon = getBackDrawable(ctx);
mBackLandIcon = mBackIcon;
- mBackAltIcon = getDrawable(ctx,
- R.drawable.ic_sysbar_back_ime, R.drawable.ic_sysbar_back_ime_dark);
+ mBackAltIcon = getBackImeDrawable(ctx);
mBackAltLandIcon = mBackAltIcon;
-
- mHomeDefaultIcon = getDrawable(ctx,
- R.drawable.ic_sysbar_home, R.drawable.ic_sysbar_home_dark);
mRecentIcon = getDrawable(ctx,
R.drawable.ic_sysbar_recent, R.drawable.ic_sysbar_recent_dark);
mMenuIcon = getDrawable(ctx, R.drawable.ic_sysbar_menu, R.drawable.ic_sysbar_menu_dark);
@@ -404,6 +440,33 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
}
}
+ public KeyButtonDrawable getBackDrawable(Context ctx) {
+ return chooseNavigationIconDrawable(ctx, R.drawable.ic_sysbar_back,
+ R.drawable.ic_sysbar_back_dark, R.drawable.ic_sysbar_back_quick_step,
+ R.drawable.ic_sysbar_back_quick_step_dark);
+ }
+
+ public KeyButtonDrawable getBackImeDrawable(Context ctx) {
+ return chooseNavigationIconDrawable(ctx, R.drawable.ic_sysbar_back_ime,
+ R.drawable.ic_sysbar_back_ime_dark, R.drawable.ic_sysbar_back_ime_quick_step,
+ R.drawable.ic_sysbar_back_ime_quick_step_dark);
+ }
+
+ public KeyButtonDrawable getHomeDrawable(Context ctx) {
+ return chooseNavigationIconDrawable(ctx, R.drawable.ic_sysbar_home,
+ R.drawable.ic_sysbar_home_dark, R.drawable.ic_sysbar_home_quick_step,
+ R.drawable.ic_sysbar_home_quick_step_dark);
+ }
+
+ private KeyButtonDrawable chooseNavigationIconDrawable(Context ctx, @DrawableRes int iconLight,
+ @DrawableRes int iconDark, @DrawableRes int quickStepIconLight,
+ @DrawableRes int quickStepIconDark) {
+ final boolean quickStepEnabled = isQuickStepSwipeUpEnabled() || isQuickScrubEnabled();
+ return quickStepEnabled
+ ? getDrawable(ctx, quickStepIconLight, quickStepIconDark)
+ : getDrawable(ctx, iconLight, iconDark);
+ }
+
private KeyButtonDrawable getDrawable(Context ctx, @DrawableRes int lightIcon,
@DrawableRes int darkIcon) {
return getDrawable(ctx, ctx, lightIcon, darkIcon);
@@ -479,22 +542,25 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
getHomeButton().setImageDrawable(mHomeDefaultIcon);
}
- // The Accessibility button always overrides the appearance of the IME switcher
+ // Update IME button visibility, a11y and rotate button always overrides the appearance
final boolean showImeButton =
- !mShowAccessibilityButton && ((hints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN)
- != 0);
+ !mShowAccessibilityButton &&
+ !mShowRotateButton &&
+ ((hints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0);
getImeSwitchButton().setVisibility(showImeButton ? View.VISIBLE : View.INVISIBLE);
getImeSwitchButton().setImageDrawable(mImeIcon);
- // Update menu button in case the IME state has changed.
+ // Update menu button, visibility logic in method
setMenuVisibility(mShowMenu, true);
getMenuButton().setImageDrawable(mMenuIcon);
+ // Update rotate button, visibility altered by a11y button logic
+ getRotateSuggestionButton().setImageDrawable(mRotateSuggestionIcon);
+
+ // Update a11y button, visibility logic in state method
setAccessibilityButtonState(mShowAccessibilityButton, mLongClickableAccessibilityButton);
getAccessibilityButton().setImageDrawable(mAccessibilityIcon);
- getRotateSuggestionButton().setImageDrawable(mRotateSuggestionIcon);
-
setDisabledFlags(mDisabledFlags, true);
mBarTransitions.reapplyDarkIntensity();
@@ -509,7 +575,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
mDisabledFlags = disabledFlags;
- final boolean disableHome = ((disabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
+ boolean disableHome = ((disabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
// Always disable recents when alternate car mode UI is active.
boolean disableRecent = mUseCarModeUi
@@ -518,15 +584,21 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0)
&& ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) == 0);
- if ((disableRecent || disableBack) && inScreenPinning()) {
- // Don't hide back and recents buttons when in screen pinning mode, as they are used for
- // exiting.
- disableBack = false;
- disableRecent = false;
- }
+ // When screen pinning, don't hide back and home when connected service or back and
+ // recents buttons when disconnected from launcher service in screen pinning mode,
+ // as they are used for exiting.
if (mOverviewProxyService.getProxy() != null) {
- // When overview is connected to the launcher service, disable the recents button
- disableRecent = true;
+ // Use interaction flags to show/hide navigation buttons but will be shown if required
+ // to exit screen pinning.
+ final int flags = mOverviewProxyService.getInteractionFlags();
+ disableRecent |= (flags & FLAG_SHOW_OVERVIEW_BUTTON) == 0;
+ if (inScreenPinning()) {
+ disableBack = disableHome = false;
+ } else {
+ disableBack |= (flags & FLAG_HIDE_BACK_BUTTON) != 0;
+ }
+ } else if (inScreenPinning()) {
+ disableBack = disableRecent = false;
}
ViewGroup navButtons = (ViewGroup) getCurrentView().findViewById(R.id.nav_buttons);
@@ -544,13 +616,8 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
}
- private boolean inScreenPinning() {
- try {
- return ActivityManager.getService().getLockTaskModeState()
- == ActivityManager.LOCK_TASK_MODE_PINNED;
- } catch (RemoteException e) {
- return false;
- }
+ public boolean inScreenPinning() {
+ return ActivityManagerWrapper.getInstance().isLockToAppActive();
}
public void setLayoutTransitionsEnabled(boolean enabled) {
@@ -604,6 +671,11 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
updateSlippery();
}
+ public void updateStates() {
+ updateSlippery();
+ setDisabledFlags(mDisabledFlags, true);
+ }
+
private void updateSlippery() {
setSlippery(mOverviewProxyService.getProxy() != null && mPanelView.isFullyExpanded());
}
@@ -638,8 +710,10 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
mShowMenu = show;
- // Only show Menu if IME switcher and Accessibility button not shown.
- final boolean shouldShow = mShowMenu && !mShowAccessibilityButton &&
+ // Only show Menu if IME switcher, rotate and Accessibility buttons are not shown.
+ final boolean shouldShow = mShowMenu &&
+ !mShowAccessibilityButton &&
+ !mShowRotateButton &&
((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) == 0);
getMenuButton().setVisibility(shouldShow ? View.VISIBLE : View.INVISIBLE);
@@ -649,15 +723,96 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
mShowAccessibilityButton = visible;
mLongClickableAccessibilityButton = longClickable;
if (visible) {
- // Accessibility button overrides Menu and IME switcher buttons.
+ // Accessibility button overrides Menu, IME switcher and rotate buttons.
setMenuVisibility(false, true);
getImeSwitchButton().setVisibility(View.INVISIBLE);
+ setRotateSuggestionButtonState(false, true);
}
getAccessibilityButton().setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
getAccessibilityButton().setLongClickable(longClickable);
}
+ public void setRotateSuggestionButtonState(final boolean visible) {
+ setRotateSuggestionButtonState(visible, false);
+ }
+
+ public void setRotateSuggestionButtonState(final boolean visible, final boolean force) {
+ ButtonDispatcher rotBtn = getRotateSuggestionButton();
+ final boolean currentlyVisible = mShowRotateButton;
+
+ // Rerun a show animation to indicate change but don't rerun a hide animation
+ if (!visible && !currentlyVisible) return;
+
+ View currentView = rotBtn.getCurrentView();
+ if (currentView == null) return;
+
+ KeyButtonDrawable kbd = rotBtn.getImageDrawable();
+ if (kbd == null) return;
+
+ AnimatedVectorDrawable animIcon = null;
+ if (kbd.getDrawable(0) instanceof AnimatedVectorDrawable) {
+ animIcon = (AnimatedVectorDrawable) kbd.getDrawable(0);
+ }
+
+ if (visible) { // Appear and change, cannot force
+ setRotateButtonVisibility(true);
+
+ // Stop any currently running hide animations
+ if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
+ mRotateHideAnimator.pause();
+ }
+
+ // Reset the alpha if any has changed due to hide animation
+ currentView.setAlpha(1f);
+
+ // Run the rotate icon's animation if it has one
+ if (animIcon != null) {
+ animIcon.reset();
+ animIcon.start();
+ }
+
+ } else { // Hide
+ if (force) {
+ // If a hide animator is running stop it and instantly make invisible
+ if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
+ mRotateHideAnimator.pause();
+ }
+ setRotateButtonVisibility(false);
+ return;
+ }
+
+ // Don't start any new hide animations if one is running
+ if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return;
+
+ ObjectAnimator fadeOut = ObjectAnimator.ofFloat(currentView, "alpha",
+ 0f);
+ fadeOut.setDuration(BUTTON_FADE_IN_OUT_DURATION_MS);
+ fadeOut.setInterpolator(Interpolators.LINEAR);
+ fadeOut.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ setRotateButtonVisibility(false);
+ }
+ });
+
+ mRotateHideAnimator = fadeOut;
+ fadeOut.start();
+ }
+ }
+
+ private void setRotateButtonVisibility(final boolean visible) {
+ // Never show if a11y is visible
+ final boolean adjVisible = visible && !mShowAccessibilityButton;
+ final int vis = adjVisible ? View.VISIBLE : View.INVISIBLE;
+
+ getRotateSuggestionButton().setVisibility(vis);
+ mShowRotateButton = visible;
+
+ // Hide/restore other button visibility, if necessary
+ setNavigationIconHints(mNavigationIconHints, true);
+ }
+
@Override
public void onFinishInflate() {
mNavigationInflaterView = (NavigationBarInflaterView) findViewById(
@@ -674,15 +829,16 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
if (mGestureHelper != null) {
mGestureHelper.onDarkIntensityChange(intensity);
}
- if (mSwipeUpOnboarding != null) {
- mSwipeUpOnboarding.setContentDarkIntensity(intensity);
+ if (mRecentsOnboarding != null) {
+ mRecentsOnboarding.setContentDarkIntensity(intensity);
}
}
public void onOverviewProxyConnectionChanged(boolean isConnected) {
- setSlippery(!isConnected);
- setDisabledFlags(mDisabledFlags, true);
- setUpSwipeUpOnboarding(isConnected);
+ updateStates();
+ setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled());
+ updateIcons(getContext(), Configuration.EMPTY, mConfiguration);
+ setNavigationIconHints(mNavigationIconHints, true);
}
@Override
@@ -694,9 +850,23 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
+ updateButtonLocationOnScreen(getBackButton(), mBackButtonBounds);
+ updateButtonLocationOnScreen(getHomeButton(), mHomeButtonBounds);
mGestureHelper.onLayout(changed, left, top, right, bottom);
}
+ private void updateButtonLocationOnScreen(ButtonDispatcher button, Rect buttonBounds) {
+ View view = button.getCurrentView();
+ if (view == null) {
+ buttonBounds.setEmpty();
+ return;
+ }
+ view.getLocationInWindow(mTmpPosition);
+ buttonBounds.set(mTmpPosition[0], mTmpPosition[1],
+ mTmpPosition[0] + view.getMeasuredWidth(),
+ mTmpPosition[1] + view.getMeasuredHeight());
+ }
+
private void updateRotatedViews() {
mRotatedViews[Surface.ROTATION_0] =
mRotatedViews[Surface.ROTATION_180] = findViewById(R.id.rot0);
@@ -751,6 +921,11 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
Log.d(TAG, "reorient(): rot=" + mCurrentRotation);
}
+ // Resolve layout direction if not resolved since components changing layout direction such
+ // as changing languages will recreate this view and the direction will be resolved later
+ if (!isLayoutDirectionResolved()) {
+ resolveLayoutDirection();
+ }
updateTaskSwitchHelper();
setNavigationIconHints(mNavigationIconHints, true);
@@ -793,7 +968,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
updateTaskSwitchHelper();
updateIcons(getContext(), mConfiguration, newConfig);
updateRecentsIcon();
- mSwipeUpOnboarding.onConfigurationChanged(newConfig);
+ mRecentsOnboarding.onConfigurationChanged(newConfig);
if (uiCarModeChanged || mConfiguration.densityDpi != newConfig.densityDpi
|| mConfiguration.getLayoutDirection() != newConfig.getLayoutDirection()) {
// If car mode or density changes, we need to reset the icons.
@@ -897,9 +1072,9 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
private void setUpSwipeUpOnboarding(boolean connectedToOverviewProxy) {
if (connectedToOverviewProxy) {
- mSwipeUpOnboarding.onConnectedToLauncher();
+ mRecentsOnboarding.onConnectedToLauncher();
} else {
- mSwipeUpOnboarding.onDisconnectedFromLauncher();
+ mRecentsOnboarding.onDisconnectedFromLauncher();
}
}
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 cd2e77ae2591..3b129fc5e08d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -45,7 +45,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
-import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
import com.android.internal.logging.MetricsLogger;
@@ -68,14 +68,12 @@ import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.stack.StackStateAnimator;
import java.util.List;
-import java.util.Collection;
public class NotificationPanelView extends PanelView implements
ExpandableView.OnHeightChangedListener,
@@ -113,6 +111,7 @@ public class NotificationPanelView extends PanelView implements
}
};
private final PowerManager mPowerManager;
+ private final AccessibilityManager mAccessibilityManager;
private KeyguardAffordanceHelper mAffordanceHelper;
private KeyguardUserSwitcher mKeyguardUserSwitcher;
@@ -251,6 +250,8 @@ public class NotificationPanelView extends PanelView implements
setWillNotDraw(!DEBUG);
mFalsingManager = FalsingManager.getInstance(context);
mPowerManager = context.getSystemService(PowerManager.class);
+ mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
+ setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
}
public void setStatusBar(StatusBar bar) {
@@ -500,7 +501,8 @@ public class NotificationPanelView extends PanelView implements
float shelfSize = shelf.getVisibility() == GONE ? 0
: shelf.getIntrinsicHeight() + notificationPadding;
float availableSpace = mNotificationStackScroller.getHeight() - minPadding - shelfSize
- - Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding);
+ - Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding)
+ - mKeyguardStatusView.getLogoutButtonHeight();
int count = 0;
for (int i = 0; i < mNotificationStackScroller.getChildCount(); i++) {
ExpandableView child = (ExpandableView) mNotificationStackScroller.getChildAt(i);
@@ -663,16 +665,6 @@ public class NotificationPanelView extends PanelView implements
}
@Override
- public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
- if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
- event.getText().add(getKeyguardOrLockScreenString());
- mLastAnnouncementWasQuickSettings = false;
- return true;
- }
- return super.dispatchPopulateAccessibilityEventInternal(event);
- }
-
- @Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (mBlockTouches || mQsFullyExpanded && mQs.onInterceptTouchEvent(event)) {
return false;
@@ -1302,10 +1294,6 @@ public class NotificationPanelView extends PanelView implements
setQsExpanded(true);
} else if (height <= mQsMinExpansionHeight && mQsExpanded) {
setQsExpanded(false);
- if (mLastAnnouncementWasQuickSettings && !mTracking && !isCollapsing()) {
- announceForAccessibility(getKeyguardOrLockScreenString());
- mLastAnnouncementWasQuickSettings = false;
- }
}
mQsExpansionHeight = height;
updateQsExpansion();
@@ -1331,13 +1319,10 @@ public class NotificationPanelView extends PanelView implements
updateClock(mClockPositionResult.clockAlpha, mClockPositionResult.clockScale);
}
- // Upon initialisation when we are not layouted yet we don't want to announce that we are
- // fully expanded, hence the != 0.0f check.
- if (height != 0.0f && mQsFullyExpanded && !mLastAnnouncementWasQuickSettings) {
- announceForAccessibility(getContext().getString(
- R.string.accessibility_desc_quick_settings));
- mLastAnnouncementWasQuickSettings = true;
+ if (mAccessibilityManager.isEnabled()) {
+ setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
}
+
if (mQsFullyExpanded && mFalsingManager.shouldEnforceBouncer()) {
mStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */);
@@ -1352,9 +1337,13 @@ public class NotificationPanelView extends PanelView implements
mQs.setQsExpansion(getQsExpansionFraction(), getHeaderTranslation());
}
- private String getKeyguardOrLockScreenString() {
+ private String determineAccessibilityPaneTitle() {
if (mQs != null && mQs.isCustomizing()) {
return getContext().getString(R.string.accessibility_desc_quick_settings_edit);
+ } else if (mQsExpansionHeight != 0.0f && mQsFullyExpanded) {
+ // Upon initialisation when we are not layouted yet we don't want to announce that we
+ // are fully expanded, hence the != 0.0f check.
+ return getContext().getString(R.string.accessibility_desc_quick_settings);
} else if (mStatusBarState == StatusBarState.KEYGUARD) {
return getContext().getString(R.string.accessibility_desc_lock_screen);
} else {
@@ -1571,7 +1560,7 @@ public class NotificationPanelView extends PanelView implements
private void updatePanelExpanded() {
boolean isExpanded = !isFullyCollapsed();
if (mPanelExpanded != isExpanded) {
- mHeadsUpManager.setIsExpanded(isExpanded);
+ mHeadsUpManager.setIsPanelExpanded(isExpanded);
mStatusBar.setPanelExpanded(isExpanded);
mPanelExpanded = isExpanded;
}
@@ -1882,6 +1871,9 @@ public class NotificationPanelView extends PanelView implements
requestScrollerTopPaddingUpdate(false /* animate */);
requestPanelHeightUpdate();
}
+ if (mAccessibilityManager.isEnabled()) {
+ setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
+ }
}
@Override
@@ -2338,7 +2330,7 @@ public class NotificationPanelView extends PanelView implements
}
@Override
- public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
super.setHeadsUpManager(headsUpManager);
mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager, mNotificationStackScroller,
this);
@@ -2630,8 +2622,8 @@ public class NotificationPanelView extends PanelView implements
}
}
- public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing) {
- mKeyguardStatusView.setPulsing(pulsing != null);
+ public void setPulsing(boolean pulsing) {
+ mKeyguardStatusView.setPulsing(pulsing);
positionClockAndNotifications();
mNotificationStackScroller.setPulsing(pulsing, mKeyguardStatusView.getLocationOnScreen()[1]
+ mKeyguardStatusView.getClockBottom());
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 b6c7d74a33d1..dbabdb2a32dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -50,8 +50,8 @@ import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
import android.util.BoostFramework;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -76,7 +76,7 @@ public abstract class PanelView extends FrameLayout {
}
protected StatusBar mStatusBar;
- protected HeadsUpManager mHeadsUpManager;
+ protected HeadsUpManagerPhone mHeadsUpManager;
private float mPeekHeight;
private float mHintDistance;
@@ -1270,7 +1270,7 @@ public abstract class PanelView extends FrameLayout {
*/
protected abstract int getClearAllHeight();
- public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
mHeadsUpManager = headsUpManager;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 6444cc816663..747a551defe6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -575,7 +575,7 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
Intent browserIntent = getTaskIntent(taskId, userId);
Notification.Builder builder = new Notification.Builder(mContext, NotificationChannels.GENERAL);
- if (browserIntent != null) {
+ if (browserIntent != null && browserIntent.isWebIntent()) {
// Make sure that this doesn't resolve back to an instant app
browserIntent.setComponent(null)
.setPackage(null)
@@ -597,8 +597,9 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
.addCategory("unique:" + System.currentTimeMillis())
.putExtra(Intent.EXTRA_PACKAGE_NAME, appInfo.packageName)
.putExtra(Intent.EXTRA_VERSION_CODE, (int) (appInfo.versionCode & 0x7fffffff))
- .putExtra(Intent.EXTRA_VERSION_CODE, appInfo.versionCode)
- .putExtra(Intent.EXTRA_EPHEMERAL_FAILURE, pendingIntent);
+ .putExtra(Intent.EXTRA_LONG_VERSION_CODE, appInfo.versionCode)
+ .putExtra(Intent.EXTRA_EPHEMERAL_FAILURE, pendingIntent)
+ .putExtra(Intent.EXTRA_INSTANT_APP_FAILURE, pendingIntent);
PendingIntent webPendingIntent = PendingIntent.getActivity(mContext, 0, goToWebIntent, 0);
Action webAction = new Notification.Action.Builder(null, mContext.getString(R.string.go_to_web),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java
index 001a1a2d7292..378858a9b816 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java
@@ -29,15 +29,12 @@ import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Handler;
import android.os.RemoteException;
-import android.os.SystemProperties;
import android.util.Log;
import android.util.Slog;
-import android.view.Display;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
-import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
@@ -53,6 +50,7 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM;
import static com.android.systemui.OverviewProxyService.DEBUG_OVERVIEW_PROXY;
import static com.android.systemui.OverviewProxyService.TAG_OPS;
+import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_HOME;
/**
* Class to detect gestures on the navigation bar and implement quick scrub and switch.
@@ -63,7 +61,7 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene
private static final String TAG = "QuickScrubController";
private static final int QUICK_SWITCH_FLING_VELOCITY = 0;
private static final int ANIM_DURATION_MS = 200;
- private static final long LONG_PRESS_DELAY_MS = 150;
+ private static final long LONG_PRESS_DELAY_MS = 225;
/**
* For quick step, set a damping value to allow the button to stick closer its origin position
@@ -76,6 +74,7 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene
private boolean mDraggingActive;
private boolean mQuickScrubActive;
+ private boolean mAllowQuickSwitch;
private float mDownOffset;
private float mTranslation;
private int mTouchDownX;
@@ -87,15 +86,14 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene
private int mLightTrackColor;
private int mDarkTrackColor;
private float mDarkIntensity;
+ private View mHomeButtonView;
private final Handler mHandler = new Handler();
private final Interpolator mQuickScrubEndInterpolator = new DecelerateInterpolator();
private final Rect mTrackRect = new Rect();
- private final Rect mHomeButtonRect = new Rect();
private final Paint mTrackPaint = new Paint();
private final int mScrollTouchSlop;
private final OverviewProxyService mOverviewEventSender;
- private final Display mDisplay;
private final int mTrackThickness;
private final int mTrackPadding;
private final ValueAnimator mTrackAnimator;
@@ -114,11 +112,10 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene
if (!mQuickScrubActive) {
pos = mDragPositive ? Math.min((int) mTranslation, pos) : Math.max((int) mTranslation, pos);
}
- final View homeView = mNavigationBarView.getHomeButton().getCurrentView();
if (mIsVertical) {
- homeView.setTranslationY(pos);
+ mHomeButtonView.setTranslationY(pos);
} else {
- homeView.setTranslationX(pos);
+ mHomeButtonView.setTranslationX(pos);
}
};
@@ -126,6 +123,7 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene
@Override
public void onAnimationEnd(Animator animation) {
mNavigationBarView.getHomeButton().setClickable(true);
+ mHomeButtonView = null;
mQuickScrubActive = false;
mTranslation = 0;
}
@@ -137,7 +135,9 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene
new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velX, float velY) {
- if (!isQuickScrubEnabled() || mQuickScrubActive) {
+ if (!mNavigationBarView.isQuickScrubEnabled() || mQuickScrubActive
+ || !mAllowQuickSwitch
+ || mNavigationBarView.getDownHitTarget() != HIT_TARGET_HOME) {
return false;
}
float velocityX = mIsRTL ? -velX : velX;
@@ -158,16 +158,15 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene
} catch (RemoteException e) {
Log.e(TAG, "Failed to send start of quick switch.", e);
}
+ return true;
}
- return true;
+ return false;
}
};
public QuickScrubController(Context context) {
mContext = context;
mScrollTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
- mDisplay = ((WindowManager) context.getSystemService(
- Context.WINDOW_SERVICE)).getDefaultDisplay();
mOverviewEventSender = Dependency.get(OverviewProxyService.class);
mGestureDetector = new GestureDetector(mContext, mGestureListener);
mTrackThickness = getDimensionPixelSize(mContext, R.dimen.nav_quick_scrub_track_thickness);
@@ -189,21 +188,49 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene
mNavigationBarView = navigationBarView;
}
+ /**
+ * @return true if we want to intercept touch events for quick scrub/switch and prevent proxying
+ * the event to the overview service.
+ */
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
- final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy();
final ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
- if (overviewProxy == null) {
+ if (!mNavigationBarView.isQuickScrubEnabled()) {
homeButton.setDelayTouchFeedback(false);
return false;
}
- mGestureDetector.onTouchEvent(event);
+
+ return handleTouchEvent(event);
+ }
+
+ /**
+ * @return true if we want to handle touch events for quick scrub/switch and prevent proxying
+ * the event to the overview service.
+ */
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ return handleTouchEvent(event);
+ }
+
+ private boolean handleTouchEvent(MotionEvent event) {
+ final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy();
+ final ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
+ if (mGestureDetector.onTouchEvent(event)) {
+ // If the fling has been handled on UP, then skip proxying the UP
+ return true;
+ }
int action = event.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
int x = (int) event.getX();
int y = (int) event.getY();
- if (isQuickScrubEnabled() && mHomeButtonRect.contains(x, y)) {
+ // End any existing quickscrub animations before starting the new transition
+ if (mQuickScrubEndAnimator != null) {
+ mQuickScrubEndAnimator.end();
+ }
+ mHomeButtonView = homeButton.getCurrentView();
+ if (mNavigationBarView.isQuickScrubEnabled()
+ && mNavigationBarView.getDownHitTarget() == HIT_TARGET_HOME) {
mTouchDownX = x;
mTouchDownY = y;
homeButton.setDelayTouchFeedback(true);
@@ -212,6 +239,7 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene
homeButton.setDelayTouchFeedback(false);
mTouchDownX = mTouchDownY = -1;
}
+ mAllowQuickSwitch = true;
break;
}
case MotionEvent.ACTION_MOVE: {
@@ -240,8 +268,9 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene
offset = pos - mTrackRect.left;
trackSize = mTrackRect.width();
}
- // Do not start scrubbing when dragging in the perpendicular direction
- if (!mDraggingActive && exceededPerpendicularTouchSlop) {
+ // Do not start scrubbing when dragging in the perpendicular direction if we
+ // haven't already started quickscrub
+ if (!mDraggingActive && !mQuickScrubActive && exceededPerpendicularTouchSlop) {
mHandler.removeCallbacksAndMessages(null);
return false;
}
@@ -268,7 +297,7 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene
: Utilities.clamp(offset - mDownOffset, 0, trackSize);
if (mQuickScrubActive) {
try {
- overviewProxy.onQuickScrubProgress(scrubFraction);
+ mOverviewEventSender.getProxy().onQuickScrubProgress(scrubFraction);
if (DEBUG_OVERVIEW_PROXY) {
Log.d(TAG_OPS, "Quick Scrub Progress:" + scrubFraction);
}
@@ -279,9 +308,9 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene
mTranslation /= SWITCH_STICKINESS;
}
if (mIsVertical) {
- homeButton.getCurrentView().setTranslationY(mTranslation);
+ mHomeButtonView.setTranslationY(mTranslation);
} else {
- homeButton.getCurrentView().setTranslationX(mTranslation);
+ mHomeButtonView.setTranslationX(mTranslation);
}
}
}
@@ -289,7 +318,7 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
- endQuickScrub();
+ endQuickScrub(true /* animate */);
break;
}
return mDraggingActive || mQuickScrubActive;
@@ -321,17 +350,6 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene
x2 = x1 + width / 2 - mTrackPadding;
}
mTrackRect.set(x1, y1, x2, y2);
-
- // Get the touch rect of the home button location
- View homeView = mNavigationBarView.getHomeButton().getCurrentView();
- if (homeView != null) {
- int[] globalHomePos = homeView.getLocationOnScreen();
- int[] globalNavBarPos = mNavigationBarView.getLocationOnScreen();
- int homeX = globalHomePos[0] - globalNavBarPos[0];
- int homeY = globalHomePos[1] - globalNavBarPos[1];
- mHomeButtonRect.set(homeX, homeY, homeX + homeView.getMeasuredWidth(),
- homeY + homeView.getMeasuredHeight());
- }
}
@Override
@@ -341,15 +359,12 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene
}
@Override
- public boolean onTouchEvent(MotionEvent event) {
- if (event.getAction() == MotionEvent.ACTION_UP) {
- endQuickScrub();
- }
- return false;
- }
-
- @Override
public void setBarState(boolean isVertical, boolean isRTL) {
+ final boolean changed = (mIsVertical != isVertical) || (mIsRTL != isRTL);
+ if (changed) {
+ // End quickscrub if the state changes mid-transition
+ endQuickScrub(false /* animate */);
+ }
mIsVertical = isVertical;
mIsRTL = isRTL;
try {
@@ -363,10 +378,6 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene
}
}
- boolean isQuickScrubEnabled() {
- return SystemProperties.getBoolean("persist.quickstep.scrub.enabled", false);
- }
-
private void startQuickScrub() {
if (!mQuickScrubActive) {
mQuickScrubActive = true;
@@ -385,7 +396,7 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene
}
}
- private void endQuickScrub() {
+ private void endQuickScrub(boolean animate) {
mHandler.removeCallbacks(mLongPressRunnable);
if (mDraggingActive || mQuickScrubActive) {
mButtonAnimator.setIntValues((int) mTranslation, 0);
@@ -399,10 +410,18 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene
} catch (RemoteException e) {
Log.e(TAG, "Failed to send end of quick scrub.", e);
}
+ if (!animate) {
+ mQuickScrubEndAnimator.end();
+ }
}
mDraggingActive = false;
}
+ public void cancelQuickSwitch() {
+ mAllowQuickSwitch = false;
+ mHandler.removeCallbacks(mLongPressRunnable);
+ }
+
private int getDimensionPixelSize(Context context, @DimenRes int resId) {
return context.getResources().getDimensionPixelSize(resId);
}
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 59c44ed6635f..2b5085389804 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -30,6 +30,7 @@ import android.os.Handler;
import android.os.Trace;
import android.util.Log;
import android.util.MathUtils;
+import android.view.Choreographer;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
@@ -111,7 +112,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
protected static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA_BUSY;
static final int TAG_KEY_ANIM = R.id.scrim;
- static final int TAG_KEY_ANIM_BLANK = R.id.scrim_blanking;
private static final int TAG_KEY_ANIM_TARGET = R.id.scrim_target;
private static final int TAG_START_ALPHA = R.id.scrim_alpha_start;
private static final int TAG_END_ALPHA = R.id.scrim_alpha_end;
@@ -166,6 +166,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
private boolean mScreenBlankingCallbackCalled;
private Callback mCallback;
private boolean mWallpaperSupportsAmbientMode;
+ private boolean mScreenOn;
+
+ // Scrim blanking callbacks
+ private Choreographer.FrameCallback mPendingFrameCallback;
+ private Runnable mBlankingTransitionRunnable;
private final WakeLock mWakeLock;
private boolean mWakeLockHeld;
@@ -248,6 +253,16 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
mCurrentInFrontAlpha = state.getFrontAlpha();
mCurrentBehindAlpha = state.getBehindAlpha();
+ // Cancel blanking transitions that were pending before we requested a new state
+ if (mPendingFrameCallback != null) {
+ Choreographer.getInstance().removeFrameCallback(mPendingFrameCallback);
+ mPendingFrameCallback = null;
+ }
+ if (getHandler().hasCallbacks(mBlankingTransitionRunnable)) {
+ getHandler().removeCallbacks(mBlankingTransitionRunnable);
+ mBlankingTransitionRunnable = null;
+ }
+
// Showing/hiding the keyguard means that scrim colors have to be switched, not necessary
// to do the same when you're just showing the brightness mirror.
mNeedsDrawableColorUpdate = state != ScrimState.BRIGHTNESS_MIRROR;
@@ -692,11 +707,12 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
}
}
- final boolean blankingInProgress = mScrimInFront.getTag(TAG_KEY_ANIM_BLANK) != null;
- if (mBlankScreen || blankingInProgress) {
- if (!blankingInProgress) {
- blankDisplay();
- }
+ if (mPendingFrameCallback != null) {
+ // Display is off and we're waiting.
+ return;
+ } else if (mBlankScreen) {
+ // Need to blank the display before continuing.
+ blankDisplay();
return;
} else if (!mScreenBlankingCallbackCalled) {
// Not blanking the screen. Letting the callback know that we're ready
@@ -750,45 +766,38 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
}
private void blankDisplay() {
- final float initialAlpha = mScrimInFront.getViewAlpha();
- final int initialTint = mScrimInFront.getTint();
- ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
- anim.addUpdateListener(animation -> {
- final float amount = (float) animation.getAnimatedValue();
- float animAlpha = MathUtils.lerp(initialAlpha, 1, amount);
- int animTint = ColorUtils.blendARGB(initialTint, Color.BLACK, amount);
- updateScrimColor(mScrimInFront, animAlpha, animTint);
- dispatchScrimsVisible();
- });
- anim.setInterpolator(getInterpolator());
- anim.setDuration(mDozeParameters.getPulseInDuration());
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mCallback != null) {
- mCallback.onDisplayBlanked();
- mScreenBlankingCallbackCalled = true;
- }
- Runnable blankingCallback = () -> {
- mScrimInFront.setTag(TAG_KEY_ANIM_BLANK, null);
- mBlankScreen = false;
- // Try again.
- updateScrims();
- };
+ updateScrimColor(mScrimInFront, 1, Color.BLACK);
- // Setting power states can happen after we push out the frame. Make sure we
- // stay fully opaque until the power state request reaches the lower levels.
- getHandler().postDelayed(blankingCallback, 100);
+ // Notify callback that the screen is completely black and we're
+ // ready to change the display power mode
+ mPendingFrameCallback = frameTimeNanos -> {
+ if (mCallback != null) {
+ mCallback.onDisplayBlanked();
+ mScreenBlankingCallbackCalled = true;
+ }
+ mBlankingTransitionRunnable = () -> {
+ mBlankingTransitionRunnable = null;
+ mPendingFrameCallback = null;
+ mBlankScreen = false;
+ // Try again.
+ updateScrims();
+ };
+
+ // Setting power states can happen after we push out the frame. Make sure we
+ // stay fully opaque until the power state request reaches the lower levels.
+ final int delay = mScreenOn ? 16 : 500;
+ if (DEBUG) {
+ Log.d(TAG, "Fading out scrims with delay: " + delay);
}
- });
- anim.start();
- mScrimInFront.setTag(TAG_KEY_ANIM_BLANK, anim);
+ getHandler().postDelayed(mBlankingTransitionRunnable, delay);
+ };
+ doOnTheNextFrame(mPendingFrameCallback);
+ }
- // Finish animation if we're already at its final state
- if (initialAlpha == 1 && mScrimInFront.getTint() == Color.BLACK) {
- anim.end();
- }
+ @VisibleForTesting
+ protected void doOnTheNextFrame(Choreographer.FrameCallback callback) {
+ Choreographer.getInstance().postFrameCallback(callback);
}
@VisibleForTesting
@@ -893,6 +902,25 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
}
}
+ /**
+ * Interrupts blanking transitions once the display notifies that it's already on.
+ */
+ public void onScreenTurnedOn() {
+ mScreenOn = true;
+ final Handler handler = getHandler();
+ if (handler.hasCallbacks(mBlankingTransitionRunnable)) {
+ if (DEBUG) {
+ Log.d(TAG, "Shorter blanking because screen turned on. All good.");
+ }
+ handler.removeCallbacks(mBlankingTransitionRunnable);
+ mBlankingTransitionRunnable.run();
+ }
+ }
+
+ public void onScreenTurnedOff() {
+ mScreenOn = false;
+ }
+
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 314d6aa2bf57..381e4af31853 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -48,7 +48,6 @@ public enum ScrimState {
// set our scrim to black in this frame to avoid flickering and
// fade it out afterwards.
mBlankScreen = true;
- updateScrimColor(mScrimInFront, 1, Color.BLACK);
}
} else {
mAnimationDuration = ScrimController.ANIMATION_DURATION;
@@ -86,9 +85,6 @@ public enum ScrimState {
AOD(3) {
@Override
public void prepare(ScrimState previousState) {
- if (previousState == ScrimState.PULSING && !mCanControlScreenOff) {
- updateScrimColor(mScrimInFront, 1, Color.BLACK);
- }
final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
final boolean wasPulsing = previousState == ScrimState.PULSING;
mBlankScreen = wasPulsing && !mCanControlScreenOff;
@@ -115,9 +111,6 @@ public enum ScrimState {
&& !mKeyguardUpdateMonitor.hasLockscreenWallpaper() ? 0f : 1f;
mCurrentBehindTint = Color.BLACK;
mBlankScreen = mDisplayRequiresBlanking;
- if (mDisplayRequiresBlanking) {
- updateScrimColor(mScrimInFront, 1, Color.BLACK);
- }
}
},
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java
deleted file mode 100644
index 15ef742af02e..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java
+++ /dev/null
@@ -1,517 +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.phone;
-
-import android.animation.ArgbEvaluator;
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.Path.Direction;
-import android.graphics.Path.FillType;
-import android.graphics.Path.Op;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.util.LayoutDirection;
-
-import com.android.settingslib.R;
-import com.android.settingslib.Utils;
-import com.android.systemui.qs.SlashDrawable;
-
-public class SignalDrawable extends Drawable {
-
- private static final String TAG = "SignalDrawable";
-
- private static final int NUM_DOTS = 3;
-
- private static final float VIEWPORT = 24f;
- private static final float PAD = 2f / VIEWPORT;
- private static final float CUT_OUT = 7.9f / VIEWPORT;
-
- private static final float DOT_SIZE = 3f / VIEWPORT;
- private static final float DOT_PADDING = 1f / VIEWPORT;
- private static final float DOT_CUT_WIDTH = (DOT_SIZE * 3) + (DOT_PADDING * 5);
- private static final float DOT_CUT_HEIGHT = (DOT_SIZE * 1) + (DOT_PADDING * 1);
-
- private static final float[] FIT = {2.26f, -3.02f, 1.76f};
-
- // All of these are masks to push all of the drawable state into one int for easy callbacks
- // and flow through sysui.
- private static final int LEVEL_MASK = 0xff;
- private static final int NUM_LEVEL_SHIFT = 8;
- private static final int NUM_LEVEL_MASK = 0xff << NUM_LEVEL_SHIFT;
- private static final int STATE_SHIFT = 16;
- private static final int STATE_MASK = 0xff << STATE_SHIFT;
- private static final int STATE_NONE = 0;
- private static final int STATE_EMPTY = 1;
- private static final int STATE_CUT = 2;
- private static final int STATE_CARRIER_CHANGE = 3;
- private static final int STATE_AIRPLANE = 4;
-
- private static final long DOT_DELAY = 1000;
-
- private static float[][] X_PATH = new float[][]{
- {21.9f / VIEWPORT, 17.0f / VIEWPORT},
- {-1.1f / VIEWPORT, -1.1f / VIEWPORT},
- {-1.9f / VIEWPORT, 1.9f / VIEWPORT},
- {-1.9f / VIEWPORT, -1.9f / VIEWPORT},
- {-1.1f / VIEWPORT, 1.1f / VIEWPORT},
- {1.9f / VIEWPORT, 1.9f / VIEWPORT},
- {-1.9f / VIEWPORT, 1.9f / VIEWPORT},
- {1.1f / VIEWPORT, 1.1f / VIEWPORT},
- {1.9f / VIEWPORT, -1.9f / VIEWPORT},
- {1.9f / VIEWPORT, 1.9f / VIEWPORT},
- {1.1f / VIEWPORT, -1.1f / VIEWPORT},
- {-1.9f / VIEWPORT, -1.9f / VIEWPORT},
- };
-
- // Rounded corners are achieved by arcing a circle of radius `R` from its tangent points along
- // the curve (curve ≡ triangle). On the top and left corners of the triangle, the tangents are
- // as follows:
- // 1) Along the straight lines (y = 0 and x = width):
- // Ps = circleOffset + R
- // 2) Along the diagonal line (y = x):
- // Pd = √((Ps^2) / 2)
- // or (remember: sin(π/4) ≈ 0.7071)
- // Pd = (circleOffset + R - 0.7071, height - R - 0.7071)
- // Where Pd is the (x,y) coords of the point that intersects the circle at the bottom
- // left of the triangle
- private static final float RADIUS_RATIO = 0.75f / 17f;
- private static final float DIAG_OFFSET_MULTIPLIER = 0.707107f;
- // How far the circle defining the corners is inset from the edges
- private final float mAppliedCornerInset;
-
- private static final float INV_TAN = 1f / (float) Math.tan(Math.PI / 8f);
- private static final float CUT_WIDTH_DP = 1f / 12f;
-
- // Where the top and left points of the triangle would be if not for rounding
- private final PointF mVirtualTop = new PointF();
- private final PointF mVirtualLeft = new PointF();
-
- private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- private final Paint mForegroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- private final int mDarkModeBackgroundColor;
- private final int mDarkModeFillColor;
- private final int mLightModeBackgroundColor;
- private final int mLightModeFillColor;
- private final Path mFullPath = new Path();
- private final Path mForegroundPath = new Path();
- private final Path mXPath = new Path();
- // Cut out when STATE_EMPTY
- private final Path mCutPath = new Path();
- // Draws the slash when in airplane mode
- private final SlashArtist mSlash = new SlashArtist();
- private final Handler mHandler;
- private float mOldDarkIntensity = -1;
- private float mNumLevels = 1;
- private int mIntrinsicSize;
- private int mLevel;
- private int mState;
- private boolean mVisible;
- private boolean mAnimating;
- private int mCurrentDot;
-
- public SignalDrawable(Context context) {
- mDarkModeBackgroundColor =
- Utils.getDefaultColor(context, R.color.dark_mode_icon_color_dual_tone_background);
- mDarkModeFillColor =
- Utils.getDefaultColor(context, R.color.dark_mode_icon_color_dual_tone_fill);
- mLightModeBackgroundColor =
- Utils.getDefaultColor(context, R.color.light_mode_icon_color_dual_tone_background);
- mLightModeFillColor =
- Utils.getDefaultColor(context, R.color.light_mode_icon_color_dual_tone_fill);
- mIntrinsicSize = context.getResources().getDimensionPixelSize(R.dimen.signal_icon_size);
-
- mHandler = new Handler();
- setDarkIntensity(0);
-
- mAppliedCornerInset = context.getResources()
- .getDimensionPixelSize(R.dimen.stat_sys_mobile_signal_circle_inset);
- }
-
- public void setIntrinsicSize(int size) {
- mIntrinsicSize = size;
- }
-
- @Override
- public int getIntrinsicWidth() {
- return mIntrinsicSize;
- }
-
- @Override
- public int getIntrinsicHeight() {
- return mIntrinsicSize;
- }
-
- public void setNumLevels(int levels) {
- if (levels == mNumLevels) return;
- mNumLevels = levels;
- invalidateSelf();
- }
-
- private void setSignalState(int state) {
- if (state == mState) return;
- mState = state;
- updateAnimation();
- invalidateSelf();
- }
-
- private void updateAnimation() {
- boolean shouldAnimate = (mState == STATE_CARRIER_CHANGE) && mVisible;
- if (shouldAnimate == mAnimating) return;
- mAnimating = shouldAnimate;
- if (shouldAnimate) {
- mChangeDot.run();
- } else {
- mHandler.removeCallbacks(mChangeDot);
- }
- }
-
- @Override
- protected boolean onLevelChange(int state) {
- setNumLevels(getNumLevels(state));
- setSignalState(getState(state));
- int level = getLevel(state);
- if (level != mLevel) {
- mLevel = level;
- invalidateSelf();
- }
- return true;
- }
-
- public void setColors(int background, int foreground) {
- mPaint.setColor(background);
- mForegroundPaint.setColor(foreground);
- }
-
- public void setDarkIntensity(float darkIntensity) {
- if (darkIntensity == mOldDarkIntensity) {
- return;
- }
- mPaint.setColor(getBackgroundColor(darkIntensity));
- mForegroundPaint.setColor(getFillColor(darkIntensity));
- mOldDarkIntensity = darkIntensity;
- invalidateSelf();
- }
-
- private int getFillColor(float darkIntensity) {
- return getColorForDarkIntensity(
- darkIntensity, mLightModeFillColor, mDarkModeFillColor);
- }
-
- private int getBackgroundColor(float darkIntensity) {
- return getColorForDarkIntensity(
- darkIntensity, mLightModeBackgroundColor, mDarkModeBackgroundColor);
- }
-
- private int getColorForDarkIntensity(float darkIntensity, int lightColor, int darkColor) {
- return (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, lightColor, darkColor);
- }
-
- @Override
- protected void onBoundsChange(Rect bounds) {
- super.onBoundsChange(bounds);
- invalidateSelf();
- }
-
- @Override
- public void draw(@NonNull Canvas canvas) {
- final float width = getBounds().width();
- final float height = getBounds().height();
-
- boolean isRtl = getLayoutDirection() == LayoutDirection.RTL;
- if (isRtl) {
- canvas.save();
- // Mirror the drawable
- canvas.translate(width, 0);
- canvas.scale(-1.0f, 1.0f);
- }
- mFullPath.reset();
- mFullPath.setFillType(FillType.WINDING);
-
- final float padding = Math.round(PAD * width);
- final float cornerRadius = RADIUS_RATIO * height;
- // Offset from circle where the hypotenuse meets the circle
- final float diagOffset = DIAG_OFFSET_MULTIPLIER * cornerRadius;
-
- // 1 - Bottom right, above corner
- mFullPath.moveTo(width - padding, height - padding - cornerRadius);
- // 2 - Line to top right, below corner
- mFullPath.lineTo(width - padding, padding + cornerRadius + mAppliedCornerInset);
- // 3 - Arc to top right, on hypotenuse
- mFullPath.arcTo(
- width - padding - (2 * cornerRadius),
- padding + mAppliedCornerInset,
- width - padding,
- padding + mAppliedCornerInset + (2 * cornerRadius),
- 0.f, -135.f, false
- );
- // 4 - Line to bottom left, on hypotenuse
- mFullPath.lineTo(padding + mAppliedCornerInset + cornerRadius - diagOffset,
- height - padding - cornerRadius - diagOffset);
- // 5 - Arc to bottom left, on leg
- mFullPath.arcTo(
- padding + mAppliedCornerInset,
- height - padding - (2 * cornerRadius),
- padding + mAppliedCornerInset + ( 2 * cornerRadius),
- height - padding,
- -135.f, -135.f, false
- );
- // 6 - Line to bottom rght, before corner
- mFullPath.lineTo(width - padding - cornerRadius, height - padding);
- // 7 - Arc to beginning (bottom right, above corner)
- mFullPath.arcTo(
- width - padding - (2 * cornerRadius),
- height - padding - (2 * cornerRadius),
- width - padding,
- height - padding,
- 90.f, -90.f, false
- );
-
- if (mState == STATE_CARRIER_CHANGE) {
- float cutWidth = (DOT_CUT_WIDTH * width);
- float cutHeight = (DOT_CUT_HEIGHT * width);
- float dotSize = (DOT_SIZE * height);
- float dotPadding = (DOT_PADDING * height);
-
- mFullPath.moveTo(width - padding, height - padding);
- mFullPath.rLineTo(-cutWidth, 0);
- mFullPath.rLineTo(0, -cutHeight);
- mFullPath.rLineTo(cutWidth, 0);
- mFullPath.rLineTo(0, cutHeight);
- float dotSpacing = dotPadding * 2 + dotSize;
- float x = width - padding - dotSize;
- float y = height - padding - dotSize;
- mForegroundPath.reset();
- drawDot(mFullPath, mForegroundPath, x, y, dotSize, 2);
- drawDot(mFullPath, mForegroundPath, x - dotSpacing, y, dotSize, 1);
- drawDot(mFullPath, mForegroundPath, x - dotSpacing * 2, y, dotSize, 0);
- } else if (mState == STATE_CUT) {
- float cut = (CUT_OUT * width);
- mFullPath.moveTo(width - padding, height - padding);
- mFullPath.rLineTo(-cut, 0);
- mFullPath.rLineTo(0, -cut);
- mFullPath.rLineTo(cut, 0);
- mFullPath.rLineTo(0, cut);
- }
-
- if (mState == STATE_EMPTY) {
- // Where the corners would be if this were a real triangle
- mVirtualTop.set(
- width - padding,
- (padding + cornerRadius + mAppliedCornerInset) - (INV_TAN * cornerRadius));
- mVirtualLeft.set(
- (padding + cornerRadius + mAppliedCornerInset) - (INV_TAN * cornerRadius),
- height - padding);
-
- final float cutWidth = CUT_WIDTH_DP * height;
- final float cutDiagInset = cutWidth * INV_TAN;
-
- // Cut out a smaller triangle from the center of mFullPath
- mCutPath.reset();
- mCutPath.setFillType(FillType.WINDING);
- mCutPath.moveTo(width - padding - cutWidth, height - padding - cutWidth);
- mCutPath.lineTo(width - padding - cutWidth, mVirtualTop.y + cutDiagInset);
- mCutPath.lineTo(mVirtualLeft.x + cutDiagInset, height - padding - cutWidth);
- mCutPath.lineTo(width - padding - cutWidth, height - padding - cutWidth);
-
- // Draw empty state as only background
- mForegroundPath.reset();
- mFullPath.op(mCutPath, Path.Op.DIFFERENCE);
- } else if (mState == STATE_AIRPLANE) {
- // Airplane mode is slashed, fully drawn background
- mForegroundPath.reset();
- mSlash.draw((int) height, (int) width, canvas, mPaint);
- } else if (mState != STATE_CARRIER_CHANGE) {
- mForegroundPath.reset();
- int sigWidth = Math.round(calcFit(mLevel / (mNumLevels - 1)) * (width - 2 * padding));
- mForegroundPath.addRect(padding, padding, padding + sigWidth, height - padding,
- Direction.CW);
- mForegroundPath.op(mFullPath, Op.INTERSECT);
- }
-
- canvas.drawPath(mFullPath, mPaint);
- canvas.drawPath(mForegroundPath, mForegroundPaint);
- if (mState == STATE_CUT) {
- mXPath.reset();
- mXPath.moveTo(X_PATH[0][0] * width, X_PATH[0][1] * height);
- for (int i = 1; i < X_PATH.length; i++) {
- mXPath.rLineTo(X_PATH[i][0] * width, X_PATH[i][1] * height);
- }
- canvas.drawPath(mXPath, mForegroundPaint);
- }
- if (isRtl) {
- canvas.restore();
- }
- }
-
- private void drawDot(Path fullPath, Path foregroundPath, float x, float y, float dotSize,
- int i) {
- Path p = (i == mCurrentDot) ? foregroundPath : fullPath;
- p.addRect(x, y, x + dotSize, y + dotSize, Direction.CW);
- }
-
- // This is a fit line based on previous values of provided in assets, but if
- // you look at the a plot of this actual fit, it makes a lot of sense, what it does
- // is compress the areas that are very visually easy to see changes (the middle sections)
- // and spread out the sections that are hard to see (each end of the icon).
- // The current fit is cubic, but pretty easy to change the way the code is written (just add
- // terms to the end of FIT).
- private float calcFit(float v) {
- float ret = 0;
- float t = v;
- for (int i = 0; i < FIT.length; i++) {
- ret += FIT[i] * t;
- t *= v;
- }
- return ret;
- }
-
- @Override
- public int getAlpha() {
- return mPaint.getAlpha();
- }
-
- @Override
- public void setAlpha(@IntRange(from = 0, to = 255) int alpha) {
- mPaint.setAlpha(alpha);
- mForegroundPaint.setAlpha(alpha);
- }
-
- @Override
- public void setColorFilter(@Nullable ColorFilter colorFilter) {
- mPaint.setColorFilter(colorFilter);
- mForegroundPaint.setColorFilter(colorFilter);
- }
-
- @Override
- public int getOpacity() {
- return 255;
- }
-
- @Override
- public boolean setVisible(boolean visible, boolean restart) {
- mVisible = visible;
- updateAnimation();
- return super.setVisible(visible, restart);
- }
-
- private final Runnable mChangeDot = new Runnable() {
- @Override
- public void run() {
- if (++mCurrentDot == NUM_DOTS) {
- mCurrentDot = 0;
- }
- invalidateSelf();
- mHandler.postDelayed(mChangeDot, DOT_DELAY);
- }
- };
-
- public static int getLevel(int fullState) {
- return fullState & LEVEL_MASK;
- }
-
- public static int getState(int fullState) {
- return (fullState & STATE_MASK) >> STATE_SHIFT;
- }
-
- public static int getNumLevels(int fullState) {
- return (fullState & NUM_LEVEL_MASK) >> NUM_LEVEL_SHIFT;
- }
-
- public static int getState(int level, int numLevels, boolean cutOut) {
- return ((cutOut ? STATE_CUT : 0) << STATE_SHIFT)
- | (numLevels << NUM_LEVEL_SHIFT)
- | level;
- }
-
- public static int getCarrierChangeState(int numLevels) {
- return (STATE_CARRIER_CHANGE << STATE_SHIFT) | (numLevels << NUM_LEVEL_SHIFT);
- }
-
- public static int getEmptyState(int numLevels) {
- return (STATE_EMPTY << STATE_SHIFT) | (numLevels << NUM_LEVEL_SHIFT);
- }
-
- public static int getAirplaneModeState(int numLevels) {
- return (STATE_AIRPLANE << STATE_SHIFT) | (numLevels << NUM_LEVEL_SHIFT);
- }
-
- private final class SlashArtist {
- // These values are derived in un-rotated (vertical) orientation
- private static final float SLASH_WIDTH = 1.8384776f;
- private static final float SLASH_HEIGHT = 22f;
- private static final float CENTER_X = 10.65f;
- private static final float CENTER_Y = 15.869239f;
- private static final float SCALE = 24f;
-
- // Bottom is derived during animation
- private static final float LEFT = (CENTER_X - (SLASH_WIDTH / 2)) / SCALE;
- private static final float TOP = (CENTER_Y - (SLASH_HEIGHT / 2)) / SCALE;
- private static final float RIGHT = (CENTER_X + (SLASH_WIDTH / 2)) / SCALE;
- private static final float BOTTOM = (CENTER_Y + (SLASH_HEIGHT / 2)) / SCALE;
- // Draw the slash washington-monument style; rotate to no-u-turn style
- private static final float ROTATION = -45f;
-
- private final Path mPath = new Path();
- private final RectF mSlashRect = new RectF();
-
- void draw(int height, int width, @NonNull Canvas canvas, Paint paint) {
- Matrix m = new Matrix();
- final float radius = scale(SlashDrawable.CORNER_RADIUS, width);
- updateRect(
- scale(LEFT, width),
- scale(TOP, height),
- scale(RIGHT, width),
- scale(BOTTOM, height));
-
- mPath.reset();
- // Draw the slash vertically
- mPath.addRoundRect(mSlashRect, radius, radius, Direction.CW);
- m.setRotate(ROTATION, width / 2, height / 2);
- mPath.transform(m);
- canvas.drawPath(mPath, paint);
-
- // Rotate back to vertical, and draw the cut-out rect next to this one
- m.setRotate(-ROTATION, width / 2, height / 2);
- mPath.transform(m);
- m.setTranslate(mSlashRect.width(), 0);
- mPath.transform(m);
- mPath.addRoundRect(mSlashRect, radius, radius, Direction.CW);
- m.setRotate(ROTATION, width / 2, height / 2);
- mPath.transform(m);
- canvas.clipOutPath(mPath);
- }
-
- void updateRect(float left, float top, float right, float bottom) {
- mSlashRect.left = left;
- mSlashRect.top = top;
- mSlashRect.right = right;
- mSlashRect.bottom = bottom;
- }
-
- private float scale(float frac, int width) {
- return frac * width;
- }
- }
-}
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 1bf719ae68af..24920cba21f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -111,6 +111,7 @@ import android.view.IWindowManager;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
+import android.view.RemoteAnimationAdapter;
import android.view.ThreadedRenderer;
import android.view.View;
import android.view.ViewGroup;
@@ -140,7 +141,6 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.ActivityStarterDelegate;
import com.android.systemui.AutoReinflateContainer;
-import com.android.systemui.charging.WirelessChargingAnimation;
import com.android.systemui.DemoMode;
import com.android.systemui.Dependency;
import com.android.systemui.EventLogTags;
@@ -152,6 +152,7 @@ import com.android.systemui.SystemUI;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.UiOffloadThread;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.charging.WirelessChargingAnimation;
import com.android.systemui.classifier.FalsingLog;
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -181,6 +182,7 @@ import com.android.systemui.stackdivider.WindowManagerProxy;
import com.android.systemui.statusbar.ActivatableNotificationView;
import com.android.systemui.statusbar.BackDropView;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.DismissView;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.EmptyShadeView;
@@ -208,6 +210,7 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.AboveShelfObserver;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -219,6 +222,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
@@ -809,15 +813,14 @@ public class StatusBar extends SystemUI implements DemoMode,
.commit();
mIconController = Dependency.get(StatusBarIconController.class);
- mHeadsUpManager = new HeadsUpManager(context, mStatusBarWindow, mGroupManager);
- mHeadsUpManager.setBar(this);
+ mHeadsUpManager = new HeadsUpManagerPhone(context, mStatusBarWindow, mGroupManager, this,
+ mVisualStabilityManager);
mHeadsUpManager.addListener(this);
mHeadsUpManager.addListener(mNotificationPanel);
mHeadsUpManager.addListener(mGroupManager);
mHeadsUpManager.addListener(mVisualStabilityManager);
mNotificationPanel.setHeadsUpManager(mHeadsUpManager);
mGroupManager.setHeadsUpManager(mHeadsUpManager);
- mHeadsUpManager.setVisualStabilityManager(mVisualStabilityManager);
putComponent(HeadsUpManager.class, mHeadsUpManager);
mEntryManager.setUpWithPresenter(this, mStackScroller, this, mHeadsUpManager);
@@ -1348,7 +1351,8 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void onPerformRemoveNotification(StatusBarNotification n) {
- if (mStackScroller.hasPulsingNotifications() && mHeadsUpManager.getAllEntries().isEmpty()) {
+ if (mStackScroller.hasPulsingNotifications() &&
+ !mHeadsUpManager.hasHeadsUpNotifications()) {
// We were showing a pulse for a notification, but no notifications are pulsing anymore.
// Finish the pulse.
mDozeScrimController.pulseOutNow();
@@ -2097,9 +2101,8 @@ public class StatusBar extends SystemUI implements DemoMode,
}
public void maybeEscalateHeadsUp() {
- Collection<HeadsUpManager.HeadsUpEntry> entries = mHeadsUpManager.getAllEntries();
- for (HeadsUpManager.HeadsUpEntry entry : entries) {
- final StatusBarNotification sbn = entry.entry.notification;
+ mHeadsUpManager.getAllEntries().forEach(entry -> {
+ final StatusBarNotification sbn = entry.notification;
final Notification notification = sbn.getNotification();
if (notification.fullScreenIntent != null) {
if (DEBUG) {
@@ -2109,11 +2112,11 @@ public class StatusBar extends SystemUI implements DemoMode,
EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION,
sbn.getKey());
notification.fullScreenIntent.send();
- entry.entry.notifyFullScreenIntentLaunched();
+ entry.notifyFullScreenIntentLaunched();
} catch (PendingIntent.CanceledException e) {
}
}
- }
+ });
mHeadsUpManager.releaseAllImmediately();
}
@@ -2459,14 +2462,25 @@ public class StatusBar extends SystemUI implements DemoMode,
}
@Override
- public void showChargingAnimation(int batteryLevel) {
- if (mDozing) {
- // ambient
- } else if (mKeyguardManager.isKeyguardLocked()) {
- // lockscreen
+ public void showWirelessChargingAnimation(int batteryLevel) {
+ if (mDozing || mKeyguardManager.isKeyguardLocked()) {
+ // on ambient or lockscreen, hide notification panel
+ WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null,
+ batteryLevel, new WirelessChargingAnimation.Callback() {
+ @Override
+ public void onAnimationStarting() {
+ CrossFadeHelper.fadeOut(mNotificationPanel, 1);
+ }
+
+ @Override
+ public void onAnimationEnded() {
+ CrossFadeHelper.fadeIn(mNotificationPanel);
+ }
+ }).show();
} else {
+ // workspace
WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null,
- batteryLevel).show();
+ batteryLevel, null).show();
}
}
@@ -2836,7 +2850,7 @@ public class StatusBar extends SystemUI implements DemoMode,
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
int result = ActivityManager.START_CANCELED;
ActivityOptions options = new ActivityOptions(getActivityOptions(
- null /* sourceNotification */));
+ null /* remoteAnimation */));
options.setDisallowEnterPictureInPictureWhileLaunching(
disallowEnterPictureInPictureWhileLaunching);
if (intent == KeyguardBottomAreaView.INSECURE_CAMERA_INTENT) {
@@ -3882,6 +3896,7 @@ public class StatusBar extends SystemUI implements DemoMode,
private void instantCollapseNotificationPanel() {
mNotificationPanel.instantCollapse();
+ runPostCollapseRunnables();
}
@Override
@@ -4393,11 +4408,13 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void onScreenTurnedOn() {
+ mScrimController.onScreenTurnedOn();
}
@Override
public void onScreenTurnedOff() {
mFalsingManager.onScreenOff();
+ mScrimController.onScreenTurnedOff();
// If we pulse in from AOD, we turn the screen off first. However, updatingIsKeyguard
// in that case destroys the HeadsUpManager state, so don't do it in that case.
if (!isPulsing()) {
@@ -4658,24 +4675,22 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void onPulseStarted() {
callback.onPulseStarted();
- Collection<HeadsUpManager.HeadsUpEntry> pulsingEntries =
- mHeadsUpManager.getAllEntries();
- if (!pulsingEntries.isEmpty()) {
+ if (mHeadsUpManager.hasHeadsUpNotifications()) {
// Only pulse the stack scroller if there's actually something to show.
// Otherwise just show the always-on screen.
- setPulsing(pulsingEntries);
+ setPulsing(true);
}
}
@Override
public void onPulseFinished() {
callback.onPulseFinished();
- setPulsing(null);
+ setPulsing(false);
}
- private void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing) {
+ private void setPulsing(boolean pulsing) {
mNotificationPanel.setPulsing(pulsing);
- mVisualStabilityManager.setPulsing(pulsing != null);
+ mVisualStabilityManager.setPulsing(pulsing);
mIgnoreTouchWhilePulsing = false;
}
}, reason);
@@ -4823,7 +4838,7 @@ public class StatusBar extends SystemUI implements DemoMode,
// for heads up notifications
- protected HeadsUpManager mHeadsUpManager;
+ protected HeadsUpManagerPhone mHeadsUpManager;
private AboveShelfObserver mAboveShelfObserver;
@@ -4926,7 +4941,7 @@ public class StatusBar extends SystemUI implements DemoMode,
// Release the HUN notification to the shade.
if (isPresenterFullyCollapsed()) {
- HeadsUpManager.setIsClickedNotification(row, true);
+ HeadsUpUtil.setIsClickedHeadsUpNotification(row, true);
}
//
// In most cases, when FLAG_AUTO_CANCEL is set, the notification will
@@ -4987,11 +5002,15 @@ public class StatusBar extends SystemUI implements DemoMode,
fillInIntent = new Intent().putExtra(Notification.EXTRA_REMOTE_INPUT_DRAFT,
remoteInputText.toString());
}
+ RemoteAnimationAdapter adapter = mActivityLaunchAnimator.getLaunchAnimation(
+ row);
try {
+ ActivityManager.getService().registerRemoteAnimationForNextActivityStart(
+ intent.getCreatorPackage(), adapter);
launchResult = intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
- null, null, getActivityOptions(row));
+ null, null, getActivityOptions(adapter));
mActivityLaunchAnimator.setLaunchResult(launchResult);
- } catch (PendingIntent.CanceledException e) {
+ } catch (RemoteException | PendingIntent.CanceledException e) {
// the stack trace isn't very helpful here.
// Just log the exception message.
Log.w(TAG, "Sending contentIntent failed: " + e);
@@ -5045,6 +5064,8 @@ public class StatusBar extends SystemUI implements DemoMode,
} else if (!isPresenterFullyCollapsed()) {
instantCollapseNotificationPanel();
visibilityChanged(false);
+ } else {
+ runPostCollapseRunnables();
}
}
@@ -5149,7 +5170,8 @@ public class StatusBar extends SystemUI implements DemoMode,
AsyncTask.execute(() -> {
int launchResult = TaskStackBuilder.create(mContext)
.addNextIntentWithParentStack(intent)
- .startActivities(getActivityOptions(row),
+ .startActivities(getActivityOptions(
+ mActivityLaunchAnimator.getLaunchAnimation(row)),
new UserHandle(UserHandle.getUserId(appUid)));
mActivityLaunchAnimator.setLaunchResult(launchResult);
if (shouldCollapse()) {
@@ -5284,7 +5306,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}
try {
intent.send(null, 0, null, null, null, null, getActivityOptions(
- null /* sourceNotification */));
+ null /* animationAdapter */));
} catch (PendingIntent.CanceledException e) {
// the stack trace isn't very helpful here.
// Just log the exception message.
@@ -5312,10 +5334,10 @@ public class StatusBar extends SystemUI implements DemoMode,
return true;
}
- protected Bundle getActivityOptions(ExpandableNotificationRow sourceNotification) {
+ protected Bundle getActivityOptions(@Nullable RemoteAnimationAdapter animationAdapter) {
ActivityOptions options;
- if (sourceNotification != null) {
- options = mActivityLaunchAnimator.getLaunchAnimation(sourceNotification);
+ if (animationAdapter != null) {
+ options = ActivityOptions.makeRemoteAnimation(animationAdapter);
} else {
options = ActivityOptions.makeBasic();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index c30f6339f8da..948f524bb188 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -106,6 +106,7 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D
mLp.gravity = Gravity.TOP;
mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
mLp.setTitle("StatusBar");
+ mLp.accessibilityTitle = mContext.getString(R.string.status_bar);
mLp.packageName = mContext.getPackageName();
mStatusBarView = statusBarView;
mBarHeight = barHeight;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
index 1da50ad65d62..503a1b40bb0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
@@ -36,6 +36,7 @@ import com.android.systemui.statusbar.stack.ViewState;
public class StatusIconContainer extends AlphaOptimizedLinearLayout {
private static final String TAG = "StatusIconContainer";
+ private static final boolean DEBUG = false;
private static final int MAX_ICONS = 5;
private static final int MAX_DOTS = 3;
@@ -94,7 +95,7 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout {
int childCount = getChildCount();
// Underflow === don't show content until that index
int firstUnderflowIndex = -1;
- android.util.Log.d(TAG, "calculateIconTransitions: start=" + translationX);
+ if (DEBUG) android.util.Log.d(TAG, "calculateIconTransitions: start=" + translationX);
//TODO: Dots
for (int i = childCount - 1; i >= 0; i--) {
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 53dfb244c776..040d7ec32ecc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -16,119 +16,69 @@
package com.android.systemui.statusbar.policy;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.database.ContentObserver;
+import android.os.SystemClock;
import android.os.Handler;
import android.os.Looper;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.support.v4.util.ArraySet;
import android.util.ArrayMap;
+import android.provider.Settings;
import android.util.Log;
-import android.util.Pools;
-import android.view.View;
-import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.NotificationData;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.StatusBar;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collection;
+import java.util.Iterator;
+import java.util.stream.Stream;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Stack;
/**
* A manager which handles heads up notifications which is a special mode where
* they simply peek from the top of the screen.
*/
-public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsListener,
- VisualStabilityManager.Callback {
+public class HeadsUpManager {
private static final String TAG = "HeadsUpManager";
private static final boolean DEBUG = false;
private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms";
- private static final int TAG_CLICKED_NOTIFICATION = R.id.is_clicked_heads_up_tag;
- private final int mHeadsUpNotificationDecay;
- private final int mMinimumDisplayTime;
+ protected final Clock mClock = new Clock();
+ protected final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>();
+ protected final Handler mHandler = new Handler(Looper.getMainLooper());
- private final int mTouchAcceptanceDelay;
- private final ArrayMap<String, Long> mSnoozedPackages;
- private final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>();
- private final int mDefaultSnoozeLengthMs;
- private final Handler mHandler = new Handler(Looper.getMainLooper());
- private final Pools.Pool<HeadsUpEntry> mEntryPool = new Pools.Pool<HeadsUpEntry>() {
+ protected final Context mContext;
- private Stack<HeadsUpEntry> mPoolObjects = new Stack<>();
+ protected int mHeadsUpNotificationDecay;
+ protected int mMinimumDisplayTime;
+ protected int mTouchAcceptanceDelay;
+ protected int mSnoozeLengthMs;
+ protected boolean mHasPinnedNotification;
+ protected int mUser;
- @Override
- public HeadsUpEntry acquire() {
- if (!mPoolObjects.isEmpty()) {
- return mPoolObjects.pop();
- }
- return new HeadsUpEntry();
- }
+ private final HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>();
+ private final ArrayMap<String, Long> mSnoozedPackages;
- @Override
- public boolean release(HeadsUpEntry instance) {
- instance.reset();
- mPoolObjects.push(instance);
- return true;
- }
- };
-
- private final View mStatusBarWindowView;
- private final int mStatusBarHeight;
- private final Context mContext;
- private final NotificationGroupManager mGroupManager;
- private StatusBar mBar;
- private int mSnoozeLengthMs;
- private ContentObserver mSettingsObserver;
- private HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>();
- private HashSet<String> mSwipedOutKeys = new HashSet<>();
- private int mUser;
- private Clock mClock;
- private boolean mReleaseOnExpandFinish;
- private boolean mTrackingHeadsUp;
- private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>();
- private ArraySet<NotificationData.Entry> mEntriesToRemoveWhenReorderingAllowed
- = new ArraySet<>();
- private boolean mIsExpanded;
- private boolean mHasPinnedNotification;
- private int[] mTmpTwoArray = new int[2];
- private boolean mHeadsUpGoingAway;
- private boolean mWaitingOnCollapseWhenGoingAway;
- private boolean mIsObserving;
- private boolean mRemoteInputActive;
- private float mExpandedHeight;
- private VisualStabilityManager mVisualStabilityManager;
- private int mStatusBarState;
-
- public HeadsUpManager(final Context context, View statusBarWindowView,
- NotificationGroupManager groupManager) {
+ public HeadsUpManager(@NonNull final Context context) {
mContext = context;
- Resources resources = mContext.getResources();
- mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay);
- mSnoozedPackages = new ArrayMap<>();
- mDefaultSnoozeLengthMs = resources.getInteger(R.integer.heads_up_default_snooze_length_ms);
- mSnoozeLengthMs = mDefaultSnoozeLengthMs;
+ Resources resources = context.getResources();
mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
mHeadsUpNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay);
- mClock = new Clock();
+ mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay);
+ mSnoozedPackages = new ArrayMap<>();
+ int defaultSnoozeLengthMs =
+ resources.getInteger(R.integer.heads_up_default_snooze_length_ms);
mSnoozeLengthMs = Settings.Global.getInt(context.getContentResolver(),
- SETTING_HEADS_UP_SNOOZE_LENGTH_MS, mDefaultSnoozeLengthMs);
- mSettingsObserver = new ContentObserver(mHandler) {
+ SETTING_HEADS_UP_SNOOZE_LENGTH_MS, defaultSnoozeLengthMs);
+ ContentObserver settingsObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
final int packageSnoozeLengthMs = Settings.Global.getInt(
@@ -141,48 +91,27 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
};
context.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false,
- mSettingsObserver);
- mStatusBarWindowView = statusBarWindowView;
- mGroupManager = groupManager;
- mStatusBarHeight = resources.getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_height);
- }
-
- private void updateTouchableRegionListener() {
- boolean shouldObserve = mHasPinnedNotification || mHeadsUpGoingAway
- || mWaitingOnCollapseWhenGoingAway;
- if (shouldObserve == mIsObserving) {
- return;
- }
- if (shouldObserve) {
- mStatusBarWindowView.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
- mStatusBarWindowView.requestLayout();
- } else {
- mStatusBarWindowView.getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
- }
- mIsObserving = shouldObserve;
+ settingsObserver);
}
- public void setBar(StatusBar bar) {
- mBar = bar;
- }
-
- public void addListener(OnHeadsUpChangedListener listener) {
+ /**
+ * Adds an OnHeadUpChangedListener to observe events.
+ */
+ public void addListener(@NonNull OnHeadsUpChangedListener listener) {
mListeners.add(listener);
}
- public void removeListener(OnHeadsUpChangedListener listener) {
+ /**
+ * Removes the OnHeadUpChangedListener from the observer list.
+ */
+ public void removeListener(@NonNull OnHeadsUpChangedListener listener) {
mListeners.remove(listener);
}
- public StatusBar getBar() {
- return mBar;
- }
-
/**
* Called when posting a new notification to the heads up.
*/
- public void showNotification(NotificationData.Entry headsUp) {
+ public void showNotification(@NonNull NotificationData.Entry headsUp) {
if (DEBUG) Log.v(TAG, "showNotification");
addHeadsUpEntry(headsUp);
updateNotification(headsUp, true);
@@ -192,7 +121,7 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
/**
* Called when updating or posting a notification to the heads up.
*/
- public void updateNotification(NotificationData.Entry headsUp, boolean alert) {
+ public void updateNotification(@NonNull NotificationData.Entry headsUp, boolean alert) {
if (DEBUG) Log.v(TAG, "updateNotification");
headsUp.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
@@ -204,14 +133,13 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
// with the groupmanager
return;
}
- headsUpEntry.updateEntry();
+ headsUpEntry.updateEntry(true /* updatePostTime */);
setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUp));
}
}
- private void addHeadsUpEntry(NotificationData.Entry entry) {
- HeadsUpEntry headsUpEntry = mEntryPool.acquire();
-
+ private void addHeadsUpEntry(@NonNull NotificationData.Entry entry) {
+ HeadsUpEntry headsUpEntry = createHeadsUpEntry();
// This will also add the entry to the sortedList
headsUpEntry.setEntry(entry);
mHeadsUpEntries.put(entry.key, headsUpEntry);
@@ -223,16 +151,17 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
}
- private boolean shouldHeadsUpBecomePinned(NotificationData.Entry entry) {
- return mStatusBarState != StatusBarState.KEYGUARD
- && !mIsExpanded || hasFullScreenIntent(entry);
+ protected boolean shouldHeadsUpBecomePinned(@NonNull NotificationData.Entry entry) {
+ return hasFullScreenIntent(entry);
}
- private boolean hasFullScreenIntent(NotificationData.Entry entry) {
+ protected boolean hasFullScreenIntent(@NonNull NotificationData.Entry entry) {
return entry.notification.getNotification().fullScreenIntent != null;
}
- private void setEntryPinned(HeadsUpEntry headsUpEntry, boolean isPinned) {
+ protected void setEntryPinned(
+ @NonNull HeadsUpManager.HeadsUpEntry headsUpEntry, boolean isPinned) {
+ if (DEBUG) Log.v(TAG, "setEntryPinned: " + isPinned);
ExpandableNotificationRow row = headsUpEntry.entry.row;
if (row.isPinned() != isPinned) {
row.setPinned(isPinned);
@@ -247,33 +176,35 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
}
}
- private void removeHeadsUpEntry(NotificationData.Entry entry) {
+ protected void removeHeadsUpEntry(@NonNull NotificationData.Entry entry) {
HeadsUpEntry remove = mHeadsUpEntries.remove(entry.key);
+ onHeadsUpEntryRemoved(remove);
+ }
+
+ protected void onHeadsUpEntryRemoved(@NonNull HeadsUpEntry remove) {
+ NotificationData.Entry entry = remove.entry;
entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
entry.row.setHeadsUp(false);
setEntryPinned(remove, false /* isPinned */);
for (OnHeadsUpChangedListener listener : mListeners) {
listener.onHeadsUpStateChanged(entry, false);
}
- mEntryPool.release(remove);
+ releaseHeadsUpEntry(remove);
}
- public void removeAllHeadsUpEntries() {
- for (String key : mHeadsUpEntries.keySet()) {
- removeHeadsUpEntry(mHeadsUpEntries.get(key).entry);
- }
- }
-
- private void updatePinnedMode() {
+ protected void updatePinnedMode() {
boolean hasPinnedNotification = hasPinnedNotificationInternal();
if (hasPinnedNotification == mHasPinnedNotification) {
return;
}
+ if (DEBUG) {
+ Log.v(TAG, "Pinned mode changed: " + mHasPinnedNotification + " -> " +
+ hasPinnedNotification);
+ }
mHasPinnedNotification = hasPinnedNotification;
if (mHasPinnedNotification) {
MetricsLogger.count(mContext, "note_peek", 1);
}
- updateTouchableRegionListener();
for (OnHeadsUpChangedListener listener : mListeners) {
listener.onHeadsUpPinnedModeChanged(hasPinnedNotification);
}
@@ -285,47 +216,36 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
* @return true if the notification was removed and false if it still needs to be kept around
* for a bit since it wasn't shown long enough
*/
- public boolean removeNotification(String key, boolean ignoreEarliestRemovalTime) {
- if (DEBUG) Log.v(TAG, "remove");
- if (wasShownLongEnough(key) || ignoreEarliestRemovalTime) {
- releaseImmediately(key);
- return true;
- } else {
- getHeadsUpEntry(key).removeAsSoonAsPossible();
- return false;
- }
+ public boolean removeNotification(@NonNull String key, boolean ignoreEarliestRemovalTime) {
+ if (DEBUG) Log.v(TAG, "removeNotification");
+ releaseImmediately(key);
+ return true;
}
- private boolean wasShownLongEnough(String key) {
- HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
- HeadsUpEntry topEntry = getTopEntry();
- if (mSwipedOutKeys.contains(key)) {
- // We always instantly dismiss views being manually swiped out.
- mSwipedOutKeys.remove(key);
- return true;
- }
- if (headsUpEntry != topEntry) {
- return true;
- }
- return headsUpEntry.wasShownLongEnough();
- }
-
- public boolean isHeadsUp(String key) {
+ /**
+ * Returns if the given notification is in the Heads Up Notification list or not.
+ */
+ public boolean isHeadsUp(@NonNull String key) {
return mHeadsUpEntries.containsKey(key);
}
/**
- * Push any current Heads Up notification down into the shade.
+ * Pushes any current Heads Up notification down into the shade.
*/
public void releaseAllImmediately() {
if (DEBUG) Log.v(TAG, "releaseAllImmediately");
- ArrayList<String> keys = new ArrayList<>(mHeadsUpEntries.keySet());
- for (String key : keys) {
- releaseImmediately(key);
+ Iterator<HeadsUpEntry> iterator = mHeadsUpEntries.values().iterator();
+ while (iterator.hasNext()) {
+ HeadsUpEntry entry = iterator.next();
+ iterator.remove();
+ onHeadsUpEntryRemoved(entry);
}
}
- public void releaseImmediately(String key) {
+ /**
+ * Pushes the given Heads Up notification down into the shade.
+ */
+ public void releaseImmediately(@NonNull String key) {
HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
if (headsUpEntry == null) {
return;
@@ -334,11 +254,14 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
removeHeadsUpEntry(shadeEntry);
}
- public boolean isSnoozed(String packageName) {
+ /**
+ * Returns if the given notification is snoozed or not.
+ */
+ public boolean isSnoozed(@NonNull String packageName) {
final String key = snoozeKey(packageName, mUser);
Long snoozedUntil = mSnoozedPackages.get(key);
if (snoozedUntil != null) {
- if (snoozedUntil > SystemClock.elapsedRealtime()) {
+ if (snoozedUntil > mClock.currentTimeMillis()) {
if (DEBUG) Log.v(TAG, key + " snoozed");
return true;
}
@@ -347,39 +270,71 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
return false;
}
+ /**
+ * Snoozes all current Heads Up Notifications.
+ */
public void snooze() {
for (String key : mHeadsUpEntries.keySet()) {
HeadsUpEntry entry = mHeadsUpEntries.get(key);
String packageName = entry.entry.notification.getPackageName();
mSnoozedPackages.put(snoozeKey(packageName, mUser),
- SystemClock.elapsedRealtime() + mSnoozeLengthMs);
+ mClock.currentTimeMillis() + mSnoozeLengthMs);
}
- mReleaseOnExpandFinish = true;
}
- private static String snoozeKey(String packageName, int user) {
+ @NonNull
+ private static String snoozeKey(@NonNull String packageName, int user) {
return user + "," + packageName;
}
- private HeadsUpEntry getHeadsUpEntry(String key) {
+ @Nullable
+ protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) {
return mHeadsUpEntries.get(key);
}
- public NotificationData.Entry getEntry(String key) {
- return mHeadsUpEntries.get(key).entry;
+ /**
+ * Returns the entry of given Heads Up Notification.
+ *
+ * @param key Key of heads up notification
+ */
+ @Nullable
+ public NotificationData.Entry getEntry(@NonNull String key) {
+ HeadsUpEntry entry = mHeadsUpEntries.get(key);
+ return entry != null ? entry.entry : null;
+ }
+
+ /**
+ * Returns the stream of all current Heads Up Notifications.
+ */
+ @NonNull
+ public Stream<NotificationData.Entry> getAllEntries() {
+ return mHeadsUpEntries.values().stream().map(headsUpEntry -> headsUpEntry.entry);
+ }
+
+ /**
+ * Returns the top Heads Up Notification, which appeares to show at first.
+ */
+ @Nullable
+ public NotificationData.Entry getTopEntry() {
+ HeadsUpEntry topEntry = getTopHeadsUpEntry();
+ return (topEntry != null) ? topEntry.entry : null;
}
- public Collection<HeadsUpEntry> getAllEntries() {
- return mHeadsUpEntries.values();
+ /**
+ * Returns if any heads up notification is available or not.
+ */
+ public boolean hasHeadsUpNotifications() {
+ return !mHeadsUpEntries.isEmpty();
}
- public HeadsUpEntry getTopEntry() {
+ @Nullable
+ protected HeadsUpEntry getTopHeadsUpEntry() {
if (mHeadsUpEntries.isEmpty()) {
return null;
}
HeadsUpEntry topEntry = null;
for (HeadsUpEntry entry: mHeadsUpEntries.values()) {
- if (topEntry == null || entry.compareTo(topEntry) == -1) {
+ if (topEntry == null || entry.compareTo(topEntry) < 0) {
topEntry = entry;
}
}
@@ -387,56 +342,22 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
}
/**
- * Decides whether a click is invalid for a notification, i.e it has not been shown long enough
- * that a user might have consciously clicked on it.
- *
- * @param key the key of the touched notification
- * @return whether the touch is invalid and should be discarded
+ * Sets the current user.
*/
- public boolean shouldSwallowClick(String key) {
- HeadsUpEntry entry = mHeadsUpEntries.get(key);
- if (entry != null && mClock.currentTimeMillis() < entry.postTime) {
- return true;
- }
- return false;
- }
-
- public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
- if (mIsExpanded || mBar.isBouncerShowing()) {
- // The touchable region is always the full area when expanded
- return;
- }
- if (mHasPinnedNotification) {
- ExpandableNotificationRow topEntry = getTopEntry().entry.row;
- if (topEntry.isChildInGroup()) {
- final ExpandableNotificationRow groupSummary
- = mGroupManager.getGroupSummary(topEntry.getStatusBarNotification());
- if (groupSummary != null) {
- topEntry = groupSummary;
- }
- }
- topEntry.getLocationOnScreen(mTmpTwoArray);
- int minX = mTmpTwoArray[0];
- int maxX = mTmpTwoArray[0] + topEntry.getWidth();
- int maxY = topEntry.getIntrinsicHeight();
-
- info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
- info.touchableRegion.set(minX, 0, maxX, maxY);
- } else if (mHeadsUpGoingAway || mWaitingOnCollapseWhenGoingAway) {
- info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
- info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight);
- }
- }
-
public void setUser(int user) {
mUser = user;
}
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("HeadsUpManager state:");
+ dumpInternal(fd, pw, args);
+ }
+
+ protected void dumpInternal(
+ @NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.print(" mTouchAcceptanceDelay="); pw.println(mTouchAcceptanceDelay);
pw.print(" mSnoozeLengthMs="); pw.println(mSnoozeLengthMs);
- pw.print(" now="); pw.println(SystemClock.elapsedRealtime());
+ pw.print(" now="); pw.println(mClock.currentTimeMillis());
pw.print(" mUser="); pw.println(mUser);
for (HeadsUpEntry entry: mHeadsUpEntries.values()) {
pw.print(" HeadsUpEntry="); pw.println(entry.entry);
@@ -449,6 +370,9 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
}
}
+ /**
+ * Returns if there are any pinned Heads Up Notifications or not.
+ */
public boolean hasPinnedHeadsUp() {
return mHasPinnedNotification;
}
@@ -464,14 +388,8 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
}
/**
- * Notifies that a notification was swiped out and will be removed.
- *
- * @param key the notification key
+ * Unpins all pinned Heads Up Notifications.
*/
- public void addSwipedOutNotification(String key) {
- mSwipedOutKeys.add(key);
- }
-
public void unpinAll() {
for (String key : mHeadsUpEntries.keySet()) {
HeadsUpEntry entry = mHeadsUpEntries.get(key);
@@ -481,60 +399,13 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
}
}
- public void onExpandingFinished() {
- if (mReleaseOnExpandFinish) {
- releaseAllImmediately();
- mReleaseOnExpandFinish = false;
- } else {
- for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) {
- if (isHeadsUp(entry.key)) {
- // Maybe the heads-up was removed already
- removeHeadsUpEntry(entry);
- }
- }
- }
- mEntriesToRemoveAfterExpand.clear();
- }
-
- public void setTrackingHeadsUp(boolean trackingHeadsUp) {
- mTrackingHeadsUp = trackingHeadsUp;
- }
-
- public boolean isTrackingHeadsUp() {
- return mTrackingHeadsUp;
- }
-
- public void setIsExpanded(boolean isExpanded) {
- if (isExpanded != mIsExpanded) {
- mIsExpanded = isExpanded;
- if (isExpanded) {
- // make sure our state is sane
- mWaitingOnCollapseWhenGoingAway = false;
- mHeadsUpGoingAway = false;
- updateTouchableRegionListener();
- }
- }
- }
-
/**
- * @return the height of the top heads up notification when pinned. This is different from the
- * intrinsic height, which also includes whether the notification is system expanded and
- * is mainly used when dragging down from a heads up notification.
+ * Returns the value of the tracking-heads-up flag. See the doc of {@code setTrackingHeadsUp} as
+ * well.
*/
- public int getTopHeadsUpPinnedHeight() {
- HeadsUpEntry topEntry = getTopEntry();
- if (topEntry == null || topEntry.entry == null) {
- return 0;
- }
- ExpandableNotificationRow row = topEntry.entry.row;
- if (row.isChildInGroup()) {
- final ExpandableNotificationRow groupSummary
- = mGroupManager.getGroupSummary(row.getStatusBarNotification());
- if (groupSummary != null) {
- row = groupSummary;
- }
- }
- return row.getPinnedHeadsUpHeight();
+ public boolean isTrackingHeadsUp() {
+ // Might be implemented in subclass.
+ return false;
}
/**
@@ -543,7 +414,7 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
* @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(NotificationData.Entry a, NotificationData.Entry b) {
+ public int compare(@NonNull NotificationData.Entry a, @NonNull NotificationData.Entry b) {
HeadsUpEntry aEntry = getHeadsUpEntry(a.key);
HeadsUpEntry bEntry = getHeadsUpEntry(b.key);
if (aEntry == null || bEntry == null) {
@@ -553,147 +424,62 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
}
/**
- * Set that we are exiting the headsUp pinned mode, but some notifications might still be
- * animating out. This is used to keep the touchable regions in a sane state.
- */
- public void setHeadsUpGoingAway(boolean headsUpGoingAway) {
- if (headsUpGoingAway != mHeadsUpGoingAway) {
- mHeadsUpGoingAway = headsUpGoingAway;
- if (!headsUpGoingAway) {
- waitForStatusBarLayout();
- }
- updateTouchableRegionListener();
- }
- }
-
- /**
- * We need to wait on the whole panel to collapse, before we can remove the touchable region
- * listener.
- */
- private void waitForStatusBarLayout() {
- mWaitingOnCollapseWhenGoingAway = true;
- mStatusBarWindowView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft,
- int oldTop, int oldRight, int oldBottom) {
- if (mStatusBarWindowView.getHeight() <= mStatusBarHeight) {
- mStatusBarWindowView.removeOnLayoutChangeListener(this);
- mWaitingOnCollapseWhenGoingAway = false;
- updateTouchableRegionListener();
- }
- }
- });
- }
-
- public static void setIsClickedNotification(View child, boolean clicked) {
- child.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null);
- }
-
- public static boolean isClickedHeadsUpNotification(View child) {
- Boolean clicked = (Boolean) child.getTag(TAG_CLICKED_NOTIFICATION);
- return clicked != null && clicked;
- }
-
- public void setRemoteInputActive(NotificationData.Entry entry, boolean remoteInputActive) {
- HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key);
- if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) {
- headsUpEntry.remoteInputActive = remoteInputActive;
- if (remoteInputActive) {
- headsUpEntry.removeAutoRemovalCallbacks();
- } else {
- headsUpEntry.updateEntry(false /* updatePostTime */);
- }
- }
- }
-
- /**
* 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(NotificationData.Entry entry, boolean expanded) {
- HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key);
- if (headsUpEntry != null && headsUpEntry.expanded != expanded && entry.row.isPinned()) {
- headsUpEntry.expanded = expanded;
- if (expanded) {
- headsUpEntry.removeAutoRemovalCallbacks();
- } else {
- headsUpEntry.updateEntry(false /* updatePostTime */);
- }
+ public void setExpanded(@NonNull NotificationData.Entry entry, boolean expanded) {
+ HeadsUpManager.HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key);
+ if (headsUpEntry != null && entry.row.isPinned()) {
+ headsUpEntry.expanded(expanded);
}
}
- @Override
- public void onReorderingAllowed() {
- mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(false);
- for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) {
- if (isHeadsUp(entry.key)) {
- // Maybe the heads-up was removed already
- removeHeadsUpEntry(entry);
- }
- }
- mEntriesToRemoveWhenReorderingAllowed.clear();
- mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(true);
+ @NonNull
+ protected HeadsUpEntry createHeadsUpEntry() {
+ return new HeadsUpEntry();
}
- public void setVisualStabilityManager(VisualStabilityManager visualStabilityManager) {
- mVisualStabilityManager = visualStabilityManager;
- }
-
- public void setStatusBarState(int statusBarState) {
- mStatusBarState = statusBarState;
+ protected void releaseHeadsUpEntry(@NonNull HeadsUpEntry entry) {
+ entry.reset();
}
/**
* This represents a notification and how long it is in a heads up mode. It also manages its
* lifecycle automatically when created.
*/
- public class HeadsUpEntry implements Comparable<HeadsUpEntry> {
- public NotificationData.Entry entry;
+ protected class HeadsUpEntry implements Comparable<HeadsUpEntry> {
+ @Nullable public NotificationData.Entry entry;
public long postTime;
- public long earliestRemovaltime;
- private Runnable mRemoveHeadsUpRunnable;
public boolean remoteInputActive;
+ public long earliestRemovaltime;
public boolean expanded;
- public void setEntry(final NotificationData.Entry entry) {
+ @Nullable private Runnable mRemoveHeadsUpRunnable;
+
+ public void setEntry(@Nullable final NotificationData.Entry entry) {
+ setEntry(entry, null);
+ }
+
+ public void setEntry(@Nullable final NotificationData.Entry entry,
+ @Nullable Runnable removeHeadsUpRunnable) {
this.entry = entry;
+ this.mRemoveHeadsUpRunnable = removeHeadsUpRunnable;
// The actual post time will be just after the heads-up really slided in
postTime = mClock.currentTimeMillis() + mTouchAcceptanceDelay;
- mRemoveHeadsUpRunnable = new Runnable() {
- @Override
- public void run() {
- if (!mVisualStabilityManager.isReorderingAllowed()) {
- mEntriesToRemoveWhenReorderingAllowed.add(entry);
- mVisualStabilityManager.addReorderingAllowedCallback(HeadsUpManager.this);
- } else if (!mTrackingHeadsUp) {
- removeHeadsUpEntry(entry);
- } else {
- mEntriesToRemoveAfterExpand.add(entry);
- }
- }
- };
- updateEntry();
- }
-
- public void updateEntry() {
- updateEntry(true);
+ updateEntry(true /* updatePostTime */);
}
public void updateEntry(boolean updatePostTime) {
+ if (DEBUG) Log.v(TAG, "updateEntry");
+
long currentTime = mClock.currentTimeMillis();
earliestRemovaltime = currentTime + mMinimumDisplayTime;
if (updatePostTime) {
postTime = Math.max(postTime, currentTime);
}
removeAutoRemovalCallbacks();
- if (mEntriesToRemoveAfterExpand.contains(entry)) {
- mEntriesToRemoveAfterExpand.remove(entry);
- }
- if (mEntriesToRemoveWhenReorderingAllowed.contains(entry)) {
- mEntriesToRemoveWhenReorderingAllowed.remove(entry);
- }
+
if (!isSticky()) {
long finishTime = postTime + mHeadsUpNotificationDecay;
long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime);
@@ -707,7 +493,7 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
}
@Override
- public int compareTo(HeadsUpEntry o) {
+ public int compareTo(@NonNull HeadsUpEntry o) {
boolean isPinned = entry.row.isPinned();
boolean otherPinned = o.entry.row.isPinned();
if (isPinned && !otherPinned) {
@@ -734,26 +520,29 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
: -1;
}
- public void removeAutoRemovalCallbacks() {
- mHandler.removeCallbacks(mRemoveHeadsUpRunnable);
- }
-
- public boolean wasShownLongEnough() {
- return earliestRemovaltime < mClock.currentTimeMillis();
- }
-
- public void removeAsSoonAsPossible() {
- removeAutoRemovalCallbacks();
- mHandler.postDelayed(mRemoveHeadsUpRunnable,
- earliestRemovaltime - mClock.currentTimeMillis());
+ public void expanded(boolean expanded) {
+ this.expanded = expanded;
}
public void reset() {
- removeAutoRemovalCallbacks();
entry = null;
- mRemoveHeadsUpRunnable = null;
expanded = false;
remoteInputActive = false;
+ removeAutoRemovalCallbacks();
+ mRemoveHeadsUpRunnable = null;
+ }
+
+ public void removeAutoRemovalCallbacks() {
+ if (mRemoveHeadsUpRunnable != null)
+ mHandler.removeCallbacks(mRemoveHeadsUpRunnable);
+ }
+
+ public void removeAsSoonAsPossible() {
+ if (mRemoveHeadsUpRunnable != null) {
+ removeAutoRemovalCallbacks();
+ mHandler.postDelayed(mRemoveHeadsUpRunnable,
+ earliestRemovaltime - mClock.currentTimeMillis());
+ }
}
}
@@ -762,5 +551,4 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
return SystemClock.elapsedRealtime();
}
}
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java
new file mode 100644
index 000000000000..1e3c123cfbc6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java
@@ -0,0 +1,47 @@
+/*
+ * 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 android.view.View;
+
+import com.android.systemui.R;
+
+/**
+ * A class of utility static methods for heads up notifications.
+ */
+public final class HeadsUpUtil {
+ private static final int TAG_CLICKED_NOTIFICATION = R.id.is_clicked_heads_up_tag;
+
+ /**
+ * Set the given view as clicked or not-clicked.
+ * @param view The view to be set the flag to.
+ * @param clicked True to set as clicked. False to not-clicked.
+ */
+ public static void setIsClickedHeadsUpNotification(View view, boolean clicked) {
+ view.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null);
+ }
+
+ /**
+ * Check if the given view has the flag of "clicked notification"
+ * @param view The view to be checked.
+ * @return True if the view has clicked. False othrewise.
+ */
+ public static boolean isClickedHeadsUpNotification(View view) {
+ Boolean clicked = (Boolean) view.getTag(TAG_CLICKED_NOTIFICATION);
+ return clicked != null && clicked;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index 077c6c38c516..98bebec8511e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -27,8 +27,12 @@ import android.media.AudioManager;
import android.metrics.LogMaker;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.TypedValue;
import android.view.HapticFeedbackConstants;
import android.view.InputDevice;
@@ -45,25 +49,32 @@ import android.widget.ImageView;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
+import com.android.systemui.OverviewProxyService;
import com.android.systemui.R;
import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider.ButtonInterface;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
public class KeyButtonView extends ImageView implements ButtonInterface {
+ private static final String TAG = KeyButtonView.class.getSimpleName();
private final boolean mPlaySounds;
private int mContentDescriptionRes;
private long mDownTime;
private int mCode;
private int mTouchSlop;
+ private int mTouchDownX;
+ private int mTouchDownY;
private boolean mSupportsLongpress = true;
private AudioManager mAudioManager;
private boolean mGestureAborted;
private boolean mLongClicked;
private OnClickListener mOnClickListener;
private final KeyButtonRipple mRipple;
+ private final OverviewProxyService mOverviewProxyService;
+ private final Vibrator mVibrator;
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
private final Runnable mCheckLongPress = new Runnable() {
@@ -110,6 +121,8 @@ public class KeyButtonView extends ImageView implements ButtonInterface {
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mRipple = new KeyButtonRipple(context, this);
+ mVibrator = mContext.getSystemService(Vibrator.class);
+ mOverviewProxyService = Dependency.get(OverviewProxyService.class);
setBackground(mRipple);
}
@@ -189,6 +202,7 @@ public class KeyButtonView extends ImageView implements ButtonInterface {
}
public boolean onTouchEvent(MotionEvent ev) {
+ final boolean isProxyConnected = mOverviewProxyService.getProxy() != null;
final int action = ev.getAction();
int x, y;
if (action == MotionEvent.ACTION_DOWN) {
@@ -203,23 +217,34 @@ public class KeyButtonView extends ImageView implements ButtonInterface {
mDownTime = SystemClock.uptimeMillis();
mLongClicked = false;
setPressed(true);
+ mTouchDownX = (int) ev.getX();
+ mTouchDownY = (int) ev.getY();
if (mCode != 0) {
sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime);
} else {
// Provide the same haptic feedback that the system offers for virtual keys.
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
}
- playSoundEffect(SoundEffectConstants.CLICK);
+ if (isProxyConnected) {
+ // Provide small vibration for quick step or immediate down feedback
+ AsyncTask.execute(() ->
+ mVibrator.vibrate(VibrationEffect
+ .get(VibrationEffect.EFFECT_TICK, false)));
+ } else {
+ playSoundEffect(SoundEffectConstants.CLICK);
+ }
removeCallbacks(mCheckLongPress);
postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
break;
case MotionEvent.ACTION_MOVE:
x = (int)ev.getX();
y = (int)ev.getY();
- setPressed(x >= -mTouchSlop
- && x < getWidth() + mTouchSlop
- && y >= -mTouchSlop
- && y < getHeight() + mTouchSlop);
+ boolean exceededTouchSlopX = Math.abs(x - mTouchDownX) > mTouchSlop;
+ boolean exceededTouchSlopY = Math.abs(y - mTouchDownY) > mTouchSlop;
+ if (exceededTouchSlopX || exceededTouchSlopY) {
+ setPressed(false);
+ removeCallbacks(mCheckLongPress);
+ }
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
@@ -231,13 +256,21 @@ public class KeyButtonView extends ImageView implements ButtonInterface {
case MotionEvent.ACTION_UP:
final boolean doIt = isPressed() && !mLongClicked;
setPressed(false);
- // Always send a release ourselves because it doesn't seem to be sent elsewhere
- // and it feels weird to sometimes get a release haptic and other times not.
- if ((SystemClock.uptimeMillis() - mDownTime) > 150 && !mLongClicked) {
+ if (isProxyConnected) {
+ if (doIt) {
+ performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+ playSoundEffect(SoundEffectConstants.CLICK);
+ }
+ } else if ((SystemClock.uptimeMillis() - mDownTime) > 150 && !mLongClicked) {
+ // Always send a release ourselves because it doesn't seem to be sent elsewhere
+ // and it feels weird to sometimes get a release haptic and other times not.
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE);
}
if (mCode != 0) {
if (doIt) {
+ // If there was a pending remote recents animation, then we need to
+ // cancel the animation now before we handle the button itself
+ ActivityManagerWrapper.getInstance().cancelRecentsAnimation();
sendEvent(KeyEvent.ACTION_UP, 0);
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index 46455eb95460..4a3a173bc4ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -36,8 +36,8 @@ import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.cdma.EriInfo;
+import com.android.settingslib.graph.SignalDrawable;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.SignalDrawable;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
import com.android.systemui.statusbar.policy.NetworkControllerImpl.Config;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
new file mode 100644
index 000000000000..c5067a6578eb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
@@ -0,0 +1,95 @@
+/*
+ * 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.statusbar.policy;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.Settings;
+import android.util.KeyValueListParser;
+import android.util.Log;
+
+import com.android.systemui.R;
+
+public final class SmartReplyConstants extends ContentObserver {
+
+ private static final String TAG = "SmartReplyConstants";
+
+ private static final String KEY_ENABLED = "enabled";
+ private static final String KEY_MAX_SQUEEZE_REMEASURE_ATTEMPTS =
+ "max_squeeze_remeasure_attempts";
+
+ private final boolean mDefaultEnabled;
+ private final int mDefaultMaxSqueezeRemeasureAttempts;
+
+ private boolean mEnabled;
+ private int mMaxSqueezeRemeasureAttempts;
+
+ private final Context mContext;
+ private final KeyValueListParser mParser = new KeyValueListParser(',');
+
+ public SmartReplyConstants(Handler handler, Context context) {
+ super(handler);
+
+ mContext = context;
+ final Resources resources = mContext.getResources();
+ mDefaultEnabled = resources.getBoolean(
+ R.bool.config_smart_replies_in_notifications_enabled);
+ mDefaultMaxSqueezeRemeasureAttempts = resources.getInteger(
+ R.integer.config_smart_replies_in_notifications_max_squeeze_remeasure_attempts);
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS),
+ false, this);
+ updateConstants();
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ updateConstants();
+ }
+
+ private void updateConstants() {
+ synchronized (SmartReplyConstants.this) {
+ try {
+ mParser.setString(Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS));
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Bad smart reply constants", e);
+ }
+ mEnabled = mParser.getBoolean(KEY_ENABLED, mDefaultEnabled);
+ mMaxSqueezeRemeasureAttempts = mParser.getInt(
+ KEY_MAX_SQUEEZE_REMEASURE_ATTEMPTS, mDefaultMaxSqueezeRemeasureAttempts);
+ }
+ }
+
+ /** Returns whether smart replies in notifications are enabled. */
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ /**
+ * Returns the maximum number of times {@link SmartReplyView#onMeasure(int, int)} will try to
+ * find a better (narrower) line-break for a double-line smart reply button.
+ */
+ public int getMaxSqueezeRemeasureAttempts() {
+ return mMaxSqueezeRemeasureAttempts;
+ }
+}
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 2d829af9cda7..790135fc03ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -4,23 +4,105 @@ import android.app.PendingIntent;
import android.app.RemoteInput;
import android.content.Context;
import android.content.Intent;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.RippleDrawable;
import android.os.Bundle;
+import android.text.Layout;
+import android.text.TextPaint;
+import android.text.method.TransformationMethod;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
+import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
-import android.widget.LinearLayout;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
+import java.text.BreakIterator;
+import java.util.Comparator;
+import java.util.PriorityQueue;
+
/** View which displays smart reply buttons in notifications. */
-public class SmartReplyView extends LinearLayout {
+public class SmartReplyView extends ViewGroup {
private static final String TAG = "SmartReplyView";
+ private static final int MEASURE_SPEC_ANY_WIDTH =
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+
+ private static final Comparator<View> DECREASING_MEASURED_WIDTH_WITHOUT_PADDING_COMPARATOR =
+ (v1, v2) -> ((v2.getMeasuredWidth() - v2.getPaddingLeft() - v2.getPaddingRight())
+ - (v1.getMeasuredWidth() - v1.getPaddingLeft() - v1.getPaddingRight()));
+
+ private static final int SQUEEZE_FAILED = -1;
+
+ private final SmartReplyConstants mConstants;
+
+ /** Spacing to be applied between views. */
+ private final int mSpacing;
+
+ /** Horizontal padding of smart reply buttons if all of them use only one line of text. */
+ private final int mSingleLineButtonPaddingHorizontal;
+
+ /** Horizontal padding of smart reply buttons if at least one of them uses two lines of text. */
+ private final int mDoubleLineButtonPaddingHorizontal;
+
+ /** Increase in width of a smart reply button as a result of using two lines instead of one. */
+ private final int mSingleToDoubleLineButtonWidthIncrease;
+
+ private final BreakIterator mBreakIterator;
+
+ private PriorityQueue<Button> mCandidateButtonQueueForSqueezing;
+
public SmartReplyView(Context context, AttributeSet attrs) {
super(context, attrs);
+ mConstants = Dependency.get(SmartReplyConstants.class);
+
+ int spacing = 0;
+ int singleLineButtonPaddingHorizontal = 0;
+ int doubleLineButtonPaddingHorizontal = 0;
+
+ final TypedArray arr = context.obtainStyledAttributes(attrs, R.styleable.SmartReplyView,
+ 0, 0);
+ final int length = arr.getIndexCount();
+ for (int i = 0; i < length; i++) {
+ int attr = arr.getIndex(i);
+ switch (attr) {
+ case R.styleable.SmartReplyView_spacing:
+ spacing = arr.getDimensionPixelSize(i, 0);
+ break;
+ case R.styleable.SmartReplyView_singleLineButtonPaddingHorizontal:
+ singleLineButtonPaddingHorizontal = arr.getDimensionPixelSize(i, 0);
+ break;
+ case R.styleable.SmartReplyView_doubleLineButtonPaddingHorizontal:
+ doubleLineButtonPaddingHorizontal = arr.getDimensionPixelSize(i, 0);
+ break;
+ }
+ }
+ arr.recycle();
+
+ mSpacing = spacing;
+ mSingleLineButtonPaddingHorizontal = singleLineButtonPaddingHorizontal;
+ mDoubleLineButtonPaddingHorizontal = doubleLineButtonPaddingHorizontal;
+ mSingleToDoubleLineButtonWidthIncrease =
+ 2 * (doubleLineButtonPaddingHorizontal - singleLineButtonPaddingHorizontal);
+
+ mBreakIterator = BreakIterator.getLineInstance();
+ reallocateCandidateButtonQueueForSqueezing();
+ }
+
+ private void reallocateCandidateButtonQueueForSqueezing() {
+ // Instead of clearing the priority queue, we re-allocate so that it would fit all buttons
+ // exactly. This avoids (1) wasting memory because PriorityQueue never shrinks and
+ // (2) growing in onMeasure.
+ // The constructor throws an IllegalArgument exception if initial capacity is less than 1.
+ mCandidateButtonQueueForSqueezing = new PriorityQueue<>(
+ Math.max(getChildCount(), 1), DECREASING_MEASURED_WIDTH_WITHOUT_PADDING_COMPARATOR);
}
public void setRepliesFromRemoteInput(RemoteInput remoteInput, PendingIntent pendingIntent) {
@@ -35,6 +117,7 @@ public class SmartReplyView extends LinearLayout {
}
}
}
+ reallocateCandidateButtonQueueForSqueezing();
}
public static SmartReplyView inflate(Context context, ViewGroup root) {
@@ -42,7 +125,8 @@ public class SmartReplyView extends LinearLayout {
LayoutInflater.from(context).inflate(R.layout.smart_reply_view, root, false);
}
- private static Button inflateReplyButton(Context context, ViewGroup root, CharSequence choice,
+ @VisibleForTesting
+ static Button inflateReplyButton(Context context, ViewGroup root, CharSequence choice,
RemoteInput remoteInput, PendingIntent pendingIntent) {
Button b = (Button) LayoutInflater.from(context).inflate(
R.layout.smart_reply_button, root, false);
@@ -61,4 +145,376 @@ public class SmartReplyView extends LinearLayout {
});
return b;
}
+
+ @Override
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new LayoutParams(mContext, attrs);
+ }
+
+ @Override
+ protected LayoutParams generateDefaultLayoutParams() {
+ return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ }
+
+ @Override
+ protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams params) {
+ return new LayoutParams(params.width, params.height);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int targetWidth = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.UNSPECIFIED
+ ? Integer.MAX_VALUE : MeasureSpec.getSize(widthMeasureSpec);
+
+ // Mark all buttons as hidden and un-squeezed.
+ resetButtonsLayoutParams();
+
+ if (!mCandidateButtonQueueForSqueezing.isEmpty()) {
+ Log.wtf(TAG, "Single line button queue leaked between onMeasure calls");
+ mCandidateButtonQueueForSqueezing.clear();
+ }
+
+ int measuredWidth = mPaddingLeft + mPaddingRight;
+ int maxChildHeight = 0;
+ int displayedChildCount = 0;
+ int buttonPaddingHorizontal = mSingleLineButtonPaddingHorizontal;
+
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (child.getVisibility() != View.VISIBLE || !(child instanceof Button)) {
+ continue;
+ }
+
+ child.setPadding(buttonPaddingHorizontal, child.getPaddingTop(),
+ buttonPaddingHorizontal, child.getPaddingBottom());
+ child.measure(MEASURE_SPEC_ANY_WIDTH, heightMeasureSpec);
+
+ final int lineCount = ((Button) child).getLineCount();
+ if (lineCount < 1 || lineCount > 2) {
+ // If smart reply has no text, or more than two lines, then don't show it.
+ continue;
+ }
+
+ if (lineCount == 1) {
+ mCandidateButtonQueueForSqueezing.add((Button) child);
+ }
+
+ // Remember the current measurements in case the current button doesn't fit in.
+ final int originalMaxChildHeight = maxChildHeight;
+ final int originalMeasuredWidth = measuredWidth;
+ final int originalButtonPaddingHorizontal = buttonPaddingHorizontal;
+
+ final int spacing = displayedChildCount == 0 ? 0 : mSpacing;
+ final int childWidth = child.getMeasuredWidth();
+ final int childHeight = child.getMeasuredHeight();
+ measuredWidth += spacing + childWidth;
+ maxChildHeight = Math.max(maxChildHeight, childHeight);
+
+ // Do we need to increase the number of lines in smart reply buttons to two?
+ final boolean increaseToTwoLines =
+ buttonPaddingHorizontal == mSingleLineButtonPaddingHorizontal
+ && (lineCount == 2 || measuredWidth > targetWidth);
+ if (increaseToTwoLines) {
+ measuredWidth += (displayedChildCount + 1) * mSingleToDoubleLineButtonWidthIncrease;
+ buttonPaddingHorizontal = mDoubleLineButtonPaddingHorizontal;
+ }
+
+ // If the last button doesn't fit into the remaining width, try squeezing preceding
+ // smart reply buttons.
+ if (measuredWidth > targetWidth) {
+ // Keep squeezing preceding and current smart reply buttons until they all fit.
+ while (measuredWidth > targetWidth
+ && !mCandidateButtonQueueForSqueezing.isEmpty()) {
+ final Button candidate = mCandidateButtonQueueForSqueezing.poll();
+ final int squeezeReduction = squeezeButton(candidate, heightMeasureSpec);
+ if (squeezeReduction != SQUEEZE_FAILED) {
+ maxChildHeight = Math.max(maxChildHeight, candidate.getMeasuredHeight());
+ measuredWidth -= squeezeReduction;
+ }
+ }
+
+ // If the current button still doesn't fit after squeezing all buttons, undo the
+ // last squeezing round.
+ if (measuredWidth > targetWidth) {
+ measuredWidth = originalMeasuredWidth;
+ maxChildHeight = originalMaxChildHeight;
+ buttonPaddingHorizontal = originalButtonPaddingHorizontal;
+
+ // Mark all buttons from the last squeezing round as "failed to squeeze", so
+ // that they're re-measured without squeezing later.
+ markButtonsWithPendingSqueezeStatusAs(LayoutParams.SQUEEZE_STATUS_FAILED, i);
+
+ // The current button doesn't fit, so there's no point in measuring further
+ // buttons.
+ break;
+ }
+
+ // The current button fits, so mark all squeezed buttons as "successfully squeezed"
+ // to prevent them from being un-squeezed in a subsequent squeezing round.
+ markButtonsWithPendingSqueezeStatusAs(LayoutParams.SQUEEZE_STATUS_SUCCESSFUL, i);
+ }
+
+ lp.show = true;
+ displayedChildCount++;
+ }
+
+ // We're done squeezing buttons, so we can clear the priority queue.
+ mCandidateButtonQueueForSqueezing.clear();
+
+ // Finally, we need to update corner radius and re-measure some buttons.
+ updateCornerRadiusAndRemeasureButtonsIfNecessary(buttonPaddingHorizontal, maxChildHeight);
+
+ setMeasuredDimension(
+ resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth), widthMeasureSpec),
+ resolveSize(Math.max(getSuggestedMinimumHeight(),
+ mPaddingTop + maxChildHeight + mPaddingBottom), heightMeasureSpec));
+ }
+
+ private void resetButtonsLayoutParams() {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ lp.show = false;
+ lp.squeezeStatus = LayoutParams.SQUEEZE_STATUS_NONE;
+ }
+ }
+
+ private int squeezeButton(Button button, int heightMeasureSpec) {
+ final int estimatedOptimalTextWidth = estimateOptimalSqueezedButtonTextWidth(button);
+ if (estimatedOptimalTextWidth == SQUEEZE_FAILED) {
+ return SQUEEZE_FAILED;
+ }
+ return squeezeButtonToTextWidth(button, heightMeasureSpec, estimatedOptimalTextWidth);
+ }
+
+ private int estimateOptimalSqueezedButtonTextWidth(Button button) {
+ // Find a line-break point in the middle of the smart reply button text.
+ final String rawText = button.getText().toString();
+
+ // The button sometimes has a transformation affecting text layout (e.g. all caps).
+ final TransformationMethod transformation = button.getTransformationMethod();
+ final String text = transformation == null ?
+ rawText : transformation.getTransformation(rawText, button).toString();
+ final int length = text.length();
+ mBreakIterator.setText(text);
+
+ if (mBreakIterator.preceding(length / 2) == BreakIterator.DONE) {
+ if (mBreakIterator.next() == BreakIterator.DONE) {
+ // Can't find a single possible line break in either direction.
+ return SQUEEZE_FAILED;
+ }
+ }
+
+ final TextPaint paint = button.getPaint();
+ final int initialPosition = mBreakIterator.current();
+ final float initialLeftTextWidth = Layout.getDesiredWidth(text, 0, initialPosition, paint);
+ final float initialRightTextWidth =
+ Layout.getDesiredWidth(text, initialPosition, length, paint);
+ float optimalTextWidth = Math.max(initialLeftTextWidth, initialRightTextWidth);
+
+ if (initialLeftTextWidth != initialRightTextWidth) {
+ // See if there's a better line-break point (leading to a more narrow button) in
+ // either left or right direction.
+ final boolean moveLeft = initialLeftTextWidth > initialRightTextWidth;
+ final int maxSqueezeRemeasureAttempts = mConstants.getMaxSqueezeRemeasureAttempts();
+ for (int i = 0; i < maxSqueezeRemeasureAttempts; i++) {
+ final int newPosition =
+ moveLeft ? mBreakIterator.previous() : mBreakIterator.next();
+ if (newPosition == BreakIterator.DONE) {
+ break;
+ }
+
+ final float newLeftTextWidth = Layout.getDesiredWidth(text, 0, newPosition, paint);
+ final float newRightTextWidth =
+ Layout.getDesiredWidth(text, newPosition, length, paint);
+ final float newOptimalTextWidth = Math.max(newLeftTextWidth, newRightTextWidth);
+ if (newOptimalTextWidth < optimalTextWidth) {
+ optimalTextWidth = newOptimalTextWidth;
+ } else {
+ break;
+ }
+
+ boolean tooFar = moveLeft
+ ? newLeftTextWidth <= newRightTextWidth
+ : newLeftTextWidth >= newRightTextWidth;
+ if (tooFar) {
+ break;
+ }
+ }
+ }
+
+ return (int) Math.ceil(optimalTextWidth);
+ }
+
+ private int squeezeButtonToTextWidth(Button button, int heightMeasureSpec, int textWidth) {
+ int oldWidth = button.getMeasuredWidth();
+ if (button.getPaddingLeft() != mDoubleLineButtonPaddingHorizontal) {
+ // Correct for the fact that the button was laid out with single-line horizontal
+ // padding.
+ oldWidth += mSingleToDoubleLineButtonWidthIncrease;
+ }
+
+ // Re-measure the squeezed smart reply button.
+ button.setPadding(mDoubleLineButtonPaddingHorizontal, button.getPaddingTop(),
+ mDoubleLineButtonPaddingHorizontal, button.getPaddingBottom());
+ final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(
+ 2 * mDoubleLineButtonPaddingHorizontal + textWidth, MeasureSpec.AT_MOST);
+ button.measure(widthMeasureSpec, heightMeasureSpec);
+
+ final int newWidth = button.getMeasuredWidth();
+
+ final LayoutParams lp = (LayoutParams) button.getLayoutParams();
+ if (button.getLineCount() > 2 || newWidth >= oldWidth) {
+ lp.squeezeStatus = LayoutParams.SQUEEZE_STATUS_FAILED;
+ return SQUEEZE_FAILED;
+ } else {
+ lp.squeezeStatus = LayoutParams.SQUEEZE_STATUS_PENDING;
+ return oldWidth - newWidth;
+ }
+ }
+
+ private void updateCornerRadiusAndRemeasureButtonsIfNecessary(
+ int buttonPaddingHorizontal, int maxChildHeight) {
+ final float cornerRadius = ((float) maxChildHeight) / 2;
+ final int maxChildHeightMeasure =
+ MeasureSpec.makeMeasureSpec(maxChildHeight, MeasureSpec.EXACTLY);
+
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (!lp.show) {
+ continue;
+ }
+
+ // Update corner radius.
+ GradientDrawable backgroundDrawable =
+ (GradientDrawable) ((RippleDrawable) child.getBackground()).getDrawable(0);
+ backgroundDrawable.setCornerRadius(cornerRadius);
+
+ boolean requiresNewMeasure = false;
+ int newWidth = child.getMeasuredWidth();
+
+ // Re-measure reason 1: The button needs to be un-squeezed (either because it resulted
+ // in more than two lines or because it was unnecessary).
+ if (lp.squeezeStatus == LayoutParams.SQUEEZE_STATUS_FAILED) {
+ requiresNewMeasure = true;
+ newWidth = Integer.MAX_VALUE;
+ }
+
+ // Re-measure reason 2: The button's horizontal padding is incorrect (because it was
+ // measured with the wrong number of lines).
+ if (child.getPaddingLeft() != buttonPaddingHorizontal) {
+ requiresNewMeasure = true;
+ if (buttonPaddingHorizontal == mSingleLineButtonPaddingHorizontal) {
+ // Decrease padding (2->1 line).
+ newWidth -= mSingleToDoubleLineButtonWidthIncrease;
+ } else {
+ // Increase padding (1->2 lines).
+ newWidth += mSingleToDoubleLineButtonWidthIncrease;
+ }
+ child.setPadding(buttonPaddingHorizontal, child.getPaddingTop(),
+ buttonPaddingHorizontal, child.getPaddingBottom());
+ }
+
+ // Re-measure reason 3: The button's height is less than the max height of all buttons
+ // (all should have the same height).
+ if (child.getMeasuredHeight() != maxChildHeight) {
+ requiresNewMeasure = true;
+ }
+
+ if (requiresNewMeasure) {
+ child.measure(MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.AT_MOST),
+ maxChildHeightMeasure);
+ }
+ }
+ }
+
+ private void markButtonsWithPendingSqueezeStatusAs(int squeezeStatus, int maxChildIndex) {
+ for (int i = 0; i <= maxChildIndex; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (lp.squeezeStatus == LayoutParams.SQUEEZE_STATUS_PENDING) {
+ lp.squeezeStatus = squeezeStatus;
+ }
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ final boolean isRtl = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+
+ final int width = right - left;
+ int position = isRtl ? width - mPaddingRight : mPaddingLeft;
+
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (!lp.show) {
+ continue;
+ }
+
+ final int childWidth = child.getMeasuredWidth();
+ final int childHeight = child.getMeasuredHeight();
+ final int childLeft = isRtl ? position - childWidth : position;
+ child.layout(childLeft, 0, childLeft + childWidth, childHeight);
+
+ final int childWidthWithSpacing = childWidth + mSpacing;
+ if (isRtl) {
+ position -= childWidthWithSpacing;
+ } else {
+ position += childWidthWithSpacing;
+ }
+ }
+ }
+
+ @Override
+ protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ return lp.show && super.drawChild(canvas, child, drawingTime);
+ }
+
+ @VisibleForTesting
+ static class LayoutParams extends ViewGroup.LayoutParams {
+
+ /** Button is not squeezed. */
+ private static final int SQUEEZE_STATUS_NONE = 0;
+
+ /**
+ * Button was successfully squeezed, but it might be un-squeezed later if the squeezing
+ * turns out to have been unnecessary (because there's still not enough space to add another
+ * button).
+ */
+ private static final int SQUEEZE_STATUS_PENDING = 1;
+
+ /** Button was successfully squeezed and it won't be un-squeezed. */
+ private static final int SQUEEZE_STATUS_SUCCESSFUL = 2;
+
+ /**
+ * Button wasn't successfully squeezed. The squeezing resulted in more than two lines of
+ * text or it didn't reduce the button's width at all. The button will have to be
+ * re-measured to use only one line of text.
+ */
+ private static final int SQUEEZE_STATUS_FAILED = 3;
+
+ private boolean show = false;
+ private int squeezeStatus = SQUEEZE_STATUS_NONE;
+
+ private LayoutParams(Context c, AttributeSet attrs) {
+ super(c, attrs);
+ }
+
+ private LayoutParams(int width, int height) {
+ super(width, height);
+ }
+
+ @VisibleForTesting
+ boolean isShown() {
+ return show;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index 424858a86e58..d7a810eca02e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -64,7 +64,7 @@ public class AmbientState {
private boolean mPanelTracking;
private boolean mExpansionChanging;
private boolean mPanelFullWidth;
- private Collection<HeadsUpManager.HeadsUpEntry> mPulsing;
+ private boolean mPulsing;
private boolean mUnlockHintRunning;
private boolean mQsCustomizerShowing;
private int mIntrinsicPadding;
@@ -315,23 +315,18 @@ public class AmbientState {
}
public boolean hasPulsingNotifications() {
- return mPulsing != null;
+ return mPulsing;
}
- public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> hasPulsing) {
+ public void setPulsing(boolean hasPulsing) {
mPulsing = hasPulsing;
}
public boolean isPulsing(NotificationData.Entry entry) {
- if (mPulsing == null) {
+ if (!mPulsing || mHeadsUpManager == null) {
return false;
}
- for (HeadsUpManager.HeadsUpEntry e : mPulsing) {
- if (e.entry == entry) {
- return true;
- }
- }
- return false;
+ return mHeadsUpManager.getAllEntries().anyMatch(e -> (e == entry));
}
public boolean isPanelTracking() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index c114a6f5a6d9..1b55a5b0325f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -92,10 +92,11 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.FakeShadowView;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.ScrollAdapter;
import android.support.v4.graphics.ColorUtils;
@@ -288,7 +289,7 @@ public class NotificationStackScrollLayout extends ViewGroup
private HashSet<View> mClearOverlayViewsWhenFinished = new HashSet<>();
private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
= new HashSet<>();
- private HeadsUpManager mHeadsUpManager;
+ private HeadsUpManagerPhone mHeadsUpManager;
private boolean mTrackingHeadsUp;
private ScrimController mScrimController;
private boolean mForceNoOverlappingRendering;
@@ -358,7 +359,7 @@ public class NotificationStackScrollLayout extends ViewGroup
}
};
private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
- private Collection<HeadsUpManager.HeadsUpEntry> mPulsing;
+ private boolean mPulsing;
private boolean mDrawBackgroundAsSrc;
private boolean mFadingOut;
private boolean mParentNotFullyVisible;
@@ -690,7 +691,7 @@ public class NotificationStackScrollLayout extends ViewGroup
}
private void updateAlgorithmHeightAndPadding() {
- if (mPulsing != null) {
+ if (mPulsing) {
mTopPadding = mClockBottom;
} else {
mTopPadding = mAmbientState.isDark() ? mDarkTopPadding : mRegularTopPadding;
@@ -920,6 +921,27 @@ public class NotificationStackScrollLayout extends ViewGroup
}
/**
+ * @return the height of the top heads up notification when pinned. This is different from the
+ * intrinsic height, which also includes whether the notification is system expanded and
+ * is mainly used when dragging down from a heads up notification.
+ */
+ private int getTopHeadsUpPinnedHeight() {
+ NotificationData.Entry topEntry = mHeadsUpManager.getTopEntry();
+ if (topEntry == null) {
+ return 0;
+ }
+ ExpandableNotificationRow row = topEntry.row;
+ if (row.isChildInGroup()) {
+ final ExpandableNotificationRow groupSummary
+ = mGroupManager.getGroupSummary(row.getStatusBarNotification());
+ if (groupSummary != null) {
+ row = groupSummary;
+ }
+ }
+ return row.getPinnedHeadsUpHeight();
+ }
+
+ /**
* @return the position from where the appear transition ends when expanding.
* Measured in absolute height.
*/
@@ -930,7 +952,7 @@ public class NotificationStackScrollLayout extends ViewGroup
int minNotificationsForShelf = 1;
if (mTrackingHeadsUp
|| (mHeadsUpManager.hasPinnedHeadsUp() && !mAmbientState.isDark())) {
- appearPosition = mHeadsUpManager.getTopHeadsUpPinnedHeight();
+ appearPosition = getTopHeadsUpPinnedHeight();
minNotificationsForShelf = 2;
} else {
appearPosition = 0;
@@ -1198,9 +1220,9 @@ public class NotificationStackScrollLayout extends ViewGroup
if (slidingChild instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
- && mHeadsUpManager.getTopEntry().entry.row != row
+ && mHeadsUpManager.getTopEntry().row != row
&& mGroupManager.getGroupSummary(
- mHeadsUpManager.getTopEntry().entry.row.getStatusBarNotification())
+ mHeadsUpManager.getTopEntry().row.getStatusBarNotification())
!= row) {
continue;
}
@@ -2120,7 +2142,7 @@ public class NotificationStackScrollLayout extends ViewGroup
@Override
public boolean hasPulsingNotifications() {
- return mPulsing != null;
+ return mPulsing;
}
private void updateScrollability() {
@@ -2753,7 +2775,7 @@ public class NotificationStackScrollLayout extends ViewGroup
}
private boolean isClickedHeadsUp(View child) {
- return HeadsUpManager.isClickedHeadsUpNotification(child);
+ return HeadsUpUtil.isClickedHeadsUpNotification(child);
}
/**
@@ -4258,7 +4280,7 @@ public class NotificationStackScrollLayout extends ViewGroup
mAnimationFinishedRunnables.add(runnable);
}
- public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
mHeadsUpManager = headsUpManager;
mAmbientState.setHeadsUpManager(headsUpManager);
}
@@ -4326,8 +4348,8 @@ public class NotificationStackScrollLayout extends ViewGroup
return mIsExpanded;
}
- public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing, int clockBottom) {
- if (mPulsing == null && pulsing == null) {
+ public void setPulsing(boolean pulsing, int clockBottom) {
+ if (!mPulsing && !pulsing) {
return;
}
mPulsing = pulsing;
@@ -4466,7 +4488,7 @@ public class NotificationStackScrollLayout extends ViewGroup
pw.println(String.format("[%s: pulsing=%s qsCustomizerShowing=%s visibility=%s"
+ " alpha:%f scrollY:%d]",
this.getClass().getSimpleName(),
- mPulsing != null ?"T":"f",
+ mPulsing ? "T":"f",
mAmbientState.isQsCustomizerShowing() ? "T":"f",
getVisibility() == View.VISIBLE ? "visible"
: getVisibility() == View.GONE ? "gone"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
index 682b8493e913..04a7bd79c6ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
@@ -30,7 +30,7 @@ import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.HeadsUpUtil;
/**
* A state of a view. This can be used to apply a set of view properties to a view with
@@ -582,7 +582,7 @@ public class ViewState {
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- HeadsUpManager.setIsClickedNotification(child, false);
+ HeadsUpUtil.setIsClickedHeadsUpNotification(child, false);
child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
child.setTag(TAG_START_TRANSLATION_Y, null);
child.setTag(TAG_END_TRANSLATION_Y, null);
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
index 45abd456ea16..eb0c89b06528 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
@@ -18,7 +18,7 @@ import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_CODE_END;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_CODE_START;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_IMAGE_DELIM;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.MENU_IME;
+import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.MENU_IME_ROTATE;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAVSPACE;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_LEFT;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_RIGHT;
@@ -29,24 +29,14 @@ import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.ext
import android.annotation.Nullable;
import android.app.AlertDialog;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
-import android.content.res.Resources;
import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Paint.FontMetricsInt;
-import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.Handler;
-import android.support.v14.preference.PreferenceFragment;
-import android.support.v7.preference.DropDownPreference;
import android.support.v7.preference.ListPreference;
import android.support.v7.preference.Preference;
import android.support.v7.preference.Preference.OnPreferenceChangeListener;
-import android.support.v7.preference.Preference.OnPreferenceClickListener;
-import android.support.v7.preference.PreferenceCategory;
import android.text.SpannableStringBuilder;
import android.text.style.ImageSpan;
import android.util.Log;
@@ -56,7 +46,6 @@ import android.widget.EditText;
import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.NavigationBarInflaterView;
import com.android.systemui.tuner.TunerService.Tunable;
import java.util.ArrayList;
@@ -100,7 +89,7 @@ public class NavBarTuner extends TunerPreferenceFragment {
addPreferencesFromResource(R.xml.nav_bar_tuner);
bindLayout((ListPreference) findPreference(LAYOUT));
bindButton(NAV_BAR_LEFT, NAVSPACE, LEFT);
- bindButton(NAV_BAR_RIGHT, MENU_IME, RIGHT);
+ bindButton(NAV_BAR_RIGHT, MENU_IME_ROTATE, RIGHT);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
index 5c888ac89c15..6ed07f8d2c37 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
@@ -49,6 +49,8 @@ import android.util.Pair;
import android.view.Window;
import android.view.WindowManager;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
import com.android.settingslib.Utils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.systemui.Dependency;
@@ -81,6 +83,7 @@ public class OutputChooserDialog extends Dialog
private final MediaRouterWrapper mRouter;
private final MediaRouterCallback mRouterCallback;
private long mLastUpdateTime;
+ static final boolean INCLUDE_MEDIA_ROUTES = false;
private boolean mIsInCall;
protected boolean isAttached;
@@ -174,7 +177,7 @@ public class OutputChooserDialog extends Dialog
public void onAttachedToWindow() {
super.onAttachedToWindow();
- if (!mIsInCall) {
+ if (!mIsInCall && INCLUDE_MEDIA_ROUTES) {
mRouter.addCallback(mRouteSelector, mRouterCallback,
MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
}
@@ -201,6 +204,7 @@ public class OutputChooserDialog extends Dialog
@Override
public void show() {
super.show();
+ Dependency.get(MetricsLogger.class).visible(MetricsProto.MetricsEvent.OUTPUT_CHOOSER);
mHardwareLayout.setTranslationX(getAnimTranslation());
mHardwareLayout.setAlpha(0);
mHardwareLayout.animate()
@@ -214,6 +218,7 @@ public class OutputChooserDialog extends Dialog
@Override
public void dismiss() {
+ Dependency.get(MetricsLogger.class).hidden(MetricsProto.MetricsEvent.OUTPUT_CHOOSER);
mHardwareLayout.setTranslationX(0);
mHardwareLayout.setAlpha(1);
mHardwareLayout.animate()
@@ -236,11 +241,15 @@ public class OutputChooserDialog extends Dialog
if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_BT) {
final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
if (device.getMaxConnectionState() == BluetoothProfile.STATE_DISCONNECTED) {
+ Dependency.get(MetricsLogger.class).action(
+ MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_CONNECT);
mBluetoothController.connect(device);
}
} else if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER) {
final MediaRouter.RouteInfo route = (MediaRouter.RouteInfo) item.tag;
if (route.isEnabled()) {
+ Dependency.get(MetricsLogger.class).action(
+ MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_CONNECT);
route.select();
}
}
@@ -251,8 +260,12 @@ public class OutputChooserDialog extends Dialog
if (item == null || item.tag == null) return;
if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_BT) {
final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
+ Dependency.get(MetricsLogger.class).action(
+ MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_DISCONNECT);
mBluetoothController.disconnect(device);
} else if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER) {
+ Dependency.get(MetricsLogger.class).action(
+ MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_DISCONNECT);
mRouter.unselect(UNSELECT_REASON_DISCONNECTED);
}
}
@@ -272,7 +285,7 @@ public class OutputChooserDialog extends Dialog
addBluetoothDevices(items);
// Add remote displays
- if (!mIsInCall) {
+ if (!mIsInCall && INCLUDE_MEDIA_ROUTES) {
addRemoteDisplayRoutes(items);
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
index efa8386802e2..0203c43d3683 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -27,16 +27,15 @@ import android.os.Handler;
import android.view.WindowManager.LayoutParams;
import com.android.settingslib.applications.InterestingConfigChanges;
-import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.Dependency;
import com.android.systemui.SystemUI;
import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.PluginDependencyProvider;
import com.android.systemui.plugins.VolumeDialog;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.statusbar.policy.ExtensionController;
-import com.android.systemui.statusbar.policy.ExtensionController.Extension;
import com.android.systemui.tuner.TunerService;
import java.io.FileDescriptor;
@@ -53,8 +52,8 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna
public static final String VOLUME_SILENT_DO_NOT_DISTURB = "sysui_do_not_disturb";
public static final boolean DEFAULT_VOLUME_DOWN_TO_ENTER_SILENT = false;
- public static final boolean DEFAULT_VOLUME_UP_TO_EXIT_SILENT = true;
- public static final boolean DEFAULT_DO_NOT_DISTURB_WHEN_SILENT = true;
+ public static final boolean DEFAULT_VOLUME_UP_TO_EXIT_SILENT = false;
+ public static final boolean DEFAULT_DO_NOT_DISTURB_WHEN_SILENT = false;
private final SystemUI mSysui;
private final Context mContext;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index a131a618512f..1e8e98ca2e42 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -37,6 +37,7 @@ import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Color;
+import android.graphics.PixelFormat;
import android.graphics.drawable.ColorDrawable;
import android.media.AudioManager;
import android.media.AudioSystem;
@@ -54,6 +55,7 @@ import android.util.Log;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.view.ContextThemeWrapper;
+import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.AccessibilityDelegate;
@@ -66,6 +68,7 @@ import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
import android.view.animation.DecelerateInterpolator;
import android.widget.ImageButton;
+import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
@@ -107,7 +110,9 @@ public class VolumeDialogImpl implements VolumeDialog {
private ViewGroup mDialogRowsView;
private ViewGroup mFooter;
private ImageButton mRingerIcon;
+ private ImageView mZenIcon;
private TextView mRingerStatus;
+ private TextView mRingerTitle;
private final List<VolumeRow> mRows = new ArrayList<>();
private ConfigurableTexts mConfigurableTexts;
private final SparseBooleanArray mDynamic = new SparseBooleanArray();
@@ -173,10 +178,17 @@ public class VolumeDialogImpl implements VolumeDialog {
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
- mWindow.setTitle(VolumeDialogImpl.class.getSimpleName());
mWindow.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
mWindow.setWindowAnimations(com.android.internal.R.style.Animation_Toast);
-
+ final WindowManager.LayoutParams lp = mWindow.getAttributes();
+ lp.format = PixelFormat.TRANSLUCENT;
+ lp.setTitle(VolumeDialogImpl.class.getSimpleName());
+ lp.gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL;
+ lp.windowAnimations = -1;
+ mWindow.setAttributes(lp);
+ mWindow.setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+
+ mDialog.setCanceledOnTouchOutside(true);
mDialog.setContentView(R.layout.volume_dialog);
mDialog.setOnShowListener(dialog -> {
mDialogView.setTranslationX(mDialogView.getWidth() / 2);
@@ -199,13 +211,15 @@ public class VolumeDialogImpl implements VolumeDialog {
rescheduleTimeoutH();
return true;
});
- VolumeUiLayout hardwareLayout = VolumeUiLayout.get(mDialogView);
- hardwareLayout.setOutsideTouchListener(view -> dismiss(DISMISS_REASON_TOUCH_OUTSIDE));
+ VolumeUiLayout uiLayout = VolumeUiLayout.get(mDialogView);
+ uiLayout.updateRotation();
mDialogRowsView = mDialog.findViewById(R.id.volume_dialog_rows);
mFooter = mDialog.findViewById(R.id.footer);
mRingerIcon = mFooter.findViewById(R.id.ringer_icon);
mRingerStatus = mFooter.findViewById(R.id.ringer_status);
+ mRingerTitle = mFooter.findViewById(R.id.ringer_title);
+ mZenIcon = mFooter.findViewById(R.id.dnd_icon);
if (mRows.isEmpty()) {
addRow(AudioManager.STREAM_MUSIC,
@@ -339,6 +353,7 @@ public class VolumeDialogImpl implements VolumeDialog {
if (stream == STREAM_ACCESSIBILITY) {
row.header.setFilters(new InputFilter[] {new InputFilter.LengthFilter(13)});
}
+ row.dndIcon = row.view.findViewById(R.id.dnd_icon);
row.slider = row.view.findViewById(R.id.volume_row_slider);
row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
row.anim = null;
@@ -394,15 +409,12 @@ public class VolumeDialogImpl implements VolumeDialog {
final boolean hasVibrator = mController.hasVibrator();
if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) {
if (hasVibrator) {
+ mController.vibrate();
mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false);
} else {
- final boolean wasZero = ss.level == 0;
- mController.setStreamVolume(AudioManager.STREAM_RING, wasZero ? 1 : 0);
mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false);
}
} else if (mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) {
- final boolean wasZero = ss.level == 0;
- mController.setStreamVolume(AudioManager.STREAM_RING, wasZero ? 1 : 0);
mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false);
} else {
mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
@@ -479,15 +491,6 @@ public class VolumeDialogImpl implements VolumeDialog {
}, 50))
.start();
- if (mAccessibilityMgr.isEnabled()) {
- AccessibilityEvent event =
- AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
- event.setPackageName(mContext.getPackageName());
- event.setClassName(CustomDialog.class.getSuperclass().getName());
- event.getText().add(mContext.getString(
- R.string.volume_dialog_accessibility_dismissed_message));
- mAccessibilityMgr.sendAccessibilityEvent(event);
- }
Events.writeEvent(mContext, Events.EVENT_DISMISS_DIALOG, reason);
mController.notifyVisible(false);
synchronized (mSafetyWarningLock) {
@@ -553,6 +556,8 @@ public class VolumeDialogImpl implements VolumeDialog {
if (ss == null) {
return;
}
+
+ enableRingerViewsH(mState.zenMode == Global.ZEN_MODE_OFF || !mState.disallowRinger);
switch (mState.ringerModeInternal) {
case AudioManager.RINGER_MODE_VIBRATE:
mRingerStatus.setText(R.string.volume_ringer_status_vibrate);
@@ -597,6 +602,28 @@ public class VolumeDialogImpl implements VolumeDialog {
}
}
+ /**
+ * Toggles enable state of views in a VolumeRow (not including seekbar, outputChooser or icon)
+ * Hides/shows zen icon
+ * @param enable whether to enable volume row views and hide dnd icon
+ */
+ private void enableVolumeRowViewsH(VolumeRow row, boolean enable) {
+ row.header.setEnabled(enable);
+ row.dndIcon.setVisibility(enable ? View.GONE : View.VISIBLE);
+ }
+
+ /**
+ * Toggles enable state of footer/ringer views
+ * Hides/shows zen icon
+ * @param enable whether to enable ringer views and hide dnd icon
+ */
+ private void enableRingerViewsH(boolean enable) {
+ mRingerTitle.setEnabled(enable);
+ mRingerStatus.setEnabled(enable);
+ mRingerIcon.setEnabled(enable);
+ mZenIcon.setVisibility(enable ? View.GONE : View.VISIBLE);
+ }
+
private void trimObsoleteH() {
if (D.BUG) Log.d(TAG, "trimObsoleteH");
for (int i = mRows.size() - 1; i >= 0; i--) {
@@ -634,6 +661,8 @@ public class VolumeDialogImpl implements VolumeDialog {
updateVolumeRowH(row);
}
updateRingerH();
+ mWindow.setTitle(mContext.getString(R.string.volume_dialog_title,
+ getStreamLabelH(getActiveRow().ss)));
}
private void updateVolumeRowH(VolumeRow row) {
@@ -742,6 +771,7 @@ public class VolumeDialogImpl implements VolumeDialog {
if (zenMuted) {
row.tracking = false;
}
+ enableVolumeRowViewsH(row, !zenMuted);
// update slider
final boolean enableSlider = !zenMuted;
@@ -1019,20 +1049,10 @@ public class VolumeDialogImpl implements VolumeDialog {
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(@NonNull AccessibilityEvent event) {
- event.setClassName(getClass().getSuperclass().getName());
- event.setPackageName(mContext.getPackageName());
-
- ViewGroup.LayoutParams params = getWindow().getAttributes();
- boolean isFullScreen = (params.width == ViewGroup.LayoutParams.MATCH_PARENT) &&
- (params.height == ViewGroup.LayoutParams.MATCH_PARENT);
- event.setFullScreen(isFullScreen);
-
- if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
- if (mShowing) {
- event.getText().add(mContext.getString(
- R.string.volume_dialog_accessibility_shown_message,
- getStreamLabelH(getActiveRow().ss)));
+ public boolean onTouchEvent(MotionEvent event) {
+ if (isShowing()) {
+ if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
+ dismissH(Events.DISMISS_REASON_TOUCH_OUTSIDE);
return true;
}
}
@@ -1166,5 +1186,6 @@ public class VolumeDialogImpl implements VolumeDialog {
private int lastAudibleLevel = 1;
private View outputChooser;
private TextView connectedDevice;
+ private ImageView dndIcon;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java
index 3d4438148c39..0a3a2ab7c61f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java
@@ -37,10 +37,6 @@ import com.android.systemui.util.leak.RotationUtils;
public class VolumeUiLayout extends FrameLayout {
private View mChild;
- private int mOldHeight;
- private boolean mAnimating;
- private AnimatorSet mAnimation;
- private boolean mHasOutsideTouch;
private int mRotation = ROTATION_NONE;
@Nullable
private DisplayCutout mDisplayCutout;
@@ -52,13 +48,11 @@ public class VolumeUiLayout extends FrameLayout {
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsListener);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- getViewTreeObserver().removeOnComputeInternalInsetsListener(mInsetsListener);
mDisplayCutout = null;
}
@@ -68,16 +62,11 @@ public class VolumeUiLayout extends FrameLayout {
if (mChild == null) {
if (getChildCount() != 0) {
mChild = getChildAt(0);
- mOldHeight = mChild.getMeasuredHeight();
updateRotation();
} else {
return;
}
}
- int newHeight = mChild.getMeasuredHeight();
- if (newHeight != mOldHeight) {
- animateChild(mOldHeight, newHeight);
- }
}
@Override
@@ -95,8 +84,13 @@ public class VolumeUiLayout extends FrameLayout {
}
}
- private void updateRotation() {
+ public void updateRotation() {
setDisplayCutout();
+ if (mChild == null) {
+ if (getChildCount() != 0) {
+ mChild = getChildAt(0);
+ }
+ }
int rotation = RotationUtils.getRotation(getContext());
if (rotation != mRotation) {
updateSafeInsets(rotation);
@@ -144,43 +138,11 @@ public class VolumeUiLayout extends FrameLayout {
return r.bottom - r.top;
}
-
- private void animateChild(int oldHeight, int newHeight) {
- if (true) return;
- if (mAnimating) {
- mAnimation.cancel();
- }
- mAnimating = true;
- mAnimation = new AnimatorSet();
- mAnimation.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mAnimating = false;
- }
- });
- int fromTop = mChild.getTop();
- int fromBottom = mChild.getBottom();
- int toTop = fromTop - ((newHeight - oldHeight) / 2);
- int toBottom = fromBottom + ((newHeight - oldHeight) / 2);
- ObjectAnimator top = ObjectAnimator.ofInt(mChild, "top", fromTop, toTop);
- mAnimation.playTogether(top,
- ObjectAnimator.ofInt(mChild, "bottom", fromBottom, toBottom));
- }
-
-
@Override
public ViewOutlineProvider getOutlineProvider() {
return super.getOutlineProvider();
}
- public void setOutsideTouchListener(OnClickListener onClickListener) {
- mHasOutsideTouch = true;
- requestLayout();
- setOnClickListener(onClickListener);
- setClickable(true);
- setFocusable(true);
- }
-
public static VolumeUiLayout get(View v) {
if (v instanceof VolumeUiLayout) return (VolumeUiLayout) v;
if (v.getParent() instanceof View) {
@@ -188,16 +150,4 @@ public class VolumeUiLayout extends FrameLayout {
}
return null;
}
-
- private final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsListener = inoutInfo -> {
- if (mHasOutsideTouch || (mChild == null)) {
- inoutInfo.setTouchableInsets(
- ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
- return;
- }
- inoutInfo.setTouchableInsets(
- ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT);
- inoutInfo.contentInsets.set(mChild.getLeft(), mChild.getTop(),
- 0, getBottom() - mChild.getBottom());
- };
}