diff options
author | Steven Laver <lavers@google.com> | 2020-02-13 20:29:13 -0800 |
---|---|---|
committer | Steven Laver <lavers@google.com> | 2020-02-13 20:29:13 -0800 |
commit | d28a4f6b38dbab44128b4319f665dd65c3e4ec2c (patch) | |
tree | 680912fe833379242ee026450323ed4f34a6c64b /packages/SystemUI/src | |
parent | 029ad4fa703b5dcb74e8c4c272617464a9ba5fc8 (diff) | |
parent | 852c9950280d93875c529e4cae8396d94176f66e (diff) |
Merge RP1A.200204.001
Change-Id: I1e6c199dbee77379f84675965391c839eae04961
Diffstat (limited to 'packages/SystemUI/src')
174 files changed, 6244 insertions, 2218 deletions
diff --git a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java index 2f8ef2dc8828..b2423b9bf252 100644 --- a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java +++ b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java @@ -172,7 +172,7 @@ public class AdminSecondaryLockScreenController { private void onSurfaceReady() { try { - mClient.onSurfaceReady(mView.getInputToken(), mCallback); + mClient.onSurfaceReady(mView.getHostToken(), mCallback); } catch (RemoteException e) { Log.e(TAG, "Error in onSurfaceReady", e); dismiss(KeyguardUpdateMonitor.getCurrentUser()); diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java index ec4411cfcabd..e0594a999a23 100644 --- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java +++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java @@ -39,7 +39,6 @@ import android.util.Log; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import com.android.internal.telephony.TelephonyIntents; import com.android.settingslib.WirelessUtils; import com.android.systemui.Dependency; import com.android.systemui.R; @@ -347,15 +346,15 @@ public class CarrierTextController { CharSequence text = getContext().getText(com.android.internal.R.string.emergency_calls_only); Intent i = getContext().registerReceiver(null, - new IntentFilter(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)); + new IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED)); if (i != null) { String spn = ""; String plmn = ""; - if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) { - spn = i.getStringExtra(TelephonyIntents.EXTRA_SPN); + if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_SPN, false)) { + spn = i.getStringExtra(TelephonyManager.EXTRA_SPN); } - if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) { - plmn = i.getStringExtra(TelephonyIntents.EXTRA_PLMN); + if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false)) { + plmn = i.getStringExtra(TelephonyManager.EXTRA_PLMN); } if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn); if (Objects.equals(plmn, spn)) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java index 4e7956db4a2b..571c4ae0e386 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java @@ -290,7 +290,7 @@ public class KeyguardDisplayManager { View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); - getWindow().setFitWindowInsetsTypes(0 /* types */); + getWindow().getAttributes().setFitInsetsTypes(0 /* types */); getWindow().setNavigationBarContrastEnforced(false); getWindow().setNavigationBarColor(Color.TRANSPARENT); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java index ed1cd8191092..d5a08dda9853 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java @@ -409,15 +409,6 @@ public class KeyguardHostView extends FrameLayout implements SecurityCallback { mAudioManager.dispatchMediaKeyEvent(keyEvent); } - @Override - public void dispatchSystemUiVisibilityChanged(int visibility) { - super.dispatchSystemUiVisibilityChanged(visibility); - - if (!(mContext instanceof Activity)) { - setSystemUiVisibility(STATUS_BAR_DISABLE_BACK); - } - } - /** * In general, we enable unlocking the insecure keyguard with the menu key. However, there are * some cases where we wish to disable it, notably when the menu button placement or technology diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index ae787260adca..29c67ae1b4a6 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -15,7 +15,12 @@ */ package com.android.keyguard; +import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; +import static android.view.ViewRootImpl.sNewInsetsMode; +import static android.view.WindowInsets.Type.ime; +import static android.view.WindowInsets.Type.systemBars; import static com.android.systemui.DejankUtils.whitelistIpcs; +import static java.lang.Integer.max; import android.app.Activity; import android.app.AlertDialog; @@ -38,6 +43,7 @@ import android.view.SurfaceControl; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; +import android.view.WindowInsets; import android.view.WindowManager; import android.widget.FrameLayout; @@ -339,13 +345,22 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe } @Override - protected boolean fitSystemWindows(Rect insets) { + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + // Consume bottom insets because we're setting the padding locally (for IME and navbar.) - setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), insets.bottom); - insets.bottom = 0; - return false; + int inset; + if (sNewInsetsMode == NEW_INSETS_MODE_FULL) { + int bottomInset = insets.getInsetsIgnoringVisibility(systemBars()).bottom; + int imeInset = insets.getInsets(ime()).bottom; + inset = max(bottomInset, imeInset); + } else { + inset = insets.getSystemWindowInsetBottom(); + } + setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), inset); + return insets.inset(0, 0, 0, inset); } + private void showDialog(String title, String message) { if (mAlertDialog != null) { mAlertDialog.dismiss(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java index 5d35169cf926..f61f585cfe7c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -140,6 +140,13 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe mLayoutTransition.setAnimateParentHierarchy(false); } + // Temporary workaround to allow KeyguardStatusView to inflate a copy for Universal Smartspace. + // Eventually the existing copy will be reparented instead, and we won't need this. + public KeyguardSliceView(Context context, AttributeSet attributeSet) { + this(context, attributeSet, Dependency.get(ActivityStarter.class), + Dependency.get(ConfigurationController.class)); + } + @Override protected void onFinishInflate() { super.onFinishInflate(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java index 5a1c9976f021..61caf3bc5d8f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java @@ -18,10 +18,15 @@ package com.android.keyguard; import android.app.ActivityManager; import android.app.IActivityManager; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.res.Resources; import android.graphics.Color; +import android.graphics.PixelFormat; import android.os.Handler; +import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; import android.text.TextUtils; @@ -30,7 +35,11 @@ import android.util.AttributeSet; import android.util.Log; import android.util.Slog; import android.util.TypedValue; +import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; import android.view.View; +import android.view.WindowManager; +import android.view.WindowlessWindowManager; import android.widget.GridLayout; import android.widget.LinearLayout; import android.widget.TextView; @@ -40,6 +49,7 @@ import androidx.core.graphics.ColorUtils; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.shared.system.UniversalSmartspaceUtils; import com.android.systemui.statusbar.policy.ConfigurationController; import java.io.FileDescriptor; @@ -76,6 +86,7 @@ public class KeyguardStatusView extends GridLayout implements private int mIconTopMargin; private int mIconTopMarginWithHeader; private boolean mShowingHeader; + private SurfaceControlViewHost mUniversalSmartspaceViewHost; private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { @@ -122,6 +133,38 @@ public class KeyguardStatusView extends GridLayout implements } }; + private BroadcastReceiver mUniversalSmartspaceBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent i) { + // TODO(b/148159743): Restrict to Pixel Launcher. + if (UniversalSmartspaceUtils.ACTION_REQUEST_SMARTSPACE_VIEW.equals(i.getAction())) { + if (mUniversalSmartspaceViewHost != null) { + mUniversalSmartspaceViewHost.die(); + } + SurfaceControl surfaceControl = UniversalSmartspaceUtils.getSurfaceControl(i); + if (surfaceControl != null) { + IBinder input = UniversalSmartspaceUtils.getInputToken(i); + + WindowlessWindowManager windowlessWindowManager = + new WindowlessWindowManager(context.getResources().getConfiguration(), + surfaceControl, input); + mUniversalSmartspaceViewHost = new SurfaceControlViewHost(context, + context.getDisplay(), windowlessWindowManager); + WindowManager.LayoutParams layoutParams = + new WindowManager.LayoutParams( + surfaceControl.getWidth(), + surfaceControl.getHeight(), + WindowManager.LayoutParams.TYPE_APPLICATION, + 0, + PixelFormat.TRANSPARENT); + + mUniversalSmartspaceViewHost.addView( + inflate(context, R.layout.keyguard_status_area, null), layoutParams); + } + } + } + };; + public KeyguardStatusView(Context context) { this(context, null, 0); } @@ -316,6 +359,8 @@ public class KeyguardStatusView extends GridLayout implements super.onAttachedToWindow(); Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mInfoCallback); Dependency.get(ConfigurationController.class).addCallback(this); + getContext().registerReceiver(mUniversalSmartspaceBroadcastReceiver, + new IntentFilter(UniversalSmartspaceUtils.ACTION_REQUEST_SMARTSPACE_VIEW)); } @Override @@ -323,6 +368,7 @@ public class KeyguardStatusView extends GridLayout implements super.onDetachedFromWindow(); Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mInfoCallback); Dependency.get(ConfigurationController.class).removeCallback(this); + getContext().unregisterReceiver(mUniversalSmartspaceBroadcastReceiver); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index d71912a89504..4979329ab9d6 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -92,7 +92,6 @@ import android.util.Log; import android.util.SparseBooleanArray; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.telephony.TelephonyIntents; import com.android.internal.widget.LockPatternUtils; import com.android.settingslib.WirelessUtils; import com.android.systemui.DejankUtils; @@ -372,7 +371,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab checkIsHandlerThread(); if (DEBUG_SIM_STATES) { Log.v(TAG, "onSubscriptionInfoChanged()"); - List<SubscriptionInfo> sil = mSubscriptionManager.getActiveSubscriptionInfoList(false); + List<SubscriptionInfo> sil = mSubscriptionManager + .getActiveAndHiddenSubscriptionInfoList(); if (sil != null) { for (SubscriptionInfo subInfo : sil) { Log.v(TAG, "SubInfo:" + subInfo); @@ -426,10 +426,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab public List<SubscriptionInfo> getSubscriptionInfo(boolean forceReload) { List<SubscriptionInfo> sil = mSubscriptionInfo; if (sil == null || forceReload) { - sil = mSubscriptionManager.getActiveSubscriptionInfoList(false); + sil = mSubscriptionManager.getActiveAndHiddenSubscriptionInfoList(); } if (sil == null) { - // getActiveSubscriptionInfoList was null callers expect an empty list. + // getActiveAndHiddenSubscriptionInfoList was null callers expect an empty list. mSubscriptionInfo = new ArrayList<SubscriptionInfo>(); } else { mSubscriptionInfo = sil; @@ -1083,7 +1083,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab MSG_BATTERY_UPDATE, new BatteryStatus(status, level, plugged, health, maxChargingMicroWatt)); mHandler.sendMessage(msg); - } else if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(action)) { + } else if (Intent.ACTION_SIM_STATE_CHANGED.equals(action)) { SimData args = SimData.fromIntent(intent); // ACTION_SIM_STATE_CHANGED is rebroadcast after unlocking the device to // keep compatibility with apps that aren't direct boot aware. @@ -1122,7 +1122,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } mHandler.sendMessage( mHandler.obtainMessage(MSG_SERVICE_STATE_CHANGE, subId, 0, serviceState)); - } else if (TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED.equals(action)) { + } else if (TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED.equals(action)) { mHandler.sendEmptyMessage(MSG_SIM_SUBSCRIPTION_INFO_CHANGED); } else if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals( action)) { @@ -1273,7 +1273,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab static SimData fromIntent(Intent intent) { int state; - if (!TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(intent.getAction())) { + if (!Intent.ACTION_SIM_STATE_CHANGED.equals(intent.getAction())) { throw new IllegalArgumentException("only handles intent ACTION_SIM_STATE_CHANGED"); } String stateExtra = intent.getStringExtra(Intent.EXTRA_SIM_STATE); @@ -1673,7 +1673,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab filter.addAction(Intent.ACTION_BATTERY_CHANGED); filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); - filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED); + filter.addAction(Intent.ACTION_SIM_STATE_CHANGED); filter.addAction(Intent.ACTION_SERVICE_STATE); filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED); filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); @@ -1883,9 +1883,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive && !mGoingToSleep; final int user = getCurrentUser(); final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user); - final boolean isLockOutOrLockDown = - containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) - || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW) + final boolean isLockDown = + containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW) || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); final boolean isEncryptedOrTimedOut = containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT) @@ -1899,9 +1898,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab boolean becauseCannotSkipBouncer = !getUserCanSkipBouncer(user) || canBypass; // Scan even when encrypted or timeout to show a preemptive bouncer when bypassing. - // Lockout/lockdown modes shouldn't scan, since they are more explicit. + // Lock-down mode shouldn't scan, since it is more explicit. boolean strongAuthAllowsScanning = (!isEncryptedOrTimedOut || canBypass && !mBouncer) - && !isLockOutOrLockDown; + && !isLockDown; // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware. diff --git a/packages/SystemUI/src/com/android/systemui/DejankUtils.java b/packages/SystemUI/src/com/android/systemui/DejankUtils.java index 97578e19ccd1..3fce55f6e515 100644 --- a/packages/SystemUI/src/com/android/systemui/DejankUtils.java +++ b/packages/SystemUI/src/com/android/systemui/DejankUtils.java @@ -61,19 +61,21 @@ public class DejankUtils { || !isMainThread() || sTemporarilyIgnoreStrictMode) { return null; } + } - try { - String description = binder.getInterfaceDescriptor(); + try { + String description = binder.getInterfaceDescriptor(); + synchronized (sLock) { if (sWhitelistedFrameworkClasses.contains(description)) { return null; } - } catch (RemoteException e) { - e.printStackTrace(); } - - StrictMode.noteSlowCall("IPC detected on critical path: " + sBlockingIpcs.peek()); - return null; + } catch (RemoteException e) { + e.printStackTrace(); } + + StrictMode.noteSlowCall("IPC detected on critical path: " + sBlockingIpcs.peek()); + return null; } @Override @@ -126,9 +128,11 @@ public class DejankUtils { if (STRICT_MODE_ENABLED && sBlockingIpcs.empty()) { synchronized (sLock) { sBlockingIpcs.push("detectBlockingIpcs"); - try { - runnable.run(); - } finally { + } + try { + runnable.run(); + } finally { + synchronized (sLock) { sBlockingIpcs.pop(); } } @@ -177,9 +181,11 @@ public class DejankUtils { if (STRICT_MODE_ENABLED && !sTemporarilyIgnoreStrictMode) { synchronized (sLock) { sTemporarilyIgnoreStrictMode = true; - try { - runnable.run(); - } finally { + } + try { + runnable.run(); + } finally { + synchronized (sLock) { sTemporarilyIgnoreStrictMode = false; } } @@ -196,14 +202,16 @@ public class DejankUtils { if (STRICT_MODE_ENABLED && !sTemporarilyIgnoreStrictMode) { synchronized (sLock) { sTemporarilyIgnoreStrictMode = true; - final T val; - try { - val = supplier.get(); - } finally { + } + final T val; + try { + val = supplier.get(); + } finally { + synchronized (sLock) { sTemporarilyIgnoreStrictMode = false; } - return val; } + return val; } else { return supplier.get(); } diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index aacc2c4f614b..27fe37ef4e69 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -117,14 +117,15 @@ import com.android.systemui.statusbar.policy.SmartReplyConstants; import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.tracing.ProtoTracer; import com.android.systemui.tuner.TunablePadding.TunablePaddingService; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.leak.GarbageMonitor; import com.android.systemui.util.leak.LeakDetector; import com.android.systemui.util.leak.LeakReporter; import com.android.systemui.util.sensors.AsyncSensorManager; +import com.android.systemui.wm.DisplayController; import com.android.systemui.wm.DisplayImeController; -import com.android.systemui.wm.DisplayWindowController; import com.android.systemui.wm.SystemWindows; import java.io.FileDescriptor; @@ -323,10 +324,11 @@ public class Dependency { @Inject Lazy<CommandQueue> mCommandQueue; @Inject Lazy<Recents> mRecents; @Inject Lazy<StatusBar> mStatusBar; - @Inject Lazy<DisplayWindowController> mDisplayWindowController; + @Inject Lazy<DisplayController> mDisplayController; @Inject Lazy<SystemWindows> mSystemWindows; @Inject Lazy<DisplayImeController> mDisplayImeController; @Inject Lazy<RecordingController> mRecordingController; + @Inject Lazy<ProtoTracer> mProtoTracer; @Inject public Dependency() { @@ -516,9 +518,10 @@ public class Dependency { mProviders.put(CommandQueue.class, mCommandQueue::get); mProviders.put(Recents.class, mRecents::get); mProviders.put(StatusBar.class, mStatusBar::get); - mProviders.put(DisplayWindowController.class, mDisplayWindowController::get); + mProviders.put(DisplayController.class, mDisplayController::get); mProviders.put(SystemWindows.class, mSystemWindows::get); mProviders.put(DisplayImeController.class, mDisplayImeController::get); + mProviders.put(ProtoTracer.class, mProtoTracer::get); // TODO(b/118592525): to support multi-display , we start to add something which is // per-display, while others may be global. I think it's time to add diff --git a/packages/SystemUI/src/com/android/systemui/DumpController.kt b/packages/SystemUI/src/com/android/systemui/DumpController.kt index f14c4cd8e6c6..7a83a8948bb0 100644 --- a/packages/SystemUI/src/com/android/systemui/DumpController.kt +++ b/packages/SystemUI/src/com/android/systemui/DumpController.kt @@ -19,10 +19,10 @@ package com.android.systemui import android.util.ArraySet import android.util.Log import androidx.annotation.GuardedBy -import com.android.internal.util.Preconditions import java.io.FileDescriptor import java.io.PrintWriter import java.lang.ref.WeakReference +import java.util.Objects.requireNonNull import javax.inject.Inject import javax.inject.Singleton @@ -58,7 +58,7 @@ class DumpController @Inject constructor() : Dumpable { * @param dumpable the [Dumpable] to be added */ fun registerDumpable(dumpable: Dumpable) { - Preconditions.checkNotNull(dumpable, "The dumpable to be added cannot be null") + requireNonNull(dumpable, "The dumpable to be added cannot be null") registerDumpable(dumpable.javaClass.simpleName, dumpable) } @@ -71,7 +71,7 @@ class DumpController @Inject constructor() : Dumpable { * @param dumpable the [Dumpable] to be added */ fun registerDumpable(tag: String, dumpable: Dumpable) { - Preconditions.checkNotNull(dumpable, "The dumpable to be added cannot be null") + requireNonNull(dumpable, "The dumpable to be added cannot be null") if (DEBUG) Log.v(TAG, "*** register callback for $dumpable") synchronized<Unit>(listeners) { if (listeners.any { it.tag == tag }) { diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index db8b5831faf1..e66b9f21bd8c 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -503,7 +503,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { lp.gravity = Gravity.TOP | Gravity.LEFT; } lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - lp.setFitWindowInsetsTypes(0 /* types */); + lp.setFitInsetsTypes(0 /* types */); if (isLandscape(mRotation)) { lp.width = WRAP_CONTENT; lp.height = MATCH_PARENT; diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 75063e482f08..7b0bd9cadb9b 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -35,6 +35,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider; import com.android.systemui.statusbar.KeyguardIndicationController; +import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.phone.DozeParameters; @@ -153,6 +154,7 @@ public class SystemUIFactory { return new NotificationIconAreaController(context, statusBar, statusBarStateController, wakeUpCoordinator, keyguardBypassController, Dependency.get(NotificationMediaManager.class), + Dependency.get(NotificationListener.class), Dependency.get(DozeParameters.class)); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java new file mode 100644 index 000000000000..7262f8caac89 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java @@ -0,0 +1,393 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility; + +import android.accessibilityservice.AccessibilityService; +import android.app.PendingIntent; +import android.app.RemoteAction; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.drawable.Icon; +import android.hardware.input.InputManager; +import android.os.Handler; +import android.os.Looper; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.Log; +import android.view.Display; +import android.view.IWindowManager; +import android.view.InputDevice; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; +import android.view.accessibility.AccessibilityManager; + +import com.android.internal.R; +import com.android.internal.util.ScreenshotHelper; +import com.android.systemui.Dependency; +import com.android.systemui.SystemUI; +import com.android.systemui.recents.Recents; +import com.android.systemui.statusbar.phone.StatusBar; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Class to register system actions with accessibility framework. + */ +@Singleton +public class SystemActions extends SystemUI { + private static final String TAG = "SystemActions"; + // TODO(b/147916452): add implementation on launcher side to register this action. + + /** + * Action ID to go back. + */ + private static final int SYSTEM_ACTION_ID_BACK = AccessibilityService.GLOBAL_ACTION_BACK; // = 1 + + /** + * Action ID to go home. + */ + private static final int SYSTEM_ACTION_ID_HOME = AccessibilityService.GLOBAL_ACTION_HOME; // = 2 + + /** + * Action ID to toggle showing the overview of recent apps. Will fail on platforms that don't + * show recent apps. + */ + private static final int SYSTEM_ACTION_ID_RECENTS = + AccessibilityService.GLOBAL_ACTION_RECENTS; // = 3 + + /** + * Action ID to open the notifications. + */ + private static final int SYSTEM_ACTION_ID_NOTIFICATIONS = + AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS; // = 4 + + /** + * Action ID to open the quick settings. + */ + private static final int SYSTEM_ACTION_ID_QUICK_SETTINGS = + AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS; // = 5 + + /** + * Action ID to open the power long-press dialog. + */ + private static final int SYSTEM_ACTION_ID_POWER_DIALOG = + AccessibilityService.GLOBAL_ACTION_POWER_DIALOG; // = 6 + + /** + * Action ID to toggle docking the current app's window + */ + private static final int SYSTEM_ACTION_ID_TOGGLE_SPLIT_SCREEN = + AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN; // = 7 + + /** + * Action ID to lock the screen + */ + private static final int SYSTEM_ACTION_ID_LOCK_SCREEN = + AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN; // = 8 + + /** + * Action ID to take a screenshot + */ + private static final int SYSTEM_ACTION_ID_TAKE_SCREENSHOT = + AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT; // = 9 + + /** + * Action ID to show accessibility menu + */ + private static final int SYSTEM_ACTION_ID_ACCESSIBILITY_MENU = 10; + + private Recents mRecents; + private StatusBar mStatusBar; + private SystemActionsBroadcastReceiver mReceiver; + + @Inject + public SystemActions(Context context) { + super(context); + mRecents = Dependency.get(Recents.class); + mStatusBar = Dependency.get(StatusBar.class); + mReceiver = new SystemActionsBroadcastReceiver(); + } + + @Override + public void start() { + mContext.registerReceiverForAllUsers(mReceiver, mReceiver.createIntentFilter(), null, null); + + // TODO(b/148087487): update the icon used below to a valid one + RemoteAction actionBack = new RemoteAction( + Icon.createWithResource(mContext, R.drawable.ic_info), + mContext.getString(R.string.accessibility_system_action_back_label), + mContext.getString(R.string.accessibility_system_action_back_label), + mReceiver.createPendingIntent( + mContext, SystemActionsBroadcastReceiver.INTENT_ACTION_BACK)); + RemoteAction actionHome = new RemoteAction( + Icon.createWithResource(mContext, R.drawable.ic_info), + mContext.getString(R.string.accessibility_system_action_home_label), + mContext.getString(R.string.accessibility_system_action_home_label), + mReceiver.createPendingIntent( + mContext, SystemActionsBroadcastReceiver.INTENT_ACTION_HOME)); + + RemoteAction actionRecents = new RemoteAction( + Icon.createWithResource(mContext, R.drawable.ic_info), + mContext.getString(R.string.accessibility_system_action_recents_label), + mContext.getString(R.string.accessibility_system_action_recents_label), + mReceiver.createPendingIntent( + mContext, SystemActionsBroadcastReceiver.INTENT_ACTION_RECENTS)); + + RemoteAction actionNotifications = new RemoteAction( + Icon.createWithResource(mContext, R.drawable.ic_info), + mContext.getString(R.string.accessibility_system_action_notifications_label), + mContext.getString(R.string.accessibility_system_action_notifications_label), + mReceiver.createPendingIntent( + mContext, SystemActionsBroadcastReceiver.INTENT_ACTION_NOTIFICATIONS)); + + RemoteAction actionQuickSettings = new RemoteAction( + Icon.createWithResource(mContext, R.drawable.ic_info), + mContext.getString(R.string.accessibility_system_action_quick_settings_label), + mContext.getString(R.string.accessibility_system_action_quick_settings_label), + mReceiver.createPendingIntent( + mContext, SystemActionsBroadcastReceiver.INTENT_ACTION_QUICK_SETTINGS)); + + RemoteAction actionPowerDialog = new RemoteAction( + Icon.createWithResource(mContext, R.drawable.ic_info), + mContext.getString(R.string.accessibility_system_action_power_dialog_label), + mContext.getString(R.string.accessibility_system_action_power_dialog_label), + mReceiver.createPendingIntent( + mContext, SystemActionsBroadcastReceiver.INTENT_ACTION_POWER_DIALOG)); + + RemoteAction actionToggleSplitScreen = new RemoteAction( + Icon.createWithResource(mContext, R.drawable.ic_info), + mContext.getString(R.string.accessibility_system_action_toggle_split_screen_label), + mContext.getString(R.string.accessibility_system_action_toggle_split_screen_label), + mReceiver.createPendingIntent( + mContext, + SystemActionsBroadcastReceiver.INTENT_ACTION_TOGGLE_SPLIT_SCREEN)); + + RemoteAction actionLockScreen = new RemoteAction( + Icon.createWithResource(mContext, R.drawable.ic_info), + mContext.getString(R.string.accessibility_system_action_lock_screen_label), + mContext.getString(R.string.accessibility_system_action_lock_screen_label), + mReceiver.createPendingIntent( + mContext, SystemActionsBroadcastReceiver.INTENT_ACTION_LOCK_SCREEN)); + + RemoteAction actionTakeScreenshot = new RemoteAction( + Icon.createWithResource(mContext, R.drawable.ic_info), + mContext.getString(R.string.accessibility_system_action_screenshot_label), + mContext.getString(R.string.accessibility_system_action_screenshot_label), + mReceiver.createPendingIntent( + mContext, SystemActionsBroadcastReceiver.INTENT_ACTION_TAKE_SCREENSHOT)); + + RemoteAction actionAccessibilityMenu = new RemoteAction( + Icon.createWithResource(mContext, R.drawable.ic_info), + mContext.getString(R.string.accessibility_system_action_accessibility_menu_label), + mContext.getString(R.string.accessibility_system_action_accessibility_menu_label), + mReceiver.createPendingIntent( + mContext, SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_MENU)); + + AccessibilityManager am = (AccessibilityManager) mContext.getSystemService( + Context.ACCESSIBILITY_SERVICE); + + am.registerSystemAction(actionBack, SYSTEM_ACTION_ID_BACK); + am.registerSystemAction(actionHome, SYSTEM_ACTION_ID_HOME); + am.registerSystemAction(actionRecents, SYSTEM_ACTION_ID_RECENTS); + am.registerSystemAction(actionNotifications, SYSTEM_ACTION_ID_NOTIFICATIONS); + am.registerSystemAction(actionQuickSettings, SYSTEM_ACTION_ID_QUICK_SETTINGS); + am.registerSystemAction(actionPowerDialog, SYSTEM_ACTION_ID_POWER_DIALOG); + am.registerSystemAction(actionToggleSplitScreen, SYSTEM_ACTION_ID_TOGGLE_SPLIT_SCREEN); + am.registerSystemAction(actionLockScreen, SYSTEM_ACTION_ID_LOCK_SCREEN); + am.registerSystemAction(actionTakeScreenshot, SYSTEM_ACTION_ID_TAKE_SCREENSHOT); + am.registerSystemAction(actionAccessibilityMenu, SYSTEM_ACTION_ID_ACCESSIBILITY_MENU); + } + + private void handleBack() { + sendDownAndUpKeyEvents(KeyEvent.KEYCODE_BACK); + } + + private void handleHome() { + sendDownAndUpKeyEvents(KeyEvent.KEYCODE_HOME); + } + + private void sendDownAndUpKeyEvents(int keyCode) { + final long downTime = SystemClock.uptimeMillis(); + sendKeyEventIdentityCleared(keyCode, KeyEvent.ACTION_DOWN, downTime, downTime); + sendKeyEventIdentityCleared( + keyCode, KeyEvent.ACTION_UP, downTime, SystemClock.uptimeMillis()); + } + + private void sendKeyEventIdentityCleared(int keyCode, int action, long downTime, long time) { + KeyEvent event = KeyEvent.obtain(downTime, time, action, keyCode, 0, 0, + KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FROM_SYSTEM, + InputDevice.SOURCE_KEYBOARD, null); + InputManager.getInstance() + .injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + event.recycle(); + } + + private void handleRecents() { + mRecents.toggleRecentApps(); + } + + private void handleNotifications() { + mStatusBar.animateExpandNotificationsPanel(); + } + + private void handleQuickSettings() { + mStatusBar.animateExpandSettingsPanel(null); + } + + private void handlePowerDialog() { + IWindowManager windowManager = WindowManagerGlobal.getWindowManagerService(); + + try { + windowManager.showGlobalActions(); + } catch (RemoteException e) { + Log.e(TAG, "failed to display power dialog."); + } + } + + private void handleToggleSplitScreen() { + mStatusBar.toggleSplitScreen(); + } + + private void handleLockScreen() { + IWindowManager windowManager = WindowManagerGlobal.getWindowManagerService(); + + mContext.getSystemService(PowerManager.class).goToSleep(SystemClock.uptimeMillis(), + PowerManager.GO_TO_SLEEP_REASON_ACCESSIBILITY, 0); + try { + windowManager.lockNow(null); + } catch (RemoteException e) { + Log.e(TAG, "failed to lock screen."); + } + } + + private void handleTakeScreenshot() { + ScreenshotHelper screenshotHelper = new ScreenshotHelper(mContext); + screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN, + true, true, new Handler(Looper.getMainLooper()), null); + } + + private void handleAccessibilityMenu() { + AccessibilityManager.getInstance(mContext).notifyAccessibilityButtonClicked( + Display.DEFAULT_DISPLAY); + } + + private class SystemActionsBroadcastReceiver extends BroadcastReceiver { + private static final String INTENT_ACTION_BACK = "SYSTEM_ACTION_BACK"; + private static final String INTENT_ACTION_HOME = "SYSTEM_ACTION_HOME"; + private static final String INTENT_ACTION_RECENTS = "SYSTEM_ACTION_RECENTS"; + private static final String INTENT_ACTION_NOTIFICATIONS = "SYSTEM_ACTION_NOTIFICATIONS"; + private static final String INTENT_ACTION_QUICK_SETTINGS = "SYSTEM_ACTION_QUICK_SETTINGS"; + private static final String INTENT_ACTION_POWER_DIALOG = "SYSTEM_ACTION_POWER_DIALOG"; + private static final String INTENT_ACTION_TOGGLE_SPLIT_SCREEN = + "SYSTEM_ACTION_TOGGLE_SPLIT_SCREEN"; + private static final String INTENT_ACTION_LOCK_SCREEN = "SYSTEM_ACTION_LOCK_SCREEN"; + private static final String INTENT_ACTION_TAKE_SCREENSHOT = "SYSTEM_ACTION_TAKE_SCREENSHOT"; + private static final String INTENT_ACTION_ACCESSIBILITY_MENU = + "SYSTEM_ACTION_ACCESSIBILITY_MENU"; + + private PendingIntent createPendingIntent(Context context, String intentAction) { + switch (intentAction) { + case INTENT_ACTION_BACK: + case INTENT_ACTION_HOME: + case INTENT_ACTION_RECENTS: + case INTENT_ACTION_NOTIFICATIONS: + case INTENT_ACTION_QUICK_SETTINGS: + case INTENT_ACTION_POWER_DIALOG: + case INTENT_ACTION_TOGGLE_SPLIT_SCREEN: + case INTENT_ACTION_LOCK_SCREEN: + case INTENT_ACTION_TAKE_SCREENSHOT: + case INTENT_ACTION_ACCESSIBILITY_MENU: { + Intent intent = new Intent(intentAction); + return PendingIntent.getBroadcast(context, 0, intent, 0); + } + default: + break; + } + return null; + } + + private IntentFilter createIntentFilter() { + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(INTENT_ACTION_BACK); + intentFilter.addAction(INTENT_ACTION_HOME); + intentFilter.addAction(INTENT_ACTION_RECENTS); + intentFilter.addAction(INTENT_ACTION_NOTIFICATIONS); + intentFilter.addAction(INTENT_ACTION_QUICK_SETTINGS); + intentFilter.addAction(INTENT_ACTION_POWER_DIALOG); + intentFilter.addAction(INTENT_ACTION_TOGGLE_SPLIT_SCREEN); + intentFilter.addAction(INTENT_ACTION_LOCK_SCREEN); + intentFilter.addAction(INTENT_ACTION_TAKE_SCREENSHOT); + intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_MENU); + return intentFilter; + } + + @Override + public void onReceive(Context context, Intent intent) { + String intentAction = intent.getAction(); + switch (intentAction) { + case INTENT_ACTION_BACK: { + handleBack(); + break; + } + case INTENT_ACTION_HOME: { + handleHome(); + break; + } + case INTENT_ACTION_RECENTS: { + handleRecents(); + break; + } + case INTENT_ACTION_NOTIFICATIONS: { + handleNotifications(); + break; + } + case INTENT_ACTION_QUICK_SETTINGS: { + handleQuickSettings(); + break; + } + case INTENT_ACTION_POWER_DIALOG: { + handlePowerDialog(); + break; + } + case INTENT_ACTION_TOGGLE_SPLIT_SCREEN: { + handleToggleSplitScreen(); + break; + } + case INTENT_ACTION_LOCK_SCREEN: { + handleLockScreen(); + break; + } + case INTENT_ACTION_TAKE_SCREENSHOT: { + handleTakeScreenshot(); + break; + } + case INTENT_ACTION_ACCESSIBILITY_MENU: { + handleAccessibilityMenu(); + break; + } + default: + break; + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java index c243309d960a..581cf7a2fbef 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java @@ -86,6 +86,8 @@ public class WindowMagnificationController implements View.OnClickListener, private SurfaceView mMirrorSurfaceView; private View mControlsView; private View mOverlayView; + // The boundary of magnification frame. + private final Rect mMagnificationFrameBoundary = new Rect(); private MoveMirrorRunnable mMoveMirrorRunnable = new MoveMirrorRunnable(); @@ -93,7 +95,7 @@ public class WindowMagnificationController implements View.OnClickListener, mContext = context; mHandler = handler; Display display = mContext.getDisplay(); - display.getSize(mDisplaySize); + display.getRealSize(mDisplaySize); mDisplayId = mContext.getDisplayId(); mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); @@ -114,6 +116,7 @@ public class WindowMagnificationController implements View.OnClickListener, return; } setInitialStartBounds(); + setMagnificationFrameBoundary(); createOverlayWindow(); } @@ -330,7 +333,7 @@ public class WindowMagnificationController implements View.OnClickListener, @Override public void onClick(View v) { setMoveOffset(v, mMoveFrameAmountShort); - moveMirrorFromControls(); + moveMirrorWindow(mMoveWindowOffset.x, mMoveWindowOffset.y); } @Override @@ -370,10 +373,8 @@ public class WindowMagnificationController implements View.OnClickListener, case MotionEvent.ACTION_MOVE: int xDiff = (int) (event.getRawX() - mLastDrag.x); int yDiff = (int) (event.getRawY() - mLastDrag.y); - mMagnificationFrame.offset(xDiff, yDiff); + moveMirrorWindow(xDiff, yDiff); mLastDrag.set(event.getRawX(), event.getRawY()); - modifyWindowMagnification(mTransaction); - mTransaction.apply(); return true; } return false; @@ -393,11 +394,11 @@ public class WindowMagnificationController implements View.OnClickListener, } } - private void moveMirrorFromControls() { - mMagnificationFrame.offset(mMoveWindowOffset.x, mMoveWindowOffset.y); - - modifyWindowMagnification(mTransaction); - mTransaction.apply(); + private void moveMirrorWindow(int xOffset, int yOffset) { + if (updateMagnificationFramePosition(xOffset, yOffset)) { + modifyWindowMagnification(mTransaction); + mTransaction.apply(); + } } /** @@ -414,6 +415,52 @@ public class WindowMagnificationController implements View.OnClickListener, return new Rect(left, top, right, bottom); } + private void setMagnificationFrameBoundary() { + // Calculates width and height for magnification frame could exceed out the screen. + // TODO : re-calculating again when scale is changed. + // The half width of magnification frame. + final int halfWidth = mMagnificationFrame.width() / 2; + // The half height of magnification frame. + final int halfHeight = mMagnificationFrame.height() / 2; + // The scaled half width of magnified region. + final int scaledWidth = (int) (halfWidth / mScale); + // The scaled half height of magnified region. + final int scaledHeight = (int) (halfHeight / mScale); + final int exceededWidth = halfWidth - scaledWidth; + final int exceededHeight = halfHeight - scaledHeight; + + mMagnificationFrameBoundary.set(-exceededWidth, -exceededHeight, + mDisplaySize.x + exceededWidth, mDisplaySize.y + exceededHeight); + } + + /** + * Calculates and sets the real position of magnification frame based on the magnified region + * should be limited by the region of the display. + */ + private boolean updateMagnificationFramePosition(int xOffset, int yOffset) { + mTmpRect.set(mMagnificationFrame); + mTmpRect.offset(xOffset, yOffset); + + if (mTmpRect.left < mMagnificationFrameBoundary.left) { + mTmpRect.offsetTo(mMagnificationFrameBoundary.left, mTmpRect.top); + } else if (mTmpRect.right > mMagnificationFrameBoundary.right) { + final int leftOffset = mMagnificationFrameBoundary.right - mMagnificationFrame.width(); + mTmpRect.offsetTo(leftOffset, mTmpRect.top); + } + + if (mTmpRect.top < mMagnificationFrameBoundary.top) { + mTmpRect.offsetTo(mTmpRect.left, mMagnificationFrameBoundary.top); + } else if (mTmpRect.bottom > mMagnificationFrameBoundary.bottom) { + final int topOffset = mMagnificationFrameBoundary.bottom - mMagnificationFrame.height(); + mTmpRect.offsetTo(mTmpRect.left, topOffset); + } + + if (!mTmpRect.equals(mMagnificationFrame)) { + mMagnificationFrame.set(mTmpRect); + return true; + } + return false; + } @Override public void surfaceCreated(SurfaceHolder holder) { createMirror(); @@ -431,7 +478,7 @@ public class WindowMagnificationController implements View.OnClickListener, @Override public void run() { if (mIsPressedDown) { - moveMirrorFromControls(); + moveMirrorWindow(mMoveWindowOffset.x, mMoveWindowOffset.y); mHandler.postDelayed(mMoveMirrorRunnable, 100); } } diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java b/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java index 659629b59b45..5532a0427e79 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java @@ -70,7 +70,7 @@ public class AssistDisclosure { | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED, PixelFormat.TRANSLUCENT); lp.setTitle("AssistDisclosure"); - lp.setFitWindowInsetsTypes(0 /* types */); + lp.setFitInsetsTypes(0 /* types */); mWm.addView(mView, lp); mViewAdded = true; diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java index eb615a0392fa..f201a6fb1137 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java +++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java @@ -85,7 +85,7 @@ public class DefaultUiController implements AssistManager.UiController { PixelFormat.TRANSLUCENT); mLayoutParams.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; mLayoutParams.gravity = Gravity.BOTTOM; - mLayoutParams.setFitWindowInsetsTypes(0 /* types */); + mLayoutParams.setFitInsetsTypes(0 /* types */); mLayoutParams.setTitle("Assist"); mInvocationLightsView = (InvocationLightsView) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 89446adb0fd7..b8d32aec30e3 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -592,7 +592,7 @@ public class AuthContainerView extends LinearLayout lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; lp.setTitle("BiometricPrompt"); lp.token = windowToken; - lp.setFitWindowInsetsTypes(lp.getFitWindowInsetsTypes() & ~Type.statusBars()); + lp.setFitInsetsTypes(lp.getFitInsetsTypes() & ~Type.statusBars()); return lp; } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java index ccfd3a57811c..82c8a469a057 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java @@ -72,10 +72,10 @@ public class AuthCredentialPasswordView extends AuthCredentialView } // Wait a bit to focus the field so the focusable flag on the window is already set then. - post(() -> { + postDelayed(() -> { mPasswordField.requestFocus(); mImm.showSoftInput(mPasswordField, InputMethodManager.SHOW_IMPLICIT); - }); + }, 100); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java index dc2499602125..601bae286451 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java @@ -89,12 +89,12 @@ public class BadgedImageView extends ImageView { /** * Updates the view with provided info. */ - public void update(Bubble bubble, Bitmap bubbleImage, int dotColor, Path dotPath) { + public void update(Bubble bubble) { mBubble = bubble; - setImageBitmap(bubbleImage); + setImageBitmap(bubble.getBadgedImage()); setDotState(DOT_STATE_SUPPRESSED_FOR_FLYOUT); - mDotColor = dotColor; - drawDot(dotPath); + mDotColor = bubble.getDotColor(); + drawDot(bubble.getDotPath()); animateDot(); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java index 2d9775deb4b5..45705b76f09c 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java @@ -31,6 +31,8 @@ import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Path; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Bundle; @@ -58,6 +60,14 @@ class Bubble { private long mLastUpdated; private long mLastAccessed; + private BubbleController.NotificationSuppressionChangedListener mSuppressionListener; + + /** Whether the bubble should show a dot for the notification indicating updated content. */ + private boolean mShowBubbleUpdateDot = true; + + /** Whether flyout text should be suppressed, regardless of any other flags or state. */ + private boolean mSuppressFlyout; + // Items that are typically loaded later private String mAppName; private ShortcutInfo mShortcutInfo; @@ -69,20 +79,6 @@ class Bubble { private boolean mInflateSynchronously; /** - * Whether this notification should be shown in the shade when it is also displayed as a bubble. - * - * <p>When a notification is a bubble we don't show it in the shade once the bubble has been - * expanded</p> - */ - private boolean mShowInShadeWhenBubble = true; - - /** Whether the bubble should show a dot for the notification indicating updated content. */ - private boolean mShowBubbleUpdateDot = true; - - /** Whether flyout text should be suppressed, regardless of any other flags or state. */ - private boolean mSuppressFlyout; - - /** * Presentational info about the flyout. */ public static class FlyoutMessage { @@ -93,6 +89,9 @@ class Bubble { } private FlyoutMessage mFlyoutMessage; + private Bitmap mBadgedImage; + private int mDotColor; + private Path mDotPath; public static String groupId(NotificationEntry entry) { UserHandle user = entry.getSbn().getUser(); @@ -101,11 +100,13 @@ class Bubble { /** Used in tests when no UI is required. */ @VisibleForTesting(visibility = PRIVATE) - Bubble(NotificationEntry e) { + Bubble(NotificationEntry e, + BubbleController.NotificationSuppressionChangedListener listener) { mEntry = e; mKey = e.getKey(); mLastUpdated = e.getSbn().getPostTime(); mGroupId = groupId(e); + mSuppressionListener = listener; } public String getKey() { @@ -124,6 +125,18 @@ class Bubble { return mEntry.getSbn().getPackageName(); } + public Bitmap getBadgedImage() { + return mBadgedImage; + } + + public int getDotColor() { + return mDotColor; + } + + public Path getDotPath() { + return mDotPath; + } + @Nullable public String getAppName() { return mAppName; @@ -205,8 +218,12 @@ class Bubble { mAppName = info.appName; mFlyoutMessage = info.flyoutMessage; + mBadgedImage = info.badgedBubbleImage; + mDotColor = info.dotColor; + mDotPath = info.dotPath; + mExpandedView.update(this); - mIconView.update(this, info.badgedBubbleImage, info.dotColor, info.dotPath); + mIconView.update(this); } /** @@ -257,25 +274,42 @@ class Bubble { */ void markAsAccessedAt(long lastAccessedMillis) { mLastAccessed = lastAccessedMillis; - setShowInShade(false); + setSuppressNotification(true); setShowDot(false /* show */, true /* animate */); } /** - * Whether this notification should be shown in the shade when it is also displayed as a - * bubble. + * Should be invoked whenever a Bubble is promoted from overflow. + */ + void markUpdatedAt(long lastAccessedMillis) { + mLastUpdated = lastAccessedMillis; + } + + /** + * Whether this notification should be shown in the shade. */ boolean showInShade() { - return !mEntry.isRowDismissed() && !shouldSuppressNotification() - && (!mEntry.isClearable() || mShowInShadeWhenBubble); + return !shouldSuppressNotification() || !mEntry.isClearable(); } /** - * Sets whether this notification should be shown in the shade when it is also displayed as a - * bubble. + * Sets whether this notification should be suppressed in the shade. */ - void setShowInShade(boolean showInShade) { - mShowInShadeWhenBubble = showInShade; + void setSuppressNotification(boolean suppressNotification) { + boolean prevShowInShade = showInShade(); + + Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); + int flags = data.getFlags(); + if (suppressNotification) { + flags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; + } else { + flags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; + } + data.setFlags(flags); + + if (showInShade() != prevShowInShade && mSuppressionListener != null) { + mSuppressionListener.onBubbleNotificationSuppressionChange(this); + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index e642d4e16802..a26cce0bfc0c 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -103,6 +103,7 @@ import java.lang.annotation.Target; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.function.Consumer; import javax.inject.Inject; @@ -151,6 +152,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private BubbleData mBubbleData; @Nullable private BubbleStackView mStackView; private BubbleIconFactory mBubbleIconFactory; + private int mMaxBubbles; // Tracks the id of the current (foreground) user. private int mCurrentUserId; @@ -171,6 +173,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private StatusBarStateListener mStatusBarStateListener; private final ScreenshotHelper mScreenshotHelper; + // Callback that updates BubbleOverflowActivity on data change. + @Nullable private Runnable mOverflowCallback = null; private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; private IStatusBarService mBarService; @@ -186,6 +190,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private boolean mInflateSynchronously; + // TODO (b/145659174): allow for multiple callbacks to support the "shadow" new notif pipeline + private final List<NotifCallback> mCallbacks = new ArrayList<>(); + /** * Listener to be notified when some states of the bubbles change. */ @@ -220,6 +227,45 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } /** + * Listener to be notified when a bubbles' notification suppression state changes. + */ + public interface NotificationSuppressionChangedListener { + /** + * Called when the notification suppression state of a bubble changes. + */ + void onBubbleNotificationSuppressionChange(Bubble bubble); + } + + /** + * Callback for when the BubbleController wants to interact with the notification pipeline to: + * - Remove a previously bubbled notification + * - Update the notification shade since bubbled notification should/shouldn't be showing + */ + public interface NotifCallback { + /** + * Called when the BubbleController wants to remove an entry that it was previously hiding + * from the shade. See {@link BubbleController#isBubbleNotificationSuppressedFromShade}. + */ + void removeNotification(NotificationEntry entry); + + /** + * Called when a bubbled notification has changed whether it should be + * filtered from the shade. + */ + void invalidateNotificationFilter(String reason); + + /** + * Called on a bubbled entry that has been removed when there are no longer + * bubbled entries in its group. + * + * Checks whether its group has any other (non-bubbled) children. If it doesn't, + * removes all remnants of the group's summary from the notification pipeline. + * TODO: (b/145659174) Only old pipeline needs this - delete post-migration. + */ + void maybeCancelSummary(NotificationEntry entry); + } + + /** * Listens for the current state of the status bar and updates the visibility state * of bubbles as needed. */ @@ -299,30 +345,26 @@ public class BubbleController implements ConfigurationController.ConfigurationLi configurationController.addCallback(this /* configurationListener */); + mMaxBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_rendered); mBubbleData = data; mBubbleData.setListener(mBubbleDataListener); + mBubbleData.setSuppressionChangedListener(new NotificationSuppressionChangedListener() { + @Override + public void onBubbleNotificationSuppressionChange(Bubble bubble) { + // Make sure NoMan knows it's not showing in the shade anymore so anyone querying it + // can tell. + try { + mBarService.onBubbleNotificationSuppressionChanged(bubble.getKey(), + !bubble.showInShade()); + } catch (RemoteException e) { + // Bad things have happened + } + } + }); mNotificationEntryManager = entryManager; - mNotificationEntryManager.addNotificationEntryListener(mEntryListener); - mNotificationEntryManager.setNotificationRemoveInterceptor(mRemoveInterceptor); mNotificationGroupManager = groupManager; - mNotificationGroupManager.addOnGroupChangeListener( - new NotificationGroupManager.OnGroupChangeListener() { - @Override - public void onGroupSuppressionChanged( - NotificationGroupManager.NotificationGroup group, - boolean suppressed) { - // More notifications could be added causing summary to no longer - // be suppressed -- in this case need to remove the key. - final String groupKey = group.summary != null - ? group.summary.getSbn().getGroupKey() - : null; - if (!suppressed && groupKey != null - && mBubbleData.isSummarySuppressed(groupKey)) { - mBubbleData.removeSuppressedSummary(groupKey); - } - } - }); + setupNEM(); mNotificationShadeWindowController = notificationShadeWindowController; mStatusBarStateListener = new StatusBarStateListener(); @@ -362,6 +404,104 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } /** + * See {@link NotifCallback}. + */ + public void addNotifCallback(NotifCallback callback) { + mCallbacks.add(callback); + } + + private void setupNEM() { + mNotificationEntryManager.addNotificationEntryListener( + new NotificationEntryListener() { + @Override + public void onPendingEntryAdded(NotificationEntry entry) { + onEntryAdded(entry); + } + + @Override + public void onPreEntryUpdated(NotificationEntry entry) { + onEntryUpdated(entry); + } + + @Override + public void onNotificationRankingUpdated(RankingMap rankingMap) { + onRankingUpdated(rankingMap); + } + }); + + mNotificationEntryManager.setNotificationRemoveInterceptor( + new NotificationRemoveInterceptor() { + @Override + public boolean onNotificationRemoveRequested(String key, int reason) { + NotificationEntry entry = + mNotificationEntryManager.getActiveNotificationUnfiltered(key); + return shouldInterceptDismissal(entry, reason); + } + }); + + mNotificationGroupManager.addOnGroupChangeListener( + new NotificationGroupManager.OnGroupChangeListener() { + @Override + public void onGroupSuppressionChanged( + NotificationGroupManager.NotificationGroup group, + boolean suppressed) { + // More notifications could be added causing summary to no longer + // be suppressed -- in this case need to remove the key. + final String groupKey = group.summary != null + ? group.summary.getSbn().getGroupKey() + : null; + if (!suppressed && groupKey != null + && mBubbleData.isSummarySuppressed(groupKey)) { + mBubbleData.removeSuppressedSummary(groupKey); + } + } + }); + + addNotifCallback(new NotifCallback() { + @Override + public void removeNotification(NotificationEntry entry) { + mNotificationEntryManager.performRemoveNotification(entry.getSbn(), + UNDEFINED_DISMISS_REASON); + } + + @Override + public void invalidateNotificationFilter(String reason) { + mNotificationEntryManager.updateNotifications(reason); + } + + @Override + public void maybeCancelSummary(NotificationEntry entry) { + // Check if removed bubble has an associated suppressed group summary that needs + // to be removed now. + final String groupKey = entry.getSbn().getGroup(); + if (mBubbleData.isSummarySuppressed(groupKey)) { + mBubbleData.removeSuppressedSummary(entry.getSbn().getGroupKey()); + + final NotificationEntry summary = + mNotificationEntryManager.getActiveNotificationUnfiltered( + mBubbleData.getSummaryKey(groupKey)); + mNotificationEntryManager.performRemoveNotification(summary.getSbn(), + UNDEFINED_DISMISS_REASON); + } + + // Check if summary should be removed from NoManGroup + NotificationEntry summary = + mNotificationGroupManager.getLogicalGroupSummary(entry.getSbn()); + if (summary != null) { + ArrayList<NotificationEntry> summaryChildren = + mNotificationGroupManager.getLogicalChildren(summary.getSbn()); + boolean isSummaryThisNotif = summary.getKey().equals(entry.getKey()); + if (!isSummaryThisNotif && (summaryChildren == null + || summaryChildren.isEmpty())) { + mNotificationEntryManager.performRemoveNotification(summary.getSbn(), + UNDEFINED_DISMISS_REASON); + } + } + } + }); + } + + /** * Sets whether to perform inflation on the same thread as the caller. This method should only * be used in tests, not in production. */ @@ -370,6 +510,18 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mInflateSynchronously = inflateSynchronously; } + void setOverflowCallback(Runnable updateOverflow) { + mOverflowCallback = updateOverflow; + } + + /** + * @return Bubbles for updating overflow. + */ + List<Bubble> getOverflowBubbles() { + return mBubbleData.getOverflowBubbles(); + } + + /** * BubbleStackView is lazily created by this method the first time a Bubble is added. This * method initializes the stack view and adds it to the StatusBar just above the scrim. @@ -521,13 +673,15 @@ public class BubbleController implements ConfigurationController.ConfigurationLi * * False otherwise. */ - public boolean isBubbleNotificationSuppressedFromShade(String key) { + public boolean isBubbleNotificationSuppressedFromShade(NotificationEntry entry) { + String key = entry.getKey(); boolean isBubbleAndSuppressed = mBubbleData.hasBubbleWithKey(key) && !mBubbleData.getBubbleWithKey(key).showInShade(); - NotificationEntry entry = mNotificationEntryManager.getActiveNotificationUnfiltered(key); - String groupKey = entry != null ? entry.getSbn().getGroupKey() : null; + + String groupKey = entry.getSbn().getGroupKey(); boolean isSuppressedSummary = mBubbleData.isSummarySuppressed(groupKey); boolean isSummary = key.equals(mBubbleData.getSummaryKey(groupKey)); + return (isSummary && isSuppressedSummary) || isBubbleAndSuppressed; } @@ -537,6 +691,10 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mBubbleData.setSelectedBubble(bubble); } + void promoteBubbleFromOverflow(Bubble bubble) { + mBubbleData.promoteBubbleFromOverflow(bubble); + } + /** * Request the stack expand if needed, then select the specified Bubble as current. * @@ -657,168 +815,55 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } } - @SuppressWarnings("FieldCanBeLocal") - private final NotificationRemoveInterceptor mRemoveInterceptor = - new NotificationRemoveInterceptor() { - @Override - public boolean onNotificationRemoveRequested(String key, int reason) { - NotificationEntry entry = - mNotificationEntryManager.getActiveNotificationUnfiltered(key); - String groupKey = entry != null ? entry.getSbn().getGroupKey() : null; - ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey); - - boolean inBubbleData = mBubbleData.hasBubbleWithKey(key); - boolean isSuppressedSummary = (mBubbleData.isSummarySuppressed(groupKey) - && mBubbleData.getSummaryKey(groupKey).equals(key)); - boolean isSummary = entry != null - && entry.getSbn().getNotification().isGroupSummary(); - boolean isSummaryOfBubbles = (isSuppressedSummary || isSummary) - && bubbleChildren != null && !bubbleChildren.isEmpty(); - - if (!inBubbleData && !isSummaryOfBubbles) { - return false; - } - - final boolean isClearAll = reason == REASON_CANCEL_ALL; - final boolean isUserDimiss = reason == REASON_CANCEL || reason == REASON_CLICK; - final boolean isAppCancel = reason == REASON_APP_CANCEL - || reason == REASON_APP_CANCEL_ALL; - final boolean isSummaryCancel = reason == REASON_GROUP_SUMMARY_CANCELED; - - // Need to check for !appCancel here because the notification may have - // previously been dismissed & entry.isRowDismissed would still be true - boolean userRemovedNotif = (entry != null && entry.isRowDismissed() && !isAppCancel) - || isClearAll || isUserDimiss || isSummaryCancel; - - if (isSummaryOfBubbles) { - return handleSummaryRemovalInterception(entry, userRemovedNotif); - } - - // The bubble notification sticks around in the data as long as the bubble is - // not dismissed and the app hasn't cancelled the notification. - Bubble bubble = mBubbleData.getBubbleWithKey(key); - boolean bubbleExtended = entry != null && entry.isBubble() && userRemovedNotif; - if (bubbleExtended) { - bubble.setShowInShade(false); - bubble.setShowDot(false /* show */, true /* animate */); - mNotificationEntryManager.updateNotifications( - "BubbleController.onNotificationRemoveRequested"); - return true; - } else if (!userRemovedNotif && entry != null - && !isUserCreatedBubble(bubble.getKey())) { - // This wasn't a user removal so we should remove the bubble as well - mBubbleData.notificationEntryRemoved(entry, DISMISS_NOTIF_CANCEL); - return false; - } - return false; - } - }; - - private boolean handleSummaryRemovalInterception(NotificationEntry summary, - boolean userRemovedNotif) { - String groupKey = summary.getSbn().getGroupKey(); - ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey); - - if (userRemovedNotif) { - // If it's a user dismiss we mark the children to be hidden from the shade. - for (int i = 0; i < bubbleChildren.size(); i++) { - Bubble bubbleChild = bubbleChildren.get(i); - // As far as group manager is concerned, once a child is no longer shown - // in the shade, it is essentially removed. - mNotificationGroupManager.onEntryRemoved(bubbleChild.getEntry()); - bubbleChild.setShowInShade(false); - bubbleChild.setShowDot(false /* show */, true /* animate */); + private void onEntryAdded(NotificationEntry entry) { + boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey()); + boolean userBlocked = mUserBlockedBubbles.contains(entry.getKey()); + boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments( + mContext, entry, previouslyUserCreated, userBlocked); + + if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry) + && (canLaunchInActivityView(mContext, entry) || wasAdjusted)) { + if (wasAdjusted && !previouslyUserCreated) { + // Gotta treat the auto-bubbled / whitelisted packaged bubbles as usercreated + mUserCreatedBubbles.add(entry.getKey()); } - // And since all children are removed, remove the summary. - mNotificationGroupManager.onEntryRemoved(summary); - - // If the summary was auto-generated we don't need to keep that notification around - // because apps can't cancel it; so we only intercept & suppress real summaries. - boolean isAutogroupSummary = (summary.getSbn().getNotification().flags - & FLAG_AUTOGROUP_SUMMARY) != 0; - if (!isAutogroupSummary) { - mBubbleData.addSummaryToSuppress(summary.getSbn().getGroupKey(), - summary.getKey()); - // Tell shade to update for the suppression - mNotificationEntryManager.updateNotifications( - "BubbleController.handleSummaryRemovalInterception"); - } - return !isAutogroupSummary; - } else { - // If it's not a user dismiss it's a cancel. - for (int i = 0; i < bubbleChildren.size(); i++) { - // First check if any of these are user-created (i.e. experimental bubbles) - if (mUserCreatedBubbles.contains(bubbleChildren.get(i).getKey())) { - // Experimental bubble! Intercept the removal. - return true; - } - } - // Not an experimental bubble, safe to remove. - mBubbleData.removeSuppressedSummary(groupKey); - // Remove any associated bubble children with the summary. - for (int i = 0; i < bubbleChildren.size(); i++) { - Bubble bubbleChild = bubbleChildren.get(i); - mBubbleData.notificationEntryRemoved(bubbleChild.getEntry(), - DISMISS_GROUP_CANCELLED); - } - return false; + updateBubble(entry); } } - @SuppressWarnings("FieldCanBeLocal") - private final NotificationEntryListener mEntryListener = new NotificationEntryListener() { - @Override - public void onNotificationAdded(NotificationEntry entry) { - boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey()); - boolean userBlocked = mUserBlockedBubbles.contains(entry.getKey()); - boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments( - mContext, entry, previouslyUserCreated, userBlocked); - - if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry) - && (canLaunchInActivityView(mContext, entry) || wasAdjusted)) { - if (wasAdjusted && !previouslyUserCreated) { - // Gotta treat the auto-bubbled / whitelisted packaged bubbles as usercreated - mUserCreatedBubbles.add(entry.getKey()); - } - updateBubble(entry); - } - } - - @Override - public void onPreEntryUpdated(NotificationEntry entry) { - boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey()); - boolean userBlocked = mUserBlockedBubbles.contains(entry.getKey()); - boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments( - mContext, entry, previouslyUserCreated, userBlocked); - - boolean shouldBubble = mNotificationInterruptionStateProvider.shouldBubbleUp(entry) - && (canLaunchInActivityView(mContext, entry) || wasAdjusted); - if (!shouldBubble && mBubbleData.hasBubbleWithKey(entry.getKey())) { - // It was previously a bubble but no longer a bubble -- lets remove it - removeBubble(entry.getKey(), DISMISS_NO_LONGER_BUBBLE); - } else if (shouldBubble) { - if (wasAdjusted && !previouslyUserCreated) { - // Gotta treat the auto-bubbled / whitelisted packaged bubbles as usercreated - mUserCreatedBubbles.add(entry.getKey()); - } - updateBubble(entry); + private void onEntryUpdated(NotificationEntry entry) { + boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey()); + boolean userBlocked = mUserBlockedBubbles.contains(entry.getKey()); + boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments( + mContext, entry, previouslyUserCreated, userBlocked); + + boolean shouldBubble = mNotificationInterruptionStateProvider.shouldBubbleUp(entry) + && (canLaunchInActivityView(mContext, entry) || wasAdjusted); + if (!shouldBubble && mBubbleData.hasBubbleWithKey(entry.getKey())) { + // It was previously a bubble but no longer a bubble -- lets remove it + removeBubble(entry.getKey(), DISMISS_NO_LONGER_BUBBLE); + } else if (shouldBubble) { + if (wasAdjusted && !previouslyUserCreated) { + // Gotta treat the auto-bubbled / whitelisted packaged bubbles as usercreated + mUserCreatedBubbles.add(entry.getKey()); } + updateBubble(entry); } + } - @Override - public void onNotificationRankingUpdated(RankingMap rankingMap) { - // Forward to BubbleData to block any bubbles which should no longer be shown - mBubbleData.notificationRankingUpdated(rankingMap); - } - }; + private void onRankingUpdated(RankingMap rankingMap) { + // Forward to BubbleData to block any bubbles which should no longer be shown + mBubbleData.notificationRankingUpdated(rankingMap); + } @SuppressWarnings("FieldCanBeLocal") private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() { @Override public void applyUpdate(BubbleData.Update update) { - if (update.addedBubble != null) { - mStackView.addBubble(update.addedBubble); + // Update bubbles in overflow. + if (mOverflowCallback != null) { + mOverflowCallback.run(); } // Collapsing? Do this first before remaining steps. @@ -838,9 +883,11 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (reason != DISMISS_USER_CHANGED) { if (!mBubbleData.hasBubbleWithKey(bubble.getKey()) && !bubble.showInShade()) { - // The bubble is gone & the notification is gone, time to actually remove it - mNotificationEntryManager.performRemoveNotification( - bubble.getEntry().getSbn(), UNDEFINED_DISMISS_REASON); + // The bubble is now gone & the notification is hidden from the shade, so + // time to actually remove it + for (NotifCallback cb : mCallbacks) { + cb.removeNotification(bubble.getEntry()); + } } else { // Update the flag for SysUI bubble.getEntry().getSbn().getNotification().flags &= ~FLAG_BUBBLE; @@ -855,41 +902,26 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } } - // Check if removed bubble has an associated suppressed group summary that needs - // to be removed now. final String groupKey = bubble.getEntry().getSbn().getGroupKey(); - if (mBubbleData.isSummarySuppressed(groupKey) - && mBubbleData.getBubblesInGroup(groupKey).isEmpty()) { - // Time to actually remove the summary. - String notifKey = mBubbleData.getSummaryKey(groupKey); - mBubbleData.removeSuppressedSummary(groupKey); - NotificationEntry entry = - mNotificationEntryManager.getActiveNotificationUnfiltered(notifKey); - mNotificationEntryManager.performRemoveNotification( - entry.getSbn(), UNDEFINED_DISMISS_REASON); - } - - // Check if summary should be removed from NoManGroup - NotificationEntry summary = mNotificationGroupManager.getLogicalGroupSummary( - bubble.getEntry().getSbn()); - if (summary != null) { - ArrayList<NotificationEntry> summaryChildren = - mNotificationGroupManager.getLogicalChildren(summary.getSbn()); - boolean isSummaryThisNotif = summary.getKey().equals( - bubble.getEntry().getKey()); - if (!isSummaryThisNotif - && (summaryChildren == null || summaryChildren.isEmpty())) { - mNotificationEntryManager.performRemoveNotification( - summary.getSbn(), UNDEFINED_DISMISS_REASON); + if (mBubbleData.getBubblesInGroup(groupKey).isEmpty()) { + // Time to potentially remove the summary + for (NotifCallback cb : mCallbacks) { + cb.maybeCancelSummary(bubble.getEntry()); } } } } + if (update.addedBubble != null) { + mStackView.addBubble(update.addedBubble); + } + if (update.updatedBubble != null) { mStackView.updateBubble(update.updatedBubble); } + // At this point, the correct bubbles are inflated in the stack. + // Make sure the order in bubble data is reflected in bubble row. if (update.orderChanged) { mStackView.updateBubbleOrder(update.bubbles); } @@ -907,25 +939,150 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mStackView.setExpanded(true); } - mNotificationEntryManager.updateNotifications( - "BubbleData.Listener.applyUpdate"); + for (NotifCallback cb : mCallbacks) { + cb.invalidateNotificationFilter("BubbleData.Listener.applyUpdate"); + } updateStack(); if (DEBUG_BUBBLE_CONTROLLER) { - Log.d(TAG, "[BubbleData]"); + Log.d(TAG, "\n[BubbleData] bubbles:"); Log.d(TAG, BubbleDebugConfig.formatBubblesString(mBubbleData.getBubbles(), mBubbleData.getSelectedBubble())); if (mStackView != null) { - Log.d(TAG, "[BubbleStackView]"); + Log.d(TAG, "\n[BubbleStackView]"); Log.d(TAG, BubbleDebugConfig.formatBubblesString(mStackView.getBubblesOnScreen(), mStackView.getExpandedBubble())); } + Log.d(TAG, "\n[BubbleData] overflow:"); + Log.d(TAG, BubbleDebugConfig.formatBubblesString(mBubbleData.getOverflowBubbles(), + null)); } } }; /** + * We intercept notification entries cancelled by the user (i.e. dismissed) when there is an + * active bubble associated with it. We do this so that developers can still cancel it + * (and hence the bubbles associated with it). However, these intercepted notifications + * should then be hidden from the shade since the user has cancelled them, so we update + * {@link Bubble#showInShade}. + * + * The cancellation of summaries with children associated with bubbles are also handled in this + * method. User-cancelled summaries are tracked by {@link BubbleData#addSummaryToSuppress}. + * + * @return true if we want to intercept the dismissal of the entry, else false + */ + public boolean shouldInterceptDismissal(NotificationEntry entry, int dismissReason) { + if (entry == null) { + return false; + } + String key = entry.getKey(); + String groupKey = entry != null ? entry.getSbn().getGroupKey() : null; + ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey); + + boolean inBubbleData = mBubbleData.hasBubbleWithKey(key); + boolean isSuppressedSummary = (mBubbleData.isSummarySuppressed(groupKey) + && mBubbleData.getSummaryKey(groupKey).equals(key)); + boolean isSummary = entry != null + && entry.getSbn().getNotification().isGroupSummary(); + boolean isSummaryOfBubbles = (isSuppressedSummary || isSummary) + && bubbleChildren != null && !bubbleChildren.isEmpty(); + + if (!inBubbleData && !isSummaryOfBubbles) { + return false; + } + + final boolean isClearAll = dismissReason == REASON_CANCEL_ALL; + final boolean isUserDimiss = dismissReason == REASON_CANCEL + || dismissReason == REASON_CLICK; + final boolean isAppCancel = dismissReason == REASON_APP_CANCEL + || dismissReason == REASON_APP_CANCEL_ALL; + final boolean isSummaryCancel = dismissReason == REASON_GROUP_SUMMARY_CANCELED; + + // Need to check for !appCancel here because the notification may have + // previously been dismissed & entry.isRowDismissed would still be true + boolean userRemovedNotif = (entry != null && entry.isRowDismissed() && !isAppCancel) + || isClearAll || isUserDimiss || isSummaryCancel; + if (isSummaryOfBubbles) { + return handleSummaryRemovalInterception(entry, userRemovedNotif); + } + + // The bubble notification sticks around in the data as long as the bubble is + // not dismissed and the app hasn't cancelled the notification. + Bubble bubble = mBubbleData.getBubbleWithKey(key); + boolean bubbleExtended = entry != null && entry.isBubble() && userRemovedNotif; + if (bubbleExtended) { + bubble.setSuppressNotification(true); + bubble.setShowDot(false /* show */, true /* animate */); + for (NotifCallback cb : mCallbacks) { + cb.invalidateNotificationFilter("BubbleController" + + ".shouldInterceptDismissal"); + } + return true; + } else if (!userRemovedNotif && entry != null + && !isUserCreatedBubble(bubble.getKey())) { + // This wasn't a user removal so we should remove the bubble as well + mBubbleData.notificationEntryRemoved(entry, DISMISS_NOTIF_CANCEL); + return false; + } + return false; + } + + private boolean handleSummaryRemovalInterception(NotificationEntry summary, + boolean userRemovedNotif) { + String groupKey = summary.getSbn().getGroupKey(); + ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey); + + if (userRemovedNotif) { + // If it's a user dismiss we mark the children to be hidden from the shade. + for (int i = 0; i < bubbleChildren.size(); i++) { + Bubble bubbleChild = bubbleChildren.get(i); + // As far as group manager is concerned, once a child is no longer shown + // in the shade, it is essentially removed. + mNotificationGroupManager.onEntryRemoved(bubbleChild.getEntry()); + bubbleChild.setSuppressNotification(true); + bubbleChild.setShowDot(false /* show */, true /* animate */); + } + // And since all children are removed, remove the summary. + mNotificationGroupManager.onEntryRemoved(summary); + + // If the summary was auto-generated we don't need to keep that notification around + // because apps can't cancel it; so we only intercept & suppress real summaries. + boolean isAutogroupSummary = (summary.getSbn().getNotification().flags + & FLAG_AUTOGROUP_SUMMARY) != 0; + if (!isAutogroupSummary) { + // TODO: (b/145659174) remove references to mSuppressedGroupKeys once fully migrated + mBubbleData.addSummaryToSuppress(summary.getSbn().getGroupKey(), + summary.getKey()); + // Tell shade to update for the suppression + mNotificationEntryManager.updateNotifications("BubbleController" + + ".handleSummaryRemovalInterception"); + } + return !isAutogroupSummary; + } else { + // If it's not a user dismiss it's a cancel. + for (int i = 0; i < bubbleChildren.size(); i++) { + // First check if any of these are user-created (i.e. experimental bubbles) + if (mUserCreatedBubbles.contains(bubbleChildren.get(i).getKey())) { + // Experimental bubble! Intercept the removal. + return true; + } + } + + // Not an experimental bubble, safe to remove. + mBubbleData.removeSuppressedSummary(groupKey); + // Remove any associated bubble children with the summary. + for (int i = 0; i < bubbleChildren.size(); i++) { + Bubble bubbleChild = bubbleChildren.get(i); + mBubbleData.notificationEntryRemoved(bubbleChild.getEntry(), + DISMISS_GROUP_CANCELLED); + } + return false; + } + } + + /** * Lets any listeners know if bubble state has changed. * Updates the visibility of the bubbles based on current state. * Does not un-bubble, just hides or un-hides. Notifies any @@ -1043,12 +1200,10 @@ public class BubbleController implements ConfigurationController.ConfigurationLi @Override public void onSingleTaskDisplayDrawn(int displayId) { - final Bubble expandedBubble = mStackView != null - ? mStackView.getExpandedBubble() - : null; - if (expandedBubble != null && expandedBubble.getDisplayId() == displayId) { - expandedBubble.setContentVisibility(true); + if (mStackView == null) { + return; } + mStackView.showExpandedViewContents(displayId); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index cc0824ecc45c..673121f92716 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -78,9 +78,11 @@ public class BubbleData { // A read-only view of the bubbles list, changes there will be reflected here. final List<Bubble> bubbles; + final List<Bubble> overflowBubbles; - private Update(List<Bubble> bubbleOrder) { - bubbles = Collections.unmodifiableList(bubbleOrder); + private Update(List<Bubble> row, List<Bubble> overflow) { + bubbles = Collections.unmodifiableList(row); + overflowBubbles = Collections.unmodifiableList(overflow); } boolean anythingChanged() { @@ -113,11 +115,14 @@ public class BubbleData { private final Context mContext; /** Bubbles that are actively in the stack. */ private final List<Bubble> mBubbles; + /** Bubbles that aged out to overflow. */ + private final List<Bubble> mOverflowBubbles; /** Bubbles that are being loaded but haven't been added to the stack just yet. */ private final List<Bubble> mPendingBubbles; private Bubble mSelectedBubble; private boolean mExpanded; private final int mMaxBubbles; + private final int mMaxOverflowBubbles; // State tracked during an operation -- keeps track of what listener events to dispatch. private Update mStateChange; @@ -129,6 +134,9 @@ public class BubbleData { @Nullable private Listener mListener; + @Nullable + private BubbleController.NotificationSuppressionChangedListener mSuppressionListener; + /** * We track groups with summaries that aren't visibly displayed but still kept around because * the bubble(s) associated with the summary still exist. @@ -146,9 +154,16 @@ public class BubbleData { public BubbleData(Context context) { mContext = context; mBubbles = new ArrayList<>(); + mOverflowBubbles = new ArrayList<>(); mPendingBubbles = new ArrayList<>(); - mStateChange = new Update(mBubbles); + mStateChange = new Update(mBubbles, mOverflowBubbles); mMaxBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_rendered); + mMaxOverflowBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_overflow); + } + + public void setSuppressionChangedListener( + BubbleController.NotificationSuppressionChangedListener listener) { + mSuppressionListener = listener; } public boolean hasBubbles() { @@ -184,6 +199,19 @@ public class BubbleData { dispatchPendingChanges(); } + public void promoteBubbleFromOverflow(Bubble bubble) { + if (DEBUG_BUBBLE_DATA) { + Log.d(TAG, "promoteBubbleFromOverflow: " + bubble); + } + mOverflowBubbles.remove(bubble); + doAdd(bubble); + setSelectedBubbleInternal(bubble); + // Preserve new order for next repack, which sorts by last updated time. + bubble.markUpdatedAt(mTimeSource.currentTimeMillis()); + trim(); + dispatchPendingChanges(); + } + /** * Constructs a new bubble or returns an existing one. Does not add new bubbles to * bubble data, must go through {@link #notificationEntryUpdated(Bubble, boolean, boolean)} @@ -199,7 +227,7 @@ public class BubbleData { return b; } } - bubble = new Bubble(entry); + bubble = new Bubble(entry, mSuppressionListener); mPendingBubbles.add(bubble); } else { bubble.setEntry(entry); @@ -238,11 +266,13 @@ public class BubbleData { } else if (mSelectedBubble == null) { setSelectedBubbleInternal(bubble); } + boolean isBubbleExpandedAndSelected = mExpanded && mSelectedBubble == bubble; - bubble.setShowInShade(!isBubbleExpandedAndSelected && showInShade); + boolean suppress = isBubbleExpandedAndSelected || !showInShade || !bubble.showInShade(); + bubble.setSuppressNotification(suppress); bubble.setShowDot(!isBubbleExpandedAndSelected /* show */, true /* animate */); - dispatchPendingChanges(); + dispatchPendingChanges(); } public void notificationEntryRemoved(NotificationEntry entry, @DismissReason int reason) { @@ -343,6 +373,7 @@ public class BubbleData { mStateChange.orderChanged = true; } mStateChange.addedBubble = bubble; + if (!isExpanded()) { mStateChange.orderChanged |= packGroup(findFirstIndexForGroup(bubble.getGroupId())); // Top bubble becomes selected. @@ -407,6 +438,17 @@ public class BubbleData { mStateChange.orderChanged |= repackAll(); } + if (reason == BubbleController.DISMISS_AGED) { + if (DEBUG_BUBBLE_DATA) { + Log.d(TAG, "overflowing bubble: " + bubbleToRemove); + } + mOverflowBubbles.add(0, bubbleToRemove); + if (mOverflowBubbles.size() == mMaxOverflowBubbles + 1) { + // Remove oldest bubble. + mOverflowBubbles.remove(mOverflowBubbles.size() - 1); + } + } + // Note: If mBubbles.isEmpty(), then mSelectedBubble is now null. if (Objects.equals(mSelectedBubble, bubbleToRemove)) { // Move selection to the new bubble at the same position. @@ -454,7 +496,7 @@ public class BubbleData { if (mListener != null && mStateChange.anythingChanged()) { mListener.applyUpdate(mStateChange); } - mStateChange = new Update(mBubbles); + mStateChange = new Update(mBubbles, mOverflowBubbles); } /** @@ -689,12 +731,19 @@ public class BubbleData { } /** - * The set of bubbles. + * The set of bubbles in row. */ @VisibleForTesting(visibility = PRIVATE) public List<Bubble> getBubbles() { return Collections.unmodifiableList(mBubbles); } + /** + * The set of bubbles in overflow. + */ + @VisibleForTesting(visibility = PRIVATE) + public List<Bubble> getOverflowBubbles() { + return Collections.unmodifiableList(mOverflowBubbles); + } @VisibleForTesting(visibility = PRIVATE) Bubble getBubbleWithKey(String key) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index 48ce4e9b0097..50a50633f43c 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -20,10 +20,13 @@ import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.view.Display.INVALID_DISPLAY; +import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; +import static android.view.ViewRootImpl.sNewInsetsMode; import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW; import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; +import android.annotation.Nullable; import android.app.ActivityOptions; import android.app.ActivityTaskManager; import android.app.ActivityView; @@ -83,7 +86,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList private ActivityViewStatus mActivityViewStatus = ActivityViewStatus.INITIALIZING; private int mTaskId = -1; - private PendingIntent mBubbleIntent; + private PendingIntent mPendingIntent; private boolean mKeyboardVisible; private boolean mNeedsNewHeight; @@ -98,7 +101,9 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList private int[] mTempLoc = new int[2]; private int mExpandedViewTouchSlop; - private Bubble mBubble; + @Nullable private Bubble mBubble; + + private boolean mIsOverflow; private BubbleController mBubbleController = Dependency.get(BubbleController.class); private WindowManager mWindowManager; @@ -125,7 +130,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList + "bubble=" + getBubbleKey()); } try { - if (mBubble.usingShortcutInfo()) { + if (!mIsOverflow && mBubble.usingShortcutInfo()) { mActivityView.startShortcutActivity(mBubble.getShortcutInfo(), options, null /* sourceBounds */); } else { @@ -133,7 +138,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList // Apply flags to make behaviour match documentLaunchMode=always. fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT); fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); - mActivityView.startActivity(mBubbleIntent, fillInIntent, options); + mActivityView.startActivity(mPendingIntent, fillInIntent, options); } } catch (RuntimeException e) { // If there's a runtime exception here then there's something @@ -141,7 +146,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList // the bubble again so we'll just remove it. Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey() + ", " + e.getMessage() + "; removing bubble"); - mBubbleController.removeBubble(mBubble.getKey(), + mBubbleController.removeBubble(getBubbleKey(), BubbleController.DISMISS_INVALID_INTENT); } }); @@ -241,6 +246,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */, true /* singleTaskInstance */); + // Set ActivityView's alpha value as zero, since there is no view content to be shown. setContentVisibility(false); addView(mActivityView); @@ -334,7 +340,13 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList ? insets.getDisplayCutout().getSafeInsetBottom() : 0); final int insetsBottom = Math.max(activityViewBottom - keyboardTop, 0); - mActivityView.setForwardedInsets(Insets.of(0, 0, 0, insetsBottom)); + + // TODO: Temporary hack to offset the view until we can properly inset Bubbles again. + if (sNewInsetsMode == NEW_INSETS_MODE_FULL) { + mStackView.animate().translationY(-insetsBottom); + } else { + mActivityView.setForwardedInsets(Insets.of(0, 0, 0, insetsBottom)); + } } } @@ -342,6 +354,15 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList mStackView = stackView; } + public void setOverflow(boolean overflow) { + mIsOverflow = overflow; + + Intent target = new Intent(mContext, BubbleOverflowActivity.class); + mPendingIntent = PendingIntent.getActivity(mContext, /* requestCode */ 0, + target, PendingIntent.FLAG_UPDATE_CURRENT); + mSettingsIcon.setVisibility(GONE); + } + /** * Sets the bubble used to populate this view. */ @@ -350,14 +371,14 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList Log.d(TAG, "update: bubble=" + (bubble != null ? bubble.getKey() : "null")); } boolean isNew = mBubble == null; - if (isNew || bubble.getKey().equals(mBubble.getKey())) { + if (isNew || bubble != null && bubble.getKey().equals(mBubble.getKey())) { mBubble = bubble; mSettingsIcon.setContentDescription(getResources().getString( R.string.bubbles_settings_button_description, bubble.getAppName())); if (isNew) { - mBubbleIntent = mBubble.getBubbleIntent(); - if (mBubbleIntent != null || mBubble.getShortcutInfo() != null) { + mPendingIntent = mBubble.getBubbleIntent(); + if (mPendingIntent != null || mBubble.getShortcutInfo() != null) { setContentVisibility(false); mActivityView.setVisibility(VISIBLE); } @@ -393,12 +414,16 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList return true; } + // TODO(138116789) Fix overflow height. void updateHeight() { if (DEBUG_BUBBLE_EXPANDED_VIEW) { Log.d(TAG, "updateHeight: bubble=" + getBubbleKey()); } if (usingActivityView()) { - float desiredHeight = Math.max(mBubble.getDesiredHeight(mContext), mMinHeight); + float desiredHeight = mMinHeight; + if (!mIsOverflow) { + desiredHeight = Math.max(mBubble.getDesiredHeight(mContext), mMinHeight); + } float height = Math.min(desiredHeight, getMaxExpandedHeight()); height = Math.max(height, mMinHeight); LayoutParams lp = (LayoutParams) mActivityView.getLayoutParams(); @@ -543,7 +568,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList } private boolean usingActivityView() { - return (mBubbleIntent != null || mBubble.getShortcutInfo() != null) + return (mPendingIntent != null || mBubble.getShortcutInfo() != null) && mActivityView != null; } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java index 4252f72b808e..006de8406ce2 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java @@ -76,6 +76,9 @@ public class BubbleExperimentConfig { private static final String ALLOW_BUBBLE_MENU = "allow_bubble_screenshot_menu"; private static final boolean ALLOW_BUBBLE_MENU_DEFAULT = false; + private static final String ALLOW_BUBBLE_OVERFLOW = "allow_bubble_overflow"; + private static final boolean ALLOW_BUBBLE_OVERFLOW_DEFAULT = false; + /** * When true, if a notification has the information necessary to bubble (i.e. valid * contentIntent and an icon or image), then a {@link android.app.Notification.BubbleMetadata} @@ -141,6 +144,16 @@ public class BubbleExperimentConfig { } /** + * When true, show a menu when a bubble is long-pressed, which will allow the user to take + * actions on that bubble. + */ + static boolean allowBubbleOverflow(Context context) { + return Settings.Secure.getInt(context.getContentResolver(), + ALLOW_BUBBLE_OVERFLOW, + ALLOW_BUBBLE_OVERFLOW_DEFAULT ? 1 : 0) != 0; + } + + /** * If {@link #allowAnyNotifToBubble(Context)} is true, this method creates and adds * {@link android.app.Notification.BubbleMetadata} to the notification entry as long as * the notification has necessary info for BubbleMetadata. diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java index 4194352f93bd..5b9ea7dd5e3a 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java @@ -431,7 +431,7 @@ public class BubbleFlyoutView extends FrameLayout { final float interpolatedRadius = getInterpolatedRadius(); rectPath.addRoundRect(mBgRect, interpolatedRadius, interpolatedRadius, Path.Direction.CW); - outline.setConvexPath(rectPath); + outline.setPath(rectPath); // Get rid of the triangle path once it has disappeared behind the flyout. if (mPercentStillFlyout > 0.5f) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java index d99607fd6236..bea55c820b40 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java @@ -1,15 +1,42 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.bubbles; +import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_OVERFLOW; +import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES; +import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; + import android.app.Activity; import android.content.res.TypedArray; import android.graphics.Color; import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.ViewGroup; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.android.systemui.R; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + import javax.inject.Inject; /** @@ -17,9 +44,13 @@ import javax.inject.Inject; * Must be public to be accessible to androidx...AppComponentFactory */ public class BubbleOverflowActivity extends Activity { + private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleOverflowActivity" : TAG_BUBBLES; + + private BubbleController mBubbleController; + private BubbleOverflowAdapter mAdapter; private RecyclerView mRecyclerView; + private List<Bubble> mOverflowBubbles = new ArrayList<>(); private int mMaxBubbles; - private BubbleController mBubbleController; @Inject public BubbleOverflowActivity(BubbleController controller) { @@ -35,17 +66,42 @@ public class BubbleOverflowActivity extends Activity { mMaxBubbles = getResources().getInteger(R.integer.bubbles_max_rendered); mRecyclerView = findViewById(R.id.bubble_overflow_recycler); mRecyclerView.setLayoutManager( - new GridLayoutManager(getApplicationContext(), /* numberOfColumns */ mMaxBubbles)); + new GridLayoutManager(getApplicationContext(), + getResources().getInteger(R.integer.bubbles_overflow_columns))); + + mAdapter = new BubbleOverflowAdapter(mOverflowBubbles, + mBubbleController::promoteBubbleFromOverflow); + mRecyclerView.setAdapter(mAdapter); + + updateData(mBubbleController.getOverflowBubbles()); + mBubbleController.setOverflowCallback(() -> { + updateData(mBubbleController.getOverflowBubbles()); + }); } void setBackgroundColor() { final TypedArray ta = getApplicationContext().obtainStyledAttributes( - new int[] {android.R.attr.colorBackgroundFloating}); + new int[]{android.R.attr.colorBackgroundFloating}); int bgColor = ta.getColor(0, Color.WHITE); ta.recycle(); findViewById(android.R.id.content).setBackgroundColor(bgColor); } + void updateData(List<Bubble> bubbles) { + mOverflowBubbles.clear(); + if (bubbles.size() > mMaxBubbles) { + mOverflowBubbles.addAll(bubbles.subList(mMaxBubbles, bubbles.size())); + } else { + mOverflowBubbles.addAll(bubbles); + } + mAdapter.notifyDataSetChanged(); + + if (DEBUG_OVERFLOW) { + Log.d(TAG, "Updated overflow bubbles:\n" + BubbleDebugConfig.formatBubblesString( + mOverflowBubbles, /*selected*/ null)); + } + } + @Override public void onStart() { super.onStart(); @@ -75,3 +131,48 @@ public class BubbleOverflowActivity extends Activity { super.onDestroy(); } } + +class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.ViewHolder> { + private Consumer<Bubble> mPromoteBubbleFromOverflow; + private List<Bubble> mBubbles; + + public BubbleOverflowAdapter(List<Bubble> list, Consumer<Bubble> promoteBubble) { + mBubbles = list; + mPromoteBubbleFromOverflow = promoteBubble; + } + + @Override + public BubbleOverflowAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, + int viewType) { + BadgedImageView view = (BadgedImageView) LayoutInflater.from(parent.getContext()) + .inflate(R.layout.bubble_view, parent, false); + view.setPadding(15, 15, 15, 15); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(ViewHolder vh, int index) { + Bubble bubble = mBubbles.get(index); + + vh.mBadgedImageView.update(bubble); + vh.mBadgedImageView.setOnClickListener(view -> { + mBubbles.remove(bubble); + notifyDataSetChanged(); + mPromoteBubbleFromOverflow.accept(bubble); + }); + } + + @Override + public int getItemCount() { + return mBubbles.size(); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + public BadgedImageView mBadgedImageView; + + public ViewHolder(BadgedImageView v) { + super(v); + mBadgedImageView = v; + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 54a42a6ec212..6062a3d45be0 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -33,6 +33,8 @@ import android.app.Notification; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Color; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.graphics.Paint; @@ -40,6 +42,9 @@ import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.drawable.AdaptiveIconDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.InsetDrawable; import android.os.Bundle; import android.os.VibrationEffect; import android.os.Vibrator; @@ -58,6 +63,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.FrameLayout; +import android.widget.ImageView; import androidx.annotation.MainThread; import androidx.annotation.Nullable; @@ -195,8 +201,6 @@ public class BubbleStackView extends FrameLayout { private int mPointerHeight; private int mStatusBarHeight; private int mImeOffset; - private int mBubbleMenuOffset = 252; - private BubbleIconFactory mBubbleIconFactory; private Bubble mExpandedBubble; private boolean mIsExpanded; @@ -320,6 +324,8 @@ public class BubbleStackView extends FrameLayout { private Runnable mAfterMagnet; private int mOrientation = Configuration.ORIENTATION_UNDEFINED; + private BubbleExpandedView mOverflowExpandedView; + private ImageView mOverflowBtn; public BubbleStackView(Context context, BubbleData data, @Nullable SurfaceSynchronizer synchronizer) { @@ -369,8 +375,6 @@ public class BubbleStackView extends FrameLayout { mBubbleContainer.setClipChildren(false); addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); - mBubbleIconFactory = new BubbleIconFactory(context); - mExpandedViewContainer = new FrameLayout(context); mExpandedViewContainer.setElevation(elevation); mExpandedViewContainer.setPadding(mExpandedViewPadding, mExpandedViewPadding, @@ -405,8 +409,12 @@ public class BubbleStackView extends FrameLayout { .setStiffness(SpringForce.STIFFNESS_LOW) .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)); mExpandedViewYAnim.addEndListener((anim, cancelled, value, velocity) -> { - if (mIsExpanded && mExpandedBubble != null) { - mExpandedBubble.getExpandedView().updateView(); + if (mIsExpanded) { + if (mExpandedBubble == null) { + mOverflowExpandedView.updateView(); + } else { + mExpandedBubble.getExpandedView().updateView(); + } } }); @@ -414,6 +422,10 @@ public class BubbleStackView extends FrameLayout { setFocusable(true); mBubbleContainer.bringToFront(); + if (BubbleExperimentConfig.allowBubbleOverflow(mContext)) { + setUpOverflow(); + } + setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> { if (!mIsExpanded || mIsExpansionAnimating) { return view.onApplyWindowInsets(insets); @@ -421,7 +433,13 @@ public class BubbleStackView extends FrameLayout { mExpandedAnimationController.updateYPosition( // Update the insets after we're done translating otherwise position // calculation for them won't be correct. - () -> mExpandedBubble.getExpandedView().updateInsets(insets)); + () -> { + if (mExpandedBubble == null) { + mOverflowExpandedView.updateInsets(insets); + } else { + mExpandedBubble.getExpandedView().updateInsets(insets); + } + }); return view.onApplyWindowInsets(insets); }); @@ -433,7 +451,11 @@ public class BubbleStackView extends FrameLayout { // Reposition & adjust the height for new orientation if (mIsExpanded) { mExpandedViewContainer.setTranslationY(getExpandedViewY()); - mExpandedBubble.getExpandedView().updateView(); + if (mExpandedBubble == null) { + mOverflowExpandedView.updateView(); + } else { + mExpandedBubble.getExpandedView().updateView(); + } } // Need to update the padding around the view @@ -499,6 +521,45 @@ public class BubbleStackView extends FrameLayout { mBubbleMenuView = findViewById(R.id.bubble_menu_container); } + private void setUpOverflow() { + mOverflowExpandedView = (BubbleExpandedView) mInflater.inflate( + R.layout.bubble_expanded_view, this /* root */, false /* attachToRoot */); + mOverflowExpandedView.setOverflow(true); + + mOverflowBtn = (ImageView) mInflater.inflate(R.layout.bubble_overflow_button, + this /* root */, + false /* attachToRoot */); + + mBubbleContainer.addView(mOverflowBtn, 0, + new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); + + mOverflowBtn.setOnClickListener(v -> { + setSelectedBubble(null); + }); + + TypedArray ta = mContext.obtainStyledAttributes( + new int[]{android.R.attr.colorBackgroundFloating}); + int bgColor = ta.getColor(0, Color.WHITE /* default */); + ta.recycle(); + + InsetDrawable fg = new InsetDrawable(mOverflowBtn.getDrawable(), 28); + ColorDrawable bg = new ColorDrawable(bgColor); + AdaptiveIconDrawable adaptiveIcon = new AdaptiveIconDrawable(bg, fg); + mOverflowBtn.setImageDrawable(adaptiveIcon); + + mOverflowBtn.setVisibility(GONE); + } + + void showExpandedViewContents(int displayId) { + if (mOverflowExpandedView != null + && mOverflowExpandedView.getVirtualDisplayId() == displayId) { + mOverflowExpandedView.setContentVisibility(true); + } else if (mExpandedBubble != null + && mExpandedBubble.getExpandedView().getVirtualDisplayId() == displayId) { + mExpandedBubble.setContentVisibility(true); + } + } + private void setUpFlyout() { if (mFlyout != null) { removeView(mFlyout); @@ -660,7 +721,7 @@ public class BubbleStackView extends FrameLayout { private void updateSystemGestureExcludeRects() { // Exclude the region occupied by the first BubbleView in the stack Rect excludeZone = mSystemGestureExclusionRects.get(0); - if (mBubbleContainer.getChildCount() > 0) { + if (getBubbleCount() > 0) { View firstBubble = mBubbleContainer.getChildAt(0); excludeZone.set(firstBubble.getLeft(), firstBubble.getTop(), firstBubble.getRight(), firstBubble.getBottom()); @@ -721,7 +782,7 @@ public class BubbleStackView extends FrameLayout { Log.d(TAG, "addBubble: " + bubble); } - if (mBubbleContainer.getChildCount() == 0) { + if (getBubbleCount() == 0) { mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide(); } @@ -734,9 +795,10 @@ public class BubbleStackView extends FrameLayout { new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); ViewClippingUtil.setClippingDeactivated(bubble.getIconView(), true, mClippingParameters); animateInFlyoutForBubble(bubble); + updatePointerPosition(); + updateOverflowBtnVisibility( /*apply */ true); requestUpdate(); logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__POSTED); - updatePointerPosition(); } // via BubbleData.Listener @@ -753,7 +815,29 @@ public class BubbleStackView extends FrameLayout { } else { Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble); } - updatePointerPosition(); + updateOverflowBtnVisibility(/* apply */ true); + } + + private void updateOverflowBtnVisibility(boolean apply) { + if (!BubbleExperimentConfig.allowBubbleOverflow(mContext)) { + return; + } + if (mIsExpanded) { + if (DEBUG_BUBBLE_STACK_VIEW) { + Log.d(TAG, "Show overflow button."); + } + mOverflowBtn.setVisibility(VISIBLE); + if (apply) { + mExpandedAnimationController.expandFromStack(() -> { + updatePointerPosition(); + } /* after */); + } + } else { + if (DEBUG_BUBBLE_STACK_VIEW) { + Log.d(TAG, "Collapsed. Hide overflow button."); + } + mOverflowBtn.setVisibility(GONE); + } } // via BubbleData.Listener @@ -794,7 +878,9 @@ public class BubbleStackView extends FrameLayout { // expanded view becomes visible on the screen. See b/126856255 mExpandedViewContainer.setAlpha(0.0f); mSurfaceSynchronizer.syncSurfaceAndRun(() -> { - if (previouslySelected != null) { + if (previouslySelected == null) { + mOverflowExpandedView.setContentVisibility(false); + } else { previouslySelected.setContentVisibility(false); } updateExpandedBubble(); @@ -865,7 +951,7 @@ public class BubbleStackView extends FrameLayout { if (mIsExpanded) { if (isIntersecting(mBubbleContainer, x, y)) { // Could be tapping or dragging a bubble while expanded - for (int i = 0; i < mBubbleContainer.getChildCount(); i++) { + for (int i = 0; i < getBubbleCount(); i++) { BadgedImageView view = (BadgedImageView) mBubbleContainer.getChildAt(i); if (isIntersecting(view, x, y)) { return view; @@ -953,6 +1039,12 @@ public class BubbleStackView extends FrameLayout { final Bubble previouslySelected = mExpandedBubble; beforeExpandedViewAnimation(); + if (DEBUG_BUBBLE_STACK_VIEW) { + Log.d(TAG, "animateCollapse"); + Log.d(TAG, BubbleDebugConfig.formatBubblesString(this.getBubblesOnScreen(), + this.getExpandedBubble())); + } + updateOverflowBtnVisibility(/* apply */ false); mBubbleContainer.cancelAllAnimations(); mExpandedAnimationController.collapseBackToStack( mStackAnimationController.getStackPositionAlongNearestHorizontalEdge() @@ -960,7 +1052,11 @@ public class BubbleStackView extends FrameLayout { () -> { mBubbleContainer.setActiveController(mStackAnimationController); afterExpandedViewAnimation(); - previouslySelected.setContentVisibility(false); + if (previouslySelected == null) { + mOverflowExpandedView.setContentVisibility(false); + } else { + previouslySelected.setContentVisibility(false); + } }); mExpandedViewXAnim.animateToFinalPosition(getCollapsedX()); @@ -975,12 +1071,12 @@ public class BubbleStackView extends FrameLayout { beforeExpandedViewAnimation(); mBubbleContainer.setActiveController(mExpandedAnimationController); + updateOverflowBtnVisibility(/* apply */ false); mExpandedAnimationController.expandFromStack(() -> { updatePointerPosition(); afterExpandedViewAnimation(); } /* after */); - mExpandedViewContainer.setTranslationX(getCollapsedX()); mExpandedViewContainer.setTranslationY(getCollapsedY()); mExpandedViewContainer.setAlpha(0f); @@ -1011,7 +1107,7 @@ public class BubbleStackView extends FrameLayout { /** Return the BubbleView at the given index from the bubble container. */ public BadgedImageView getBubbleAt(int i) { - return mBubbleContainer.getChildCount() > i + return getBubbleCount() > i ? (BadgedImageView) mBubbleContainer.getChildAt(i) : null; } @@ -1063,9 +1159,11 @@ public class BubbleStackView extends FrameLayout { Log.d(TAG, "onDragStart()"); } if (mIsExpanded || mIsExpansionAnimating) { + if (DEBUG_BUBBLE_STACK_VIEW) { + Log.d(TAG, "mIsExpanded or mIsExpansionAnimating"); + } return; } - hideBubbleMenu(); mStackAnimationController.cancelStackPositionAnimations(); mBubbleContainer.setActiveController(mStackAnimationController); @@ -1473,7 +1571,7 @@ public class BubbleStackView extends FrameLayout { return; } if (!mIsExpanded) { - if (mBubbleContainer.getChildCount() > 0) { + if (getBubbleCount() > 0) { mBubbleContainer.getChildAt(0).getBoundsOnScreen(outRect); } // Increase the touch target size of the bubble @@ -1526,10 +1624,14 @@ public class BubbleStackView extends FrameLayout { Log.d(TAG, "updateExpandedBubble()"); } mExpandedViewContainer.removeAllViews(); - if (mExpandedBubble != null && mIsExpanded) { - mExpandedViewContainer.addView(mExpandedBubble.getExpandedView()); - mExpandedBubble.getExpandedView().populateExpandedView(); - mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE); + if (mIsExpanded) { + BubbleExpandedView bev = mOverflowExpandedView; + if (mExpandedBubble != null) { + bev = mExpandedBubble.getExpandedView(); + } + mExpandedViewContainer.addView(bev); + bev.populateExpandedView(); + mExpandedViewContainer.setVisibility(VISIBLE); mExpandedViewContainer.setAlpha(1.0f); } } @@ -1545,7 +1647,11 @@ public class BubbleStackView extends FrameLayout { if (!mExpandedViewYAnim.isRunning()) { // We're not animating so set the value mExpandedViewContainer.setTranslationY(y); - mExpandedBubble.getExpandedView().updateView(); + if (mExpandedBubble == null) { + mOverflowExpandedView.updateView(); + } else { + mExpandedBubble.getExpandedView().updateView(); + } } else { // We are animating so update the value; there is an end listener on the animator // that will ensure expandedeView.updateView gets called. @@ -1559,7 +1665,7 @@ public class BubbleStackView extends FrameLayout { /** Sets the appropriate Z-order and dot position for each bubble in the stack. */ private void updateBubbleZOrdersAndDotPosition(boolean animate) { - int bubbleCount = mBubbleContainer.getChildCount(); + int bubbleCount = getBubbleCount(); for (int i = 0; i < bubbleCount; i++) { BadgedImageView bv = (BadgedImageView) mBubbleContainer.getChildAt(i); bv.setZ((mMaxBubbles * mBubbleElevation) - i); @@ -1571,22 +1677,16 @@ public class BubbleStackView extends FrameLayout { } private void updatePointerPosition() { - if (DEBUG_BUBBLE_STACK_VIEW) { - Log.d(TAG, "updatePointerPosition()"); - } - Bubble expandedBubble = getExpandedBubble(); if (expandedBubble == null) { return; } - int index = getBubbleIndex(expandedBubble); float bubbleLeftFromScreenLeft = mExpandedAnimationController.getBubbleLeft(index); float halfBubble = mBubbleSize / 2f; float bubbleCenter = bubbleLeftFromScreenLeft + halfBubble; // Padding might be adjusted for insets, so get it directly from the view bubbleCenter -= mExpandedViewContainer.getPaddingLeft(); - expandedBubble.getExpandedView().setPointerPosition(bubbleCenter); } @@ -1594,6 +1694,10 @@ public class BubbleStackView extends FrameLayout { * @return the number of bubbles in the stack view. */ public int getBubbleCount() { + if (BubbleExperimentConfig.allowBubbleOverflow(mContext)) { + // Subtract 1 for the overflow button which is always in the bubble container. + return mBubbleContainer.getChildCount() - 1; + } return mBubbleContainer.getChildCount(); } @@ -1680,13 +1784,17 @@ public class BubbleStackView extends FrameLayout { if (!isExpanded()) { return false; } - return mExpandedBubble.getExpandedView().performBackPressIfNeeded(); + if (mExpandedBubble == null) { + return mOverflowExpandedView.performBackPressIfNeeded(); + } else { + return mExpandedBubble.getExpandedView().performBackPressIfNeeded(); + } } /** For debugging only */ List<Bubble> getBubblesOnScreen() { List<Bubble> bubbles = new ArrayList<>(); - for (int i = 0; i < mBubbleContainer.getChildCount(); i++) { + for (int i = 0; i < getBubbleCount(); i++) { View child = mBubbleContainer.getChildAt(i); if (child instanceof BadgedImageView) { String key = ((BadgedImageView) child).getKey(); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java index e7055847f034..55a7a11987fc 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java @@ -157,6 +157,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask PackageManager pm = c.getPackageManager(); ApplicationInfo appInfo; Drawable badgedIcon; + Drawable appIcon; try { appInfo = pm.getApplicationInfo( packageName, @@ -167,7 +168,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask if (appInfo != null) { info.appName = String.valueOf(pm.getApplicationLabel(appInfo)); } - Drawable appIcon = pm.getApplicationIcon(packageName); + appIcon = pm.getApplicationIcon(packageName); badgedIcon = pm.getUserBadgedIcon(appIcon, sbn.getUser()); } catch (PackageManager.NameNotFoundException exception) { // If we can't find package... don't think we should show the bubble. @@ -178,6 +179,11 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask // Badged bubble image Drawable bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo, b.getEntry().getBubbleMetadata()); + if (bubbleDrawable == null) { + // Default to app icon + bubbleDrawable = appIcon; + } + BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon); info.badgedBubbleImage = iconFactory.getBubbleBitmap(bubbleDrawable, badgeBitmapInfo).icon; @@ -240,7 +246,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask bubbleMessage.senderName = sender != null ? sender.getName() : null; - bubbleMessage.senderAvatar = sender != null + bubbleMessage.senderAvatar = sender != null && sender.getIcon() != null ? sender.getIcon().loadDrawable(context) : null; return bubbleMessage; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java index 6528f3762473..aa549dc23f9b 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java @@ -31,6 +31,7 @@ import androidx.dynamicanimation.animation.SpringForce; import com.android.systemui.Interpolators; import com.android.systemui.R; +import com.android.systemui.bubbles.BubbleExperimentConfig; import com.google.android.collect.Sets; @@ -67,11 +68,13 @@ public class ExpandedAnimationController private float mBubblePaddingTop; /** Size of each bubble. */ private float mBubbleSizePx; + /** Space between bubbles in row above expanded view. */ + private float mSpaceBetweenBubbles; /** Height of the status bar. */ private float mStatusBarHeight; /** Size of display. */ private Point mDisplaySize; - /** Max number of bubbles shown in row above expanded view.*/ + /** Max number of bubbles shown in row above expanded view. */ private int mBubblesMaxRendered; /** What the current screen orientation is. */ private int mScreenOrientation; @@ -81,7 +84,7 @@ public class ExpandedAnimationController private boolean mAnimatingExpand = false; private boolean mAnimatingCollapse = false; - private Runnable mAfterExpand; + private @Nullable Runnable mAfterExpand; private Runnable mAfterCollapse; private PointF mCollapsePoint; @@ -116,7 +119,7 @@ public class ExpandedAnimationController /** * Animates expanding the bubbles into a row along the top of the screen. */ - public void expandFromStack(Runnable after) { + public void expandFromStack(@Nullable Runnable after) { mAnimatingCollapse = false; mAnimatingExpand = true; mAfterExpand = after; @@ -358,10 +361,10 @@ public class ExpandedAnimationController } final WindowInsets insets = mLayout.getRootWindowInsets(); return mBubblePaddingTop + Math.max( - mStatusBarHeight, - insets.getDisplayCutout() != null - ? insets.getDisplayCutout().getSafeInsetTop() - : 0); + mStatusBarHeight, + insets.getDisplayCutout() != null + ? insets.getDisplayCutout().getSafeInsetTop() + : 0); } /** Description of current animation controller state. */ @@ -384,6 +387,11 @@ public class ExpandedAnimationController res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered); + // Includes overflow button. + float totalGapWidth = getWidthForDisplayingBubbles() - (mExpandedViewPadding * 2) + - (mBubblesMaxRendered + 1) * mBubbleSizePx; + mSpaceBetweenBubbles = totalGapWidth / mBubblesMaxRendered; + // Ensure that all child views are at 1x scale, and visible, in case they were animating // in. mLayout.setVisibility(View.VISIBLE); @@ -494,7 +502,7 @@ public class ExpandedAnimationController * @return Bubble left x from left edge of screen. */ public float getBubbleLeft(int index) { - final float bubbleFromRowLeft = index * (mBubbleSizePx + getSpaceBetweenBubbles()); + final float bubbleFromRowLeft = index * (mBubbleSizePx + mSpaceBetweenBubbles); return getRowLeft() + bubbleFromRowLeft; } @@ -505,7 +513,7 @@ public class ExpandedAnimationController * * @return the desired width to display the expanded bubbles in. */ - private float getWidthForDisplayingBubbles() { + public float getWidthForDisplayingBubbles() { final float availableWidth = getAvailableScreenWidth(true /* includeStableInsets */); if (mScreenOrientation == Configuration.ORIENTATION_LANDSCAPE) { // display size y in landscape will be the smaller dimension of the screen @@ -519,7 +527,7 @@ public class ExpandedAnimationController * Determines the available screen width without the cutout. * * @param subtractStableInsets Whether or not stable insets should also be removed from the - * returned width. + * returned width. * @return the total screen width available accounting for cutouts and insets, * iff {@param includeStableInsets} is true. */ @@ -546,35 +554,13 @@ public class ExpandedAnimationController if (mLayout == null) { return 0; } - - int bubbleCount = mLayout.getChildCount(); - - final float totalBubbleWidth = bubbleCount * mBubbleSizePx; - final float totalGapWidth = (bubbleCount - 1) * getSpaceBetweenBubbles(); - final float rowWidth = totalGapWidth + totalBubbleWidth; + float rowWidth = (mLayout.getChildCount() * mBubbleSizePx) + + ((mLayout.getChildCount() - 1) * mSpaceBetweenBubbles); // This display size we're using includes the size of the insets, we want the true // center of the display minus the notch here, which means we should include the // stable insets (e.g. status bar, nav bar) in this calculation. final float trueCenter = getAvailableScreenWidth(false /* subtractStableInsets */) / 2f; - final float halfRow = rowWidth / 2f; - final float rowLeft = trueCenter - halfRow; - - return rowLeft; - } - - /** - * @return Space between bubbles in row above expanded view. - */ - private float getSpaceBetweenBubbles() { - final float rowMargins = mExpandedViewPadding * 2; - final float maxRowWidth = getWidthForDisplayingBubbles() - rowMargins; - - final float totalBubbleWidth = mBubblesMaxRendered * mBubbleSizePx; - final float totalGapWidth = maxRowWidth - totalBubbleWidth; - - final int gapCount = mBubblesMaxRendered - 1; - final float gapWidth = totalGapWidth / gapCount; - return gapWidth; + return trueCenter - (rowWidth / 2f); } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt index e6cdf50580d8..53841e2f144b 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt @@ -18,4 +18,8 @@ package com.android.systemui.controls import android.service.controls.Control -data class ControlStatus(val control: Control, val favorite: Boolean, val removed: Boolean = false)
\ No newline at end of file +data class ControlStatus( + val control: Control, + val favorite: Boolean, + val removed: Boolean = false +)
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt index 265ddd8043b6..588ef5c4e68f 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt @@ -22,7 +22,7 @@ import com.android.settingslib.applications.DefaultAppInfo class ControlsServiceInfo( context: Context, - serviceInfo: ServiceInfo + val serviceInfo: ServiceInfo ) : DefaultAppInfo( context, context.packageManager, diff --git a/packages/SystemUI/src/com/android/systemui/controls/UserAwareController.kt b/packages/SystemUI/src/com/android/systemui/controls/UserAwareController.kt new file mode 100644 index 000000000000..4f39f2255a75 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/UserAwareController.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls + +import android.os.UserHandle + +interface UserAwareController { + + fun changeUser(newUser: UserHandle) {} + val currentUserId: Int +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt index 6b7fc4b7e827..12c3ce9c69ee 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt @@ -19,8 +19,9 @@ package com.android.systemui.controls.controller import android.content.ComponentName import android.service.controls.Control import android.service.controls.actions.ControlAction +import com.android.systemui.controls.UserAwareController -interface ControlsBindingController { +interface ControlsBindingController : UserAwareController { fun bindAndLoad(component: ComponentName, callback: (List<Control>) -> Unit) fun bindServices(components: List<ComponentName>) fun subscribe(controls: List<ControlInfo>) diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt index 80e48b925fc9..0a2a9255c3ea 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt @@ -19,8 +19,12 @@ package com.android.systemui.controls.controller import android.content.ComponentName import android.content.Context import android.os.IBinder +import android.os.UserHandle import android.service.controls.Control -import android.service.controls.IControlsProviderCallback +import android.service.controls.IControlsActionCallback +import android.service.controls.IControlsLoadCallback +import android.service.controls.IControlsSubscriber +import android.service.controls.IControlsSubscription import android.service.controls.actions.ControlAction import android.util.ArrayMap import android.util.Log @@ -47,32 +51,52 @@ open class ControlsBindingControllerImpl @Inject constructor( private val refreshing = AtomicBoolean(false) + private var currentUser = context.user + + override val currentUserId: Int + get() = currentUser.identifier + @GuardedBy("componentMap") private val tokenMap: MutableMap<IBinder, ControlsProviderLifecycleManager> = ArrayMap<IBinder, ControlsProviderLifecycleManager>() @GuardedBy("componentMap") - private val componentMap: MutableMap<ComponentName, ControlsProviderLifecycleManager> = - ArrayMap<ComponentName, ControlsProviderLifecycleManager>() + private val componentMap: MutableMap<Key, ControlsProviderLifecycleManager> = + ArrayMap<Key, ControlsProviderLifecycleManager>() - private val serviceCallback = object : IControlsProviderCallback.Stub() { - override fun onLoad(token: IBinder, controls: MutableList<Control>) { + private val loadCallbackService = object : IControlsLoadCallback.Stub() { + override fun accept(token: IBinder, controls: MutableList<Control>) { backgroundExecutor.execute(OnLoadRunnable(token, controls)) } + } - override fun onRefreshState(token: IBinder, controlStates: List<Control>) { + private val actionCallbackService = object : IControlsActionCallback.Stub() { + override fun accept( + token: IBinder, + controlId: String, + @ControlAction.ResponseResult response: Int + ) { + backgroundExecutor.execute(OnActionResponseRunnable(token, controlId, response)) + } + } + + private val subscriberService = object : IControlsSubscriber.Stub() { + override fun onSubscribe(token: IBinder, subs: IControlsSubscription) { + backgroundExecutor.execute(OnSubscribeRunnable(token, subs)) + } + + override fun onNext(token: IBinder, c: Control) { if (!refreshing.get()) { Log.d(TAG, "Refresh outside of window for token:$token") } else { - backgroundExecutor.execute(OnRefreshStateRunnable(token, controlStates)) + backgroundExecutor.execute(OnNextRunnable(token, c)) } } + override fun onError(token: IBinder, s: String) { + backgroundExecutor.execute(OnErrorRunnable(token, s)) + } - override fun onControlActionResponse( - token: IBinder, - controlId: String, - @ControlAction.ResponseResult response: Int - ) { - backgroundExecutor.execute(OnActionResponseRunnable(token, controlId, response)) + override fun onComplete(token: IBinder) { + backgroundExecutor.execute(OnCompleteRunnable(token)) } } @@ -82,7 +106,10 @@ open class ControlsBindingControllerImpl @Inject constructor( return ControlsProviderLifecycleManager( context, backgroundExecutor, - serviceCallback, + loadCallbackService, + actionCallbackService, + subscriberService, + currentUser, component ) } @@ -90,7 +117,7 @@ open class ControlsBindingControllerImpl @Inject constructor( private fun retrieveLifecycleManager(component: ComponentName): ControlsProviderLifecycleManager { synchronized(componentMap) { - val provider = componentMap.getOrPut(component) { + val provider = componentMap.getOrPut(Key(component, currentUser)) { createProviderManager(component) } tokenMap.putIfAbsent(provider.token, provider) @@ -117,7 +144,7 @@ open class ControlsBindingControllerImpl @Inject constructor( val providersWithFavorites = controlsByComponentName.keys synchronized(componentMap) { componentMap.forEach { - if (it.key !in providersWithFavorites) { + if (it.key.component !in providersWithFavorites) { backgroundExecutor.execute { it.value.unbindService() } } } @@ -143,10 +170,40 @@ open class ControlsBindingControllerImpl @Inject constructor( override fun bindServices(components: List<ComponentName>) { components.forEach { val provider = retrieveLifecycleManager(it) - backgroundExecutor.execute { provider.bindPermanently() } + backgroundExecutor.execute { provider.bindService() } + } + } + + override fun changeUser(newUser: UserHandle) { + if (newUser == currentUser) return + synchronized(componentMap) { + unbindAllProvidersLocked() // unbind all providers from the old user + } + refreshing.set(false) + currentUser = newUser + } + + private fun unbindAllProvidersLocked() { + componentMap.values.forEach { + if (it.user == currentUser) { + it.unbindService() + } } } + override fun toString(): String { + return StringBuilder(" ControlsBindingController:\n").apply { + append(" refreshing=${refreshing.get()}\n") + append(" currentUser=$currentUser\n") + append(" Providers:\n") + synchronized(componentMap) { + componentMap.values.forEach { + append(" $it\n") + } + } + }.toString() + } + private abstract inner class CallbackRunnable(val token: IBinder) : Runnable { protected val provider: ControlsProviderLifecycleManager? = synchronized(componentMap) { @@ -163,6 +220,10 @@ open class ControlsBindingControllerImpl @Inject constructor( Log.e(TAG, "No provider found for token:$token") return } + if (provider.user != currentUser) { + Log.e(TAG, "User ${provider.user} is not current user") + return + } synchronized(componentMap) { if (token !in tokenMap.keys) { Log.e(TAG, "Provider for token:$token does not exist anymore") @@ -172,20 +233,59 @@ open class ControlsBindingControllerImpl @Inject constructor( provider.lastLoadCallback?.invoke(list) ?: run { Log.w(TAG, "Null callback") } - provider.maybeUnbindAndRemoveCallback() + provider.unbindService() } } - private inner class OnRefreshStateRunnable( + private inner class OnNextRunnable( token: IBinder, - val list: List<Control> + val control: Control ) : CallbackRunnable(token) { override fun run() { if (!refreshing.get()) { Log.d(TAG, "onRefresh outside of window from:${provider?.componentName}") } + if (provider?.user != currentUser) { + Log.e(TAG, "User ${provider?.user} is not current user") + return + } + provider?.let { + lazyController.get().refreshStatus(it.componentName, control) + } + } + } + + private inner class OnSubscribeRunnable( + token: IBinder, + val subscription: IControlsSubscription + ) : CallbackRunnable(token) { + override fun run() { + if (!refreshing.get()) { + Log.d(TAG, "onRefresh outside of window from '${provider?.componentName}'") + } + provider?.let { + it.startSubscription(subscription) + } + } + } + + private inner class OnCompleteRunnable( + token: IBinder + ) : CallbackRunnable(token) { + override fun run() { + provider?.let { + Log.i(TAG, "onComplete receive from '${provider.componentName}'") + } + } + } + + private inner class OnErrorRunnable( + token: IBinder, + val error: String + ) : CallbackRunnable(token) { + override fun run() { provider?.let { - lazyController.get().refreshStatus(it.componentName, list) + Log.e(TAG, "onError receive from '${provider.componentName}': $error") } } } @@ -196,9 +296,15 @@ open class ControlsBindingControllerImpl @Inject constructor( @ControlAction.ResponseResult val response: Int ) : CallbackRunnable(token) { override fun run() { + if (provider?.user != currentUser) { + Log.e(TAG, "User ${provider?.user} is not current user") + return + } provider?.let { lazyController.get().onActionResponse(it.componentName, controlId, response) } } } -}
\ No newline at end of file +} + +private data class Key(val component: ComponentName, val user: UserHandle)
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt index 4d958224e917..b02de4500043 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt @@ -20,8 +20,9 @@ import android.content.ComponentName import android.service.controls.Control import android.service.controls.actions.ControlAction import com.android.systemui.controls.ControlStatus +import com.android.systemui.controls.UserAwareController -interface ControlsController { +interface ControlsController : UserAwareController { val available: Boolean fun getFavoriteControls(): List<ControlInfo> @@ -30,11 +31,11 @@ interface ControlsController { fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean) fun unsubscribe() fun action(controlInfo: ControlInfo, action: ControlAction) - fun refreshStatus(componentName: ComponentName, controls: List<Control>) + fun refreshStatus(componentName: ComponentName, control: Control) fun onActionResponse( componentName: ComponentName, controlId: String, @ControlAction.ResponseResult response: Int ) fun clearFavorites() -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt index 7e328e467129..6ff1cf8474d3 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -17,10 +17,13 @@ package com.android.systemui.controls.controller import android.app.PendingIntent +import android.content.BroadcastReceiver import android.content.ComponentName import android.content.Context import android.content.Intent +import android.content.IntentFilter import android.os.Environment +import android.os.UserHandle import android.provider.Settings import android.service.controls.Control import android.service.controls.actions.ControlAction @@ -29,14 +32,17 @@ import android.util.Log import com.android.internal.annotations.GuardedBy import com.android.systemui.DumpController import com.android.systemui.Dumpable +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.ControlStatus import com.android.systemui.controls.management.ControlsFavoritingActivity +import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.util.concurrency.DelayableExecutor import java.io.FileDescriptor import java.io.PrintWriter import java.util.Optional +import java.util.concurrent.TimeUnit import javax.inject.Inject import javax.inject.Singleton @@ -46,35 +52,101 @@ class ControlsControllerImpl @Inject constructor ( @Background private val executor: DelayableExecutor, private val uiController: ControlsUiController, private val bindingController: ControlsBindingController, - private val optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>, + private val listingController: ControlsListingController, + broadcastDispatcher: BroadcastDispatcher, + optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>, dumpController: DumpController ) : Dumpable, ControlsController { companion object { private const val TAG = "ControlsControllerImpl" const val CONTROLS_AVAILABLE = "systemui.controls_available" + const val USER_CHANGE_RETRY_DELAY = 500L // ms } - override val available = Settings.Secure.getInt( + // Map of map: ComponentName -> (String -> ControlInfo). + // Only for current user + @GuardedBy("currentFavorites") + private val currentFavorites = ArrayMap<ComponentName, MutableMap<String, ControlInfo>>() + + private var userChanging = true + override var available = Settings.Secure.getInt( context.contentResolver, CONTROLS_AVAILABLE, 0) != 0 - val persistenceWrapper = optionalWrapper.orElseGet { + private set + + private var currentUser = context.user + override val currentUserId + get() = currentUser.identifier + + private val persistenceWrapper = optionalWrapper.orElseGet { ControlsFavoritePersistenceWrapper( Environment.buildPath( - context.filesDir, - ControlsFavoritePersistenceWrapper.FILE_NAME), + context.filesDir, + ControlsFavoritePersistenceWrapper.FILE_NAME + ), executor ) } - // Map of map: ComponentName -> (String -> ControlInfo) - @GuardedBy("currentFavorites") - private val currentFavorites = ArrayMap<ComponentName, MutableMap<String, ControlInfo>>() + private fun setValuesForUser(newUser: UserHandle) { + Log.d(TAG, "Changing to user: $newUser") + currentUser = newUser + val userContext = context.createContextAsUser(currentUser, 0) + val fileName = Environment.buildPath( + userContext.filesDir, ControlsFavoritePersistenceWrapper.FILE_NAME) + persistenceWrapper.changeFile(fileName) + available = Settings.Secure.getIntForUser( + context.contentResolver, CONTROLS_AVAILABLE, 0) != 0 + synchronized(currentFavorites) { + currentFavorites.clear() + } + if (available) { + loadFavorites() + } + bindingController.changeUser(newUser) + listingController.changeUser(newUser) + userChanging = false + } + + private val userSwitchReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == Intent.ACTION_USER_SWITCHED) { + userChanging = true + val newUser = + UserHandle.of(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, sendingUserId)) + if (currentUser == newUser) { + userChanging = false + return + } + setValuesForUser(newUser) + } + } + } init { + dumpController.registerDumpable(this) if (available) { - dumpController.registerDumpable(this) loadFavorites() } + userChanging = false + broadcastDispatcher.registerReceiver( + userSwitchReceiver, + IntentFilter(Intent.ACTION_USER_SWITCHED), + executor, + UserHandle.ALL + ) + } + + private fun confirmAvailability(): Boolean { + if (userChanging) { + Log.w(TAG, "Controls not available while user is changing") + return false + } + if (!available) { + Log.d(TAG, "Controls not available") + return false + } + return true } private fun loadFavorites() { @@ -91,8 +163,18 @@ class ControlsControllerImpl @Inject constructor ( componentName: ComponentName, callback: (List<ControlStatus>) -> Unit ) { - if (!available) { - Log.d(TAG, "Controls not available") + if (!confirmAvailability()) { + if (userChanging) { + // Try again later, userChanging should not last forever. If so, we have bigger + // problems + executor.executeDelayed( + { loadForComponent(componentName, callback) }, + USER_CHANGE_RETRY_DELAY, + TimeUnit.MILLISECONDS + ) + } else { + callback(emptyList()) + } return } bindingController.bindAndLoad(componentName) { @@ -158,10 +240,7 @@ class ControlsControllerImpl @Inject constructor ( } override fun subscribeToFavorites() { - if (!available) { - Log.d(TAG, "Controls not available") - return - } + if (!confirmAvailability()) return // Make a copy of the favorites list val favorites = synchronized(currentFavorites) { currentFavorites.flatMap { it.value.values.toList() } @@ -170,18 +249,12 @@ class ControlsControllerImpl @Inject constructor ( } override fun unsubscribe() { - if (!available) { - Log.d(TAG, "Controls not available") - return - } + if (!confirmAvailability()) return bindingController.unsubscribe() } override fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean) { - if (!available) { - Log.d(TAG, "Controls not available") - return - } + if (!confirmAvailability()) return var changed = false val listOfControls = synchronized(currentFavorites) { if (state) { @@ -210,45 +283,41 @@ class ControlsControllerImpl @Inject constructor ( } } - override fun refreshStatus(componentName: ComponentName, controls: List<Control>) { - if (!available) { + override fun refreshStatus(componentName: ComponentName, control: Control) { + if (!confirmAvailability()) { Log.d(TAG, "Controls not available") return } executor.execute { synchronized(currentFavorites) { - val changed = updateFavoritesLocked(componentName, controls) + val changed = updateFavoritesLocked(componentName, listOf(control)) if (changed) { persistenceWrapper.storeFavorites(favoritesAsListLocked()) } } } - uiController.onRefreshState(componentName, controls) + uiController.onRefreshState(componentName, listOf(control)) } override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) { - if (!available) { - Log.d(TAG, "Controls not available") - return - } + if (!confirmAvailability()) return uiController.onActionResponse(componentName, controlId, response) } override fun getFavoriteControls(): List<ControlInfo> { - if (!available) { - Log.d(TAG, "Controls not available") - return emptyList() - } + if (!confirmAvailability()) return emptyList() synchronized(currentFavorites) { return favoritesAsListLocked() } } override fun action(controlInfo: ControlInfo, action: ControlAction) { + if (!confirmAvailability()) return bindingController.action(controlInfo, action) } override fun clearFavorites() { + if (!confirmAvailability()) return val changed = synchronized(currentFavorites) { currentFavorites.isNotEmpty().also { currentFavorites.clear() @@ -261,6 +330,9 @@ class ControlsControllerImpl @Inject constructor ( override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { pw.println("ControlsController state:") + pw.println(" Available: $available") + pw.println(" Changing users: $userChanging") + pw.println(" Current user: ${currentUser.identifier}") pw.println(" Favorites:") synchronized(currentFavorites) { currentFavorites.forEach { @@ -269,5 +341,6 @@ class ControlsControllerImpl @Inject constructor ( } } } + pw.println(bindingController.toString()) } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt index 6f2d71fd0f59..7d1df14bbf1b 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt @@ -16,7 +16,6 @@ package com.android.systemui.controls.controller -import android.app.ActivityManager import android.content.ComponentName import android.util.AtomicFile import android.util.Log @@ -32,8 +31,8 @@ import java.io.FileNotFoundException import java.io.IOException class ControlsFavoritePersistenceWrapper( - val file: File, - val executor: DelayableExecutor + private var file: File, + private var executor: DelayableExecutor ) { companion object { @@ -47,11 +46,13 @@ class ControlsFavoritePersistenceWrapper( private const val TAG_TYPE = "type" } - val currentUser: Int - get() = ActivityManager.getCurrentUser() + fun changeFile(fileName: File) { + file = fileName + } fun storeFavorites(list: List<ControlInfo>) { executor.execute { + Log.d(TAG, "Saving data to file: $file") val atomicFile = AtomicFile(file) val writer = try { atomicFile.startWrite() @@ -98,6 +99,7 @@ class ControlsFavoritePersistenceWrapper( return emptyList() } try { + Log.d(TAG, "Reading data from file: $file") val parser = Xml.newPullParser() parser.setInput(reader, null) return parseXml(parser) @@ -111,7 +113,7 @@ class ControlsFavoritePersistenceWrapper( } private fun parseXml(parser: XmlPullParser): List<ControlInfo> { - var type: Int = 0 + var type = 0 val infos = mutableListOf<ControlInfo>() while (parser.next().also { type = it } != XmlPullParser.END_DOCUMENT) { if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { @@ -123,9 +125,9 @@ class ControlsFavoritePersistenceWrapper( parser.getAttributeValue(null, TAG_COMPONENT)) val id = parser.getAttributeValue(null, TAG_ID) val title = parser.getAttributeValue(null, TAG_TITLE) - val type = parser.getAttributeValue(null, TAG_TYPE)?.toInt() - if (component != null && id != null && title != null && type != null) { - infos.add(ControlInfo(component, id, title, type)) + val deviceType = parser.getAttributeValue(null, TAG_TYPE)?.toInt() + if (component != null && id != null && title != null && deviceType != null) { + infos.add(ControlInfo(component, id, title, deviceType)) } } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt index 79057ad25b20..b4bd82c84e1a 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt @@ -24,12 +24,15 @@ import android.os.Binder import android.os.Bundle import android.os.IBinder import android.os.RemoteException +import android.os.UserHandle import android.service.controls.Control -import android.service.controls.ControlsProviderService.CALLBACK_BINDER import android.service.controls.ControlsProviderService.CALLBACK_BUNDLE import android.service.controls.ControlsProviderService.CALLBACK_TOKEN +import android.service.controls.IControlsActionCallback +import android.service.controls.IControlsLoadCallback import android.service.controls.IControlsProvider -import android.service.controls.IControlsProviderCallback +import android.service.controls.IControlsSubscriber +import android.service.controls.IControlsSubscription import android.service.controls.actions.ControlAction import android.util.ArraySet import android.util.Log @@ -41,19 +44,22 @@ typealias LoadCallback = (List<Control>) -> Unit class ControlsProviderLifecycleManager( private val context: Context, private val executor: DelayableExecutor, - private val serviceCallback: IControlsProviderCallback.Stub, + private val loadCallbackService: IControlsLoadCallback.Stub, + private val actionCallbackService: IControlsActionCallback.Stub, + private val subscriberService: IControlsSubscriber.Stub, + val user: UserHandle, val componentName: ComponentName ) : IBinder.DeathRecipient { var lastLoadCallback: LoadCallback? = null private set val token: IBinder = Binder() - private var unbindImmediate = false + @GuardedBy("subscriptions") + private val subscriptions = mutableListOf<IControlsSubscription>() private var requiresBound = false - private var isBound = false @GuardedBy("queuedMessages") private val queuedMessages: MutableSet<Message> = ArraySet() - private var wrapper: ControlsProviderServiceWrapper? = null + private var wrapper: ServiceWrapper? = null private var bindTryCount = 0 private val TAG = javaClass.simpleName private var onLoadCanceller: Runnable? = null @@ -61,12 +67,12 @@ class ControlsProviderLifecycleManager( companion object { private const val MSG_LOAD = 0 private const val MSG_SUBSCRIBE = 1 - private const val MSG_UNSUBSCRIBE = 2 - private const val MSG_ON_ACTION = 3 - private const val MSG_UNBIND = 4 + private const val MSG_ACTION = 2 + private const val MSG_UNBIND = 3 private const val BIND_RETRY_DELAY = 1000L // ms private const val LOAD_TIMEOUT = 5000L // ms private const val MAX_BIND_RETRIES = 5 + private const val MAX_CONTROLS_REQUEST = 100000L private const val DEBUG = true private val BIND_FLAGS = Context.BIND_AUTO_CREATE or Context.BIND_FOREGROUND_SERVICE or Context.BIND_WAIVE_PRIORITY @@ -75,7 +81,6 @@ class ControlsProviderLifecycleManager( private val intent = Intent().apply { component = componentName putExtra(CALLBACK_BUNDLE, Bundle().apply { - putBinder(CALLBACK_BINDER, serviceCallback) putBinder(CALLBACK_TOKEN, token) }) } @@ -91,35 +96,27 @@ class ControlsProviderLifecycleManager( } bindTryCount++ try { - isBound = context.bindService(intent, serviceConnection, BIND_FLAGS) + context.bindServiceAsUser(intent, serviceConnection, BIND_FLAGS, user) } catch (e: SecurityException) { Log.e(TAG, "Failed to bind to service", e) - isBound = false } } else { if (DEBUG) { Log.d(TAG, "Unbinding service $intent") } bindTryCount = 0 - wrapper = null - if (isBound) { + wrapper?.run { context.unbindService(serviceConnection) - isBound = false } + wrapper = null } } - fun bindPermanently() { - unbindImmediate = false - unqueueMessage(Message.Unbind) - bindService(true) - } - private val serviceConnection = object : ServiceConnection { override fun onServiceConnected(name: ComponentName, service: IBinder) { if (DEBUG) Log.d(TAG, "onServiceConnected $name") bindTryCount = 0 - wrapper = ControlsProviderServiceWrapper(IControlsProvider.Stub.asInterface(service)) + wrapper = ServiceWrapper(IControlsProvider.Stub.asInterface(service)) try { service.linkToDeath(this@ControlsProviderLifecycleManager, 0) } catch (_: RemoteException) {} @@ -128,7 +125,7 @@ class ControlsProviderLifecycleManager( override fun onServiceDisconnected(name: ComponentName?) { if (DEBUG) Log.d(TAG, "onServiceDisconnected $name") - isBound = false + wrapper = null bindService(false) } } @@ -147,11 +144,13 @@ class ControlsProviderLifecycleManager( load() } queue.filter { it is Message.Subscribe }.flatMap { (it as Message.Subscribe).list }.run { - subscribe(this) + if (this.isNotEmpty()) { + subscribe(this) + } } queue.filter { it is Message.Action }.forEach { val msg = it as Message.Action - onAction(msg.id, msg.action) + action(msg.id, msg.action) } } @@ -182,87 +181,104 @@ class ControlsProviderLifecycleManager( if (DEBUG) { Log.d(TAG, "load $componentName") } - if (!(wrapper?.load() ?: false)) { + if (!(wrapper?.load(loadCallbackService) ?: false)) { queueMessage(Message.Load) binderDied() } } + private fun invokeOrQueue(f: () -> Unit, msg: Message) { + wrapper?.run { + f() + } ?: run { + queueMessage(msg) + bindService(true) + } + } + fun maybeBindAndLoad(callback: LoadCallback) { unqueueMessage(Message.Unbind) lastLoadCallback = callback onLoadCanceller = executor.executeDelayed({ // Didn't receive a response in time, log and send back empty list Log.d(TAG, "Timeout waiting onLoad for $componentName") - serviceCallback.onLoad(token, emptyList()) + loadCallbackService.accept(token, emptyList()) }, LOAD_TIMEOUT, TimeUnit.MILLISECONDS) - if (isBound) { - load() - } else { - queueMessage(Message.Load) - unbindImmediate = true - bindService(true) - } + + invokeOrQueue(::load, Message.Load) } fun maybeBindAndSubscribe(controlIds: List<String>) { - if (isBound) { - subscribe(controlIds) - } else { - queueMessage(Message.Subscribe(controlIds)) - bindService(true) - } + invokeOrQueue({ subscribe(controlIds) }, Message.Subscribe(controlIds)) } private fun subscribe(controlIds: List<String>) { if (DEBUG) { Log.d(TAG, "subscribe $componentName - $controlIds") } - if (!(wrapper?.subscribe(controlIds) ?: false)) { + if (!(wrapper?.subscribe(controlIds, subscriberService) ?: false)) { queueMessage(Message.Subscribe(controlIds)) binderDied() } } fun maybeBindAndSendAction(controlId: String, action: ControlAction) { - if (isBound) { - onAction(controlId, action) - } else { - queueMessage(Message.Action(controlId, action)) - bindService(true) - } + invokeOrQueue({ action(controlId, action) }, Message.Action(controlId, action)) } - private fun onAction(controlId: String, action: ControlAction) { + private fun action(controlId: String, action: ControlAction) { if (DEBUG) { Log.d(TAG, "onAction $componentName - $controlId") } - if (!(wrapper?.onAction(controlId, action) ?: false)) { + if (!(wrapper?.action(controlId, action, actionCallbackService) ?: false)) { queueMessage(Message.Action(controlId, action)) binderDied() } } + fun startSubscription(subscription: IControlsSubscription) { + synchronized(subscriptions) { + subscriptions.add(subscription) + } + wrapper?.request(subscription, MAX_CONTROLS_REQUEST) + } + fun unsubscribe() { if (DEBUG) { Log.d(TAG, "unsubscribe $componentName") } unqueueMessage(Message.Subscribe(emptyList())) // Removes all subscribe messages - wrapper?.unsubscribe() + + val subs = synchronized(subscriptions) { + ArrayList(subscriptions).also { + subscriptions.clear() + } + } + + subs.forEach { + wrapper?.cancel(it) + } + } + + fun bindService() { + unqueueMessage(Message.Unbind) + bindService(true) } - fun maybeUnbindAndRemoveCallback() { + fun unbindService() { lastLoadCallback = null onLoadCanceller?.run() onLoadCanceller = null - if (unbindImmediate) { - bindService(false) - } + + bindService(false) } - fun unbindService() { - unbindImmediate = true - maybeUnbindAndRemoveCallback() + override fun toString(): String { + return StringBuilder("ControlsProviderLifecycleManager(").apply { + append("component=$componentName") + append(", user=$user") + append(")") + }.toString() } sealed class Message { @@ -277,7 +293,7 @@ class ControlsProviderLifecycleManager( override val type = MSG_SUBSCRIBE } class Action(val id: String, val action: ControlAction) : Message() { - override val type = MSG_ON_ACTION + override val type = MSG_ACTION } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapper.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ServiceWrapper.kt index 882a10d54431..5c812b1347e5 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ServiceWrapper.kt @@ -17,12 +17,17 @@ package com.android.systemui.controls.controller import android.service.controls.actions.ControlAction +import android.service.controls.IControlsActionCallback +import android.service.controls.IControlsLoadCallback import android.service.controls.IControlsProvider +import android.service.controls.IControlsSubscriber +import android.service.controls.IControlsSubscription +import android.service.controls.actions.ControlActionWrapper import android.util.Log -class ControlsProviderServiceWrapper(val service: IControlsProvider) { +class ServiceWrapper(val service: IControlsProvider) { companion object { - private const val TAG = "ControlsProviderServiceWrapper" + private const val TAG = "ServiceWrapper" } private fun callThroughService(block: () -> Unit): Boolean { @@ -30,32 +35,42 @@ class ControlsProviderServiceWrapper(val service: IControlsProvider) { block() return true } catch (ex: Exception) { - Log.d(TAG, "Caught exception from ControlsProviderService", ex) + Log.e(TAG, "Caught exception from ControlsProviderService", ex) return false } } - fun load(): Boolean { + fun load(cb: IControlsLoadCallback): Boolean { return callThroughService { - service.load() + service.load(cb) } } - fun subscribe(controlIds: List<String>): Boolean { + fun subscribe(controlIds: List<String>, subscriber: IControlsSubscriber): Boolean { return callThroughService { - service.subscribe(controlIds) + service.subscribe(controlIds, subscriber) } } - fun unsubscribe(): Boolean { + fun request(subscription: IControlsSubscription, num: Long): Boolean { return callThroughService { - service.unsubscribe() + subscription.request(num) } } - fun onAction(controlId: String, action: ControlAction): Boolean { + fun cancel(subscription: IControlsSubscription): Boolean { return callThroughService { - service.onAction(controlId, action) + subscription.cancel() } } -}
\ No newline at end of file + + fun action( + controlId: String, + action: ControlAction, + cb: IControlsActionCallback + ): Boolean { + return callThroughService { + service.action(controlId, ControlActionWrapper(action), cb) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt index d62bb4def3aa..22c69086cf8c 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt @@ -23,6 +23,7 @@ import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.RecyclerView import com.android.settingslib.widget.CandidateInfo import com.android.systemui.R diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt index 01c4fef67fd4..7ee4fd5b059e 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -22,15 +22,18 @@ import android.os.Bundle import android.view.LayoutInflater import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.controller.ControlInfo import com.android.systemui.controls.controller.ControlsControllerImpl import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.settings.CurrentUserTracker import java.util.concurrent.Executor import javax.inject.Inject class ControlsFavoritingActivity @Inject constructor( @Main private val executor: Executor, - private val controller: ControlsControllerImpl + private val controller: ControlsControllerImpl, + broadcastDispatcher: BroadcastDispatcher ) : Activity() { companion object { @@ -42,11 +45,24 @@ class ControlsFavoritingActivity @Inject constructor( private lateinit var recyclerView: RecyclerView private lateinit var adapter: ControlAdapter + private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) { + private val startingUser = controller.currentUserId + + override fun onUserSwitched(newUserId: Int) { + if (newUserId != startingUser) { + stopTracking() + finish() + } + } + } + + private var component: ComponentName? = null + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val app = intent.getCharSequenceExtra(EXTRA_APP) - val component = intent.getParcelableExtra<ComponentName>(EXTRA_COMPONENT) + component = intent.getParcelableExtra<ComponentName>(EXTRA_COMPONENT) // If we have no component name, there's not much we can do. val callback = component?.let { @@ -68,6 +84,11 @@ class ControlsFavoritingActivity @Inject constructor( } setContentView(recyclerView) + currentUserTracker.startTracking() + } + + override fun onResume() { + super.onResume() component?.let { controller.loadForComponent(it) { executor.execute { diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt index 09e0ce9fea8d..34db684022fb 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt @@ -18,14 +18,17 @@ package com.android.systemui.controls.management import android.content.ComponentName import com.android.settingslib.widget.CandidateInfo +import com.android.systemui.controls.UserAwareController import com.android.systemui.statusbar.policy.CallbackController interface ControlsListingController : - CallbackController<ControlsListingController.ControlsListingCallback> { + CallbackController<ControlsListingController.ControlsListingCallback>, + UserAwareController { fun getCurrentServices(): List<CandidateInfo> fun getAppLabel(name: ComponentName): CharSequence? = "" + @FunctionalInterface interface ControlsListingCallback { fun onServicesUpdated(list: List<CandidateInfo>) } diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt index 937216230123..882382cc4ade 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt @@ -19,6 +19,7 @@ package com.android.systemui.controls.management import android.content.ComponentName import android.content.Context import android.content.pm.ServiceInfo +import android.os.UserHandle import android.service.controls.ControlsProviderService import android.util.Log import com.android.internal.annotations.VisibleForTesting @@ -31,6 +32,16 @@ import java.util.concurrent.Executor import javax.inject.Inject import javax.inject.Singleton +private fun createServiceListing(context: Context): ServiceListing { + return ServiceListing.Builder(context).apply { + setIntentAction(ControlsProviderService.SERVICE_CONTROLS) + setPermission("android.permission.BIND_CONTROLS") + setNoun("Controls Provider") + setSetting("controls_providers") + setTag("controls_providers") + }.build() +} + /** * Provides a listing of components to be used as ControlsServiceProvider. * @@ -43,41 +54,55 @@ import javax.inject.Singleton class ControlsListingControllerImpl @VisibleForTesting constructor( private val context: Context, @Background private val backgroundExecutor: Executor, - private val serviceListing: ServiceListing + private val serviceListingBuilder: (Context) -> ServiceListing ) : ControlsListingController { @Inject constructor(context: Context, executor: Executor): this( context, executor, - ServiceListing.Builder(context) - .setIntentAction(ControlsProviderService.CONTROLS_ACTION) - .setPermission("android.permission.BIND_CONTROLS") - .setNoun("Controls Provider") - .setSetting("controls_providers") - .setTag("controls_providers") - .build() + ::createServiceListing ) + private var serviceListing = serviceListingBuilder(context) + companion object { private const val TAG = "ControlsListingControllerImpl" } private var availableServices = emptyList<ServiceInfo>() - init { - serviceListing.addCallback { - Log.d(TAG, "ServiceConfig reloaded") - availableServices = it.toList() - - backgroundExecutor.execute { - callbacks.forEach { - it.onServicesUpdated(getCurrentServices()) - } + override var currentUserId = context.userId + private set + + private val serviceListingCallback = ServiceListing.Callback { + Log.d(TAG, "ServiceConfig reloaded") + availableServices = it.toList() + + backgroundExecutor.execute { + callbacks.forEach { + it.onServicesUpdated(getCurrentServices()) } } } + init { + serviceListing.addCallback(serviceListingCallback) + } + + override fun changeUser(newUser: UserHandle) { + backgroundExecutor.execute { + callbacks.clear() + availableServices = emptyList() + serviceListing.setListening(false) + serviceListing.removeCallback(serviceListingCallback) + currentUserId = newUser.identifier + val contextForUser = context.createContextAsUser(newUser, 0) + serviceListing = serviceListingBuilder(contextForUser) + serviceListing.addCallback(serviceListingCallback) + } + } + // All operations in background thread private val callbacks = mutableSetOf<ControlsListingController.ControlsListingCallback>() @@ -91,6 +116,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor( */ override fun addCallback(listener: ControlsListingController.ControlsListingCallback) { backgroundExecutor.execute { + Log.d(TAG, "Subscribing callback") callbacks.add(listener) if (callbacks.size == 1) { serviceListing.setListening(true) @@ -108,6 +134,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor( */ override fun removeCallback(listener: ControlsListingController.ControlsListingCallback) { backgroundExecutor.execute { + Log.d(TAG, "Unsubscribing callback") callbacks.remove(listener) if (callbacks.size == 0) { serviceListing.setListening(false) diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt index 69af516b4ac9..5ff949c98806 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt @@ -22,7 +22,10 @@ import android.os.Bundle import android.view.LayoutInflater import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.settings.CurrentUserTracker import com.android.systemui.util.LifecycleActivity import java.util.concurrent.Executor import javax.inject.Inject @@ -32,7 +35,9 @@ import javax.inject.Inject */ class ControlsProviderSelectorActivity @Inject constructor( @Main private val executor: Executor, - private val listingController: ControlsListingController + @Background private val backExecutor: Executor, + private val listingController: ControlsListingController, + broadcastDispatcher: BroadcastDispatcher ) : LifecycleActivity() { companion object { @@ -40,6 +45,16 @@ class ControlsProviderSelectorActivity @Inject constructor( } private lateinit var recyclerView: RecyclerView + private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) { + private val startingUser = listingController.currentUserId + + override fun onUserSwitched(newUserId: Int) { + if (newUserId != startingUser) { + stopTracking() + finish() + } + } + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -50,6 +65,7 @@ class ControlsProviderSelectorActivity @Inject constructor( recyclerView.layoutManager = LinearLayoutManager(applicationContext) setContentView(recyclerView) + currentUserTracker.startTracking() } /** @@ -57,13 +73,17 @@ class ControlsProviderSelectorActivity @Inject constructor( * @param component a component name for a [ControlsProviderService] */ fun launchFavoritingActivity(component: ComponentName?) { - component?.let { - val intent = Intent(applicationContext, ControlsFavoritingActivity::class.java).apply { - putExtra(ControlsFavoritingActivity.EXTRA_APP, listingController.getAppLabel(it)) - putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, it) - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP + backExecutor.execute { + component?.let { + val intent = Intent(applicationContext, ControlsFavoritingActivity::class.java) + .apply { + putExtra(ControlsFavoritingActivity.EXTRA_APP, + listingController.getAppLabel(it)) + putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, it) + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP + } + startActivity(intent) } - startActivity(intent) } } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/Behavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/Behavior.kt new file mode 100644 index 000000000000..44f3fbc58eaf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/Behavior.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.ui + +interface Behavior { + fun apply(cvh: ControlViewHolder, cws: ControlWithState) +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt new file mode 100644 index 000000000000..5519e32d6c10 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.ui + +import android.content.Context +import android.content.Intent +import android.graphics.drawable.ClipDrawable +import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.Icon +import android.graphics.drawable.LayerDrawable +import android.service.controls.Control +import android.service.controls.actions.ControlAction +import android.service.controls.templates.ControlTemplate +import android.service.controls.templates.TemperatureControlTemplate +import android.service.controls.templates.ToggleRangeTemplate +import android.service.controls.templates.ToggleTemplate +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView + +import com.android.systemui.controls.controller.ControlsController +import com.android.systemui.R + +public const val MIN_LEVEL = 0 +public const val MAX_LEVEL = 10000 + +class ControlViewHolder( + val layout: ViewGroup, + val controlsController: ControlsController +) { + val icon: ImageView = layout.requireViewById(R.id.icon) + val status: TextView = layout.requireViewById(R.id.status) + val statusExtra: TextView = layout.requireViewById(R.id.status_extra) + val title: TextView = layout.requireViewById(R.id.title) + val subtitle: TextView = layout.requireViewById(R.id.subtitle) + val context: Context = layout.getContext() + val clipLayer: ClipDrawable + val gd: GradientDrawable + lateinit var cws: ControlWithState + + init { + val ld = layout.getBackground() as LayerDrawable + ld.mutate() + clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) as ClipDrawable + gd = clipLayer.getDrawable() as GradientDrawable + } + + fun bindData(cws: ControlWithState) { + this.cws = cws + + val (status, template) = cws.control?.let { + title.setText(it.getTitle()) + subtitle.setText(it.getSubtitle()) + Pair(it.getStatus(), it.getControlTemplate()) + } ?: run { + title.setText(cws.ci.controlTitle) + subtitle.setText("") + Pair(Control.STATUS_UNKNOWN, ControlTemplate.NO_TEMPLATE) + } + + cws.control?.let { c -> + layout.setOnLongClickListener(View.OnLongClickListener() { + val closeDialog = Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS) + context.sendBroadcast(closeDialog) + + c.getAppIntent().send() + true + }) + } + + findBehavior(status, template).apply(this, cws) + } + + fun action(action: ControlAction) { + controlsController.action(cws.ci, action) + } + + private fun findBehavior(status: Int, template: ControlTemplate): Behavior { + return when { + status == Control.STATUS_UNKNOWN -> UnknownBehavior() + template is ToggleTemplate -> ToggleBehavior() + template is ToggleRangeTemplate -> ToggleRangeBehavior() + template is TemperatureControlTemplate -> TemperatureControlBehavior() + else -> { + object : Behavior { + override fun apply(cvh: ControlViewHolder, cws: ControlWithState) { + cvh.status.setText(cws.control?.getStatusText()) + cvh.applyRenderInfo(RenderInfo.lookup(cws.ci.deviceType, false)) + } + } + } + } + } + + internal fun applyRenderInfo(ri: RenderInfo) { + val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme()) + val bg = context.getResources().getColorStateList(ri.background, context.getTheme()) + status.setTextColor(fg) + statusExtra.setTextColor(fg) + + icon.setImageIcon(Icon.createWithResource(context, ri.iconResourceId)) + icon.setImageTintList(fg) + + gd.setColor(bg) + } + + fun setEnabled(enabled: Boolean) { + status.setEnabled(enabled) + icon.setEnabled(enabled) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlWithState.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlWithState.kt new file mode 100644 index 000000000000..816f0b2cb1d1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlWithState.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.ui + +import android.service.controls.Control + +import com.android.systemui.controls.controller.ControlInfo + +/** + * A container for: + * <ul> + * <li>ControlInfo - Basic cached info about a Control + * <li>Control - Actual Control parcelable received directly from + * the participating application + * </ul> + */ +data class ControlWithState(val ci: ControlInfo, val control: Control?) diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt index 0270c2b6b1b3..b07a75d5e757 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt @@ -19,12 +19,15 @@ package com.android.systemui.controls.ui import android.content.ComponentName import android.service.controls.Control import android.service.controls.actions.ControlAction +import android.view.ViewGroup interface ControlsUiController { + fun show(parent: ViewGroup) + fun hide() fun onRefreshState(componentName: ComponentName, controls: List<Control>) fun onActionResponse( componentName: ComponentName, controlId: String, @ControlAction.ResponseResult response: Int ) -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index 0ace1263b49b..a777faf57fce 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -16,19 +16,215 @@ package com.android.systemui.controls.ui +import android.accounts.Account +import android.accounts.AccountManager import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.os.IBinder import android.service.controls.Control +import android.service.controls.TokenProvider +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.Space +import android.widget.TextView + +import com.android.systemui.controls.controller.ControlsController +import com.android.systemui.controls.controller.ControlInfo +import com.android.systemui.controls.management.ControlsProviderSelectorActivity +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.R + +import dagger.Lazy + +import java.util.concurrent.Executor import javax.inject.Inject import javax.inject.Singleton +private const val TAG = "ControlsUi" + +// TEMP CODE for MOCK +private const val TOKEN = "https://www.googleapis.com/auth/assistant" +private const val SCOPE = "oauth2:" + TOKEN +private var tokenProviderConnection: TokenProviderConnection? = null +class TokenProviderConnection(val cc: ControlsController, val context: Context) + : ServiceConnection { + private var mTokenProvider: TokenProvider? = null + + override fun onServiceConnected(cName: ComponentName, binder: IBinder) { + Thread({ + Log.i(TAG, "TokenProviderConnection connected") + mTokenProvider = TokenProvider.Stub.asInterface(binder) + + val mLastAccountName = mTokenProvider?.getAccountName() + + if (mLastAccountName == null || mLastAccountName.isEmpty()) { + Log.e(TAG, "NO ACCOUNT IS SET. Open HomeMock app") + } else { + mTokenProvider?.setAuthToken(getAuthToken(mLastAccountName)) + cc.subscribeToFavorites() + } + }, "TokenProviderThread").start() + } + + override fun onServiceDisconnected(cName: ComponentName) { + mTokenProvider = null + } + + fun getAuthToken(accountName: String): String? { + val am = AccountManager.get(context) + val accounts = am.getAccountsByType("com.google") + if (accounts == null || accounts.size == 0) { + Log.w(TAG, "No com.google accounts found") + return null + } + + var account: Account? = null + for (a in accounts) { + if (a.name.equals(accountName)) { + account = a + break + } + } + + if (account == null) { + account = accounts[0] + } + + try { + return am.blockingGetAuthToken(account!!, SCOPE, true) + } catch (e: Throwable) { + Log.e(TAG, "Error getting auth token", e) + return null + } + } +} + @Singleton -class ControlsUiControllerImpl @Inject constructor() : ControlsUiController { +class ControlsUiControllerImpl @Inject constructor ( + val controlsController: Lazy<ControlsController>, + val context: Context, + @Main val uiExecutor: Executor +) : ControlsUiController { + + private lateinit var controlInfos: List<ControlInfo> + private val controlsById = mutableMapOf<Pair<ComponentName, String>, ControlWithState>() + private val controlViewsById = mutableMapOf<String, ControlViewHolder>() + private lateinit var parent: ViewGroup + + override fun show(parent: ViewGroup) { + Log.d(TAG, "show()") + + this.parent = parent + + controlInfos = controlsController.get().getFavoriteControls() + + controlInfos.map { + ControlWithState(it, null) + }.associateByTo(controlsById) { Pair(it.ci.component, it.ci.controlId) } + + if (controlInfos.isEmpty()) { + showInitialSetupView() + } else { + showControlsView() + } + + // Temp code to pass auth + tokenProviderConnection = TokenProviderConnection(controlsController.get(), context) + val serviceIntent = Intent() + serviceIntent.setComponent(ComponentName("com.android.systemui.home.mock", + "com.android.systemui.home.mock.AuthService")) + if (!context.bindService(serviceIntent, tokenProviderConnection!!, + Context.BIND_AUTO_CREATE)) { + controlsController.get().subscribeToFavorites() + } + } + + private fun showInitialSetupView() { + val inflater = LayoutInflater.from(context) + inflater.inflate(R.layout.controls_no_favorites, parent, true) + + val textView = parent.requireViewById(R.id.controls_title) as TextView + textView.setOnClickListener(launchSelectorActivityListener(context)) + } + + private fun launchSelectorActivityListener(context: Context): (View) -> Unit { + return { _ -> + val closeDialog = Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS) + context.sendBroadcast(closeDialog) + + val i = Intent() + i.setComponent(ComponentName(context, ControlsProviderSelectorActivity::class.java)) + i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(i) + } + } + + private fun showControlsView() { + val inflater = LayoutInflater.from(context) + inflater.inflate(R.layout.controls_with_favorites, parent, true) + + val listView = parent.requireViewById(R.id.global_actions_controls_list) as ViewGroup + var lastRow: ViewGroup = createRow(inflater, listView) + controlInfos.forEach { + Log.d(TAG, "favorited control id: " + it.controlId) + if (lastRow.getChildCount() == 2) { + lastRow = createRow(inflater, listView) + } + val item = inflater.inflate( + R.layout.controls_base_item, lastRow, false) as ViewGroup + lastRow.addView(item) + val cvh = ControlViewHolder(item, controlsController.get()) + cvh.bindData(controlsById.get(Pair(it.component, it.controlId))!!) + controlViewsById.put(it.controlId, cvh) + } + + if ((controlInfos.size % 2) == 1) { + lastRow.addView(Space(context), LinearLayout.LayoutParams(0, 0, 1f)) + } + + val moreImageView = parent.requireViewById(R.id.controls_more) as View + moreImageView.setOnClickListener(launchSelectorActivityListener(context)) + } + + override fun hide() { + Log.d(TAG, "hide()") + controlsController.get().unsubscribe() + context.unbindService(tokenProviderConnection) + tokenProviderConnection = null + + parent.removeAllViews() + controlsById.clear() + controlViewsById.clear() + } override fun onRefreshState(componentName: ComponentName, controls: List<Control>) { - TODO("not implemented") + Log.d(TAG, "onRefreshState()") + controls.forEach { c -> + controlsById.get(Pair(componentName, c.getControlId()))?.let { + Log.d(TAG, "onRefreshState() for id: " + c.getControlId()) + val cws = ControlWithState(it.ci, c) + controlsById.put(Pair(componentName, c.getControlId()), cws) + + uiExecutor.execute { + controlViewsById.get(c.getControlId())?.bindData(cws) + } + } + } } override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) { + Log.d(TAG, "onActionResponse()") TODO("not implemented") } -}
\ No newline at end of file + + private fun createRow(inflater: LayoutInflater, parent: ViewGroup): ViewGroup { + val row = inflater.inflate(R.layout.controls_row, parent, false) as ViewGroup + parent.addView(row) + return row + } +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt new file mode 100644 index 000000000000..24c8020529ff --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.ui + +import android.service.controls.DeviceTypes +import android.service.controls.templates.TemperatureControlTemplate + +import com.android.systemui.R + +data class IconState(val disabledResourceId: Int, val enabledResourceId: Int) { + operator fun get(state: Boolean): Int { + return if (state) { + enabledResourceId + } else { + disabledResourceId + } + } +} + +data class RenderInfo(val iconResourceId: Int, val foreground: Int, val background: Int) { + + companion object { + fun lookup(deviceType: Int, enabled: Boolean): RenderInfo { + val iconState = deviceIconMap.getValue(deviceType) + val (fg, bg) = deviceColorMap.getValue(deviceType) + return RenderInfo(iconState[enabled], fg, bg) + } + + fun lookup(deviceType: Int, offset: Int, enabled: Boolean): RenderInfo { + val key = deviceType * BUCKET_SIZE + offset + return lookup(key, enabled) + } + } +} + +private const val BUCKET_SIZE = 1000 +private const val THERMOSTAT_RANGE = DeviceTypes.TYPE_THERMOSTAT * BUCKET_SIZE + +public val deviceColorMap = mapOf<Int, Pair<Int, Int>>( + (THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_HEAT) to + Pair(R.color.thermo_heat_foreground, R.color.thermo_heat_background), + (THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_COOL) to + Pair(R.color.thermo_cool_foreground, R.color.thermo_cool_background), + DeviceTypes.TYPE_LIGHT to Pair(R.color.light_foreground, R.color.light_background) +).withDefault { + Pair(R.color.control_foreground, R.color.control_background) +} + +public val deviceIconMap = mapOf<Int, IconState>( + THERMOSTAT_RANGE to IconState( + R.drawable.ic_device_thermostat_gm2_24px, + R.drawable.ic_device_thermostat_gm2_24px + ), + (THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_OFF) to IconState( + R.drawable.ic_device_thermostat_gm2_24px, + R.drawable.ic_device_thermostat_gm2_24px + ), + (THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_HEAT) to IconState( + R.drawable.ic_device_thermostat_gm2_24px, + R.drawable.ic_device_thermostat_gm2_24px + ), + (THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_COOL) to IconState( + R.drawable.ic_device_thermostat_gm2_24px, + R.drawable.ic_device_thermostat_gm2_24px + ), + (THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_HEAT_COOL) to IconState( + R.drawable.ic_device_thermostat_gm2_24px, + R.drawable.ic_device_thermostat_gm2_24px + ), + (THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_ECO) to IconState( + R.drawable.ic_device_thermostat_gm2_24px, + R.drawable.ic_device_thermostat_gm2_24px + ), + DeviceTypes.TYPE_THERMOSTAT to IconState( + R.drawable.ic_device_thermostat_gm2_24px, + R.drawable.ic_device_thermostat_gm2_24px + ), + DeviceTypes.TYPE_LIGHT to IconState( + R.drawable.ic_light_off_gm2_24px, + R.drawable.ic_lightbulb_outline_gm2_24px + ), + DeviceTypes.TYPE_CAMERA to IconState( + R.drawable.ic_videocam_gm2_24px, + R.drawable.ic_videocam_gm2_24px + ), + DeviceTypes.TYPE_LOCK to IconState( + R.drawable.ic_lock_open_gm2_24px, + R.drawable.ic_lock_gm2_24px + ), + DeviceTypes.TYPE_SWITCH to IconState( + R.drawable.ic_switches_gm2_24px, + R.drawable.ic_switches_gm2_24px + ), + DeviceTypes.TYPE_OUTLET to IconState( + R.drawable.ic_power_off_gm2_24px, + R.drawable.ic_power_gm2_24px + ) +).withDefault { + IconState( + R.drawable.ic_light_off_gm2_24px, + R.drawable.ic_lightbulb_outline_gm2_24px + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt new file mode 100644 index 000000000000..ae0ebbb9e1bb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.ui + +import android.content.Context +import android.graphics.drawable.Drawable +import android.graphics.drawable.LayerDrawable +import android.service.controls.Control +import android.service.controls.templates.TemperatureControlTemplate +import android.widget.TextView + +import com.android.systemui.R + +class TemperatureControlBehavior : Behavior { + lateinit var clipLayer: Drawable + lateinit var control: Control + lateinit var cvh: ControlViewHolder + lateinit var template: TemperatureControlTemplate + lateinit var status: TextView + lateinit var context: Context + + override fun apply(cvh: ControlViewHolder, cws: ControlWithState) { + this.control = cws.control!! + this.cvh = cvh + status = cvh.status + + status.setText(control.getStatusText()) + + val ld = cvh.layout.getBackground() as LayerDrawable + clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) + + template = control.getControlTemplate() as TemperatureControlTemplate + + val activeMode = template.getCurrentActiveMode() + val enabled = activeMode != 0 && activeMode != TemperatureControlTemplate.MODE_OFF + val deviceType = control.getDeviceType() + + clipLayer.setLevel(if (enabled) MAX_LEVEL else MIN_LEVEL) + cvh.setEnabled(enabled) + cvh.applyRenderInfo(RenderInfo.lookup(deviceType, activeMode, enabled)) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt new file mode 100644 index 000000000000..7cd3ab795678 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.ui + +import android.content.Context +import android.graphics.drawable.Drawable +import android.graphics.drawable.LayerDrawable +import android.view.View +import android.widget.TextView +import android.service.controls.Control +import android.service.controls.actions.BooleanAction +import android.service.controls.templates.ToggleTemplate + +import com.android.systemui.R + +class ToggleBehavior : Behavior { + lateinit var clipLayer: Drawable + lateinit var template: ToggleTemplate + lateinit var control: Control + lateinit var cvh: ControlViewHolder + lateinit var context: Context + lateinit var status: TextView + + override fun apply(cvh: ControlViewHolder, cws: ControlWithState) { + this.control = cws.control!! + this.cvh = cvh + status = cvh.status + + status.setText(control.getStatusText()) + + cvh.layout.setOnClickListener(View.OnClickListener() { toggle() }) + + val ld = cvh.layout.getBackground() as LayerDrawable + clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) + + template = control.getControlTemplate() as ToggleTemplate + + val checked = template.isChecked() + val deviceType = control.getDeviceType() + + clipLayer.setLevel(if (checked) MAX_LEVEL else MIN_LEVEL) + cvh.setEnabled(checked) + cvh.applyRenderInfo(RenderInfo.lookup(deviceType, checked)) + } + + fun toggle() { + cvh.action(BooleanAction(template.getTemplateId(), !template.isChecked())) + + val nextLevel = if (template.isChecked()) MIN_LEVEL else MAX_LEVEL + clipLayer.setLevel(nextLevel) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt new file mode 100644 index 000000000000..a6918f50a977 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.ui + +import android.content.Context +import android.graphics.drawable.Drawable +import android.graphics.drawable.LayerDrawable +import android.view.MotionEvent +import android.view.View +import android.widget.TextView +import android.service.controls.Control +import android.service.controls.actions.BooleanAction +import android.service.controls.actions.FloatAction +import android.service.controls.templates.RangeTemplate +import android.service.controls.templates.ToggleRangeTemplate +import android.util.TypedValue + +import com.android.systemui.R + +class ToggleRangeBehavior : Behavior { + lateinit var clipLayer: Drawable + lateinit var template: ToggleRangeTemplate + lateinit var control: Control + lateinit var cvh: ControlViewHolder + lateinit var rangeTemplate: RangeTemplate + lateinit var statusExtra: TextView + lateinit var status: TextView + lateinit var context: Context + + override fun apply(cvh: ControlViewHolder, cws: ControlWithState) { + this.control = cws.control!! + this.cvh = cvh + + statusExtra = cvh.statusExtra + status = cvh.status + + status.setText(control.getStatusText()) + + context = status.getContext() + + cvh.layout.setOnTouchListener(ToggleRangeTouchListener()) + + val ld = cvh.layout.getBackground() as LayerDrawable + clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) + + template = control.getControlTemplate() as ToggleRangeTemplate + rangeTemplate = template.getRange() + + val checked = template.isChecked() + val deviceType = control.getDeviceType() + + updateRange((rangeTemplate.getCurrentValue() / 100.0f), checked) + + cvh.setEnabled(checked) + cvh.applyRenderInfo(RenderInfo.lookup(deviceType, checked)) + } + + fun toggle() { + cvh.action(BooleanAction(template.getTemplateId(), !template.isChecked())) + + val nextLevel = if (template.isChecked()) MIN_LEVEL else MAX_LEVEL + clipLayer.setLevel(nextLevel) + } + + fun beginUpdateRange() { + status.setVisibility(View.GONE) + statusExtra.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources() + .getDimensionPixelSize(R.dimen.control_status_expanded).toFloat()) + } + + fun updateRange(f: Float, checked: Boolean) { + clipLayer.setLevel(if (checked) (MAX_LEVEL * f).toInt() else MIN_LEVEL) + + if (checked && f < 100.0f && f > 0.0f) { + statusExtra.setText("" + (f * 100.0).toInt() + "%") + statusExtra.setVisibility(View.VISIBLE) + } else { + statusExtra.setText("") + statusExtra.setVisibility(View.GONE) + } + } + + fun endUpdateRange(f: Float) { + statusExtra.setText(" - " + (f * 100.0).toInt() + "%") + + val newValue = rangeTemplate.getMinValue() + + (f * (rangeTemplate.getMaxValue() - rangeTemplate.getMinValue())) + + statusExtra.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources() + .getDimensionPixelSize(R.dimen.control_status_normal).toFloat()) + status.setVisibility(View.VISIBLE) + + cvh.action(FloatAction(rangeTemplate.getTemplateId(), findNearestStep(newValue))) + } + + fun findNearestStep(value: Float): Float { + var minDiff = 1000f + + var f = rangeTemplate.getMinValue() + while (f <= rangeTemplate.getMaxValue()) { + val currentDiff = Math.abs(value - f) + if (currentDiff < minDiff) { + minDiff = currentDiff + } else { + return f - rangeTemplate.getStepValue() + } + + f += rangeTemplate.getStepValue() + } + + return rangeTemplate.getMaxValue() + } + + inner class ToggleRangeTouchListener() : View.OnTouchListener { + private var initialTouchX: Float = 0.0f + private var initialTouchY: Float = 0.0f + private var isDragging: Boolean = false + private val minDragDiff = 20 + + override fun onTouch(v: View, e: MotionEvent): Boolean { + when (e.getActionMasked()) { + MotionEvent.ACTION_DOWN -> setupTouch(e) + MotionEvent.ACTION_MOVE -> detectDrag(v, e) + MotionEvent.ACTION_UP -> endTouch(v, e) + } + + return true + } + + private fun setupTouch(e: MotionEvent) { + initialTouchX = e.getX() + initialTouchY = e.getY() + } + + private fun detectDrag(v: View, e: MotionEvent) { + val xDiff = Math.abs(e.getX() - initialTouchX) + val yDiff = Math.abs(e.getY() - initialTouchY) + + if (xDiff < minDragDiff) { + isDragging = false + } else { + if (!isDragging) { + this@ToggleRangeBehavior.beginUpdateRange() + } + v.getParent().requestDisallowInterceptTouchEvent(true) + isDragging = true + if (yDiff > xDiff) { + endTouch(v, e) + } else { + val percent = Math.max(0.0f, Math.min(1.0f, e.getX() / v.getWidth())) + this@ToggleRangeBehavior.updateRange(percent, true) + } + } + } + + private fun endTouch(v: View, e: MotionEvent) { + if (!isDragging) { + this@ToggleRangeBehavior.toggle() + } else { + val percent = Math.max(0.0f, Math.min(1.0f, e.getX() / v.getWidth())) + this@ToggleRangeBehavior.endUpdateRange(percent) + } + + initialTouchX = 0.0f + initialTouchY = 0.0f + isDragging = false + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/UnknownBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/UnknownBehavior.kt new file mode 100644 index 000000000000..5a6e5b481544 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/UnknownBehavior.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.ui + +class UnknownBehavior : Behavior { + override fun apply(cvh: ControlViewHolder, cws: ControlWithState) { + cvh.status.setText("Loading...") + cvh.setEnabled(false) + cvh.applyRenderInfo(RenderInfo.lookup(cws.ci.deviceType, false)) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java index 99dd5e2356d6..5de88e17d320 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java @@ -21,6 +21,7 @@ import com.android.systemui.ScreenDecorations; import com.android.systemui.SizeCompatModeActivityController; import com.android.systemui.SliceBroadcastRelayHandler; import com.android.systemui.SystemUI; +import com.android.systemui.accessibility.SystemActions; import com.android.systemui.accessibility.WindowMagnification; import com.android.systemui.biometrics.AuthController; import com.android.systemui.globalactions.GlobalActionsComponent; @@ -36,6 +37,7 @@ import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarModule; import com.android.systemui.statusbar.tv.TvStatusBar; import com.android.systemui.theme.ThemeOverlayController; +import com.android.systemui.toast.ToastUI; import com.android.systemui.util.leak.GarbageMonitor; import com.android.systemui.volume.VolumeUI; @@ -140,12 +142,24 @@ public abstract class SystemUIBinder { @ClassKey(StatusBar.class) public abstract SystemUI bindsStatusBar(StatusBar sysui); + /** Inject into SystemActions. */ + @Binds + @IntoMap + @ClassKey(SystemActions.class) + public abstract SystemUI bindSystemActions(SystemActions sysui); + /** Inject into ThemeOverlayController. */ @Binds @IntoMap @ClassKey(ThemeOverlayController.class) public abstract SystemUI bindThemeOverlayController(ThemeOverlayController sysui); + /** Inject into ToastUI. */ + @Binds + @IntoMap + @ClassKey(ToastUI.class) + public abstract SystemUI bindToastUI(ToastUI service); + /** Inject into TvStatusBar. */ @Binds @IntoMap diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 36a845002deb..7b541991088c 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -27,6 +27,7 @@ import com.android.systemui.BootCompleteCacheImpl; import com.android.systemui.DumpController; import com.android.systemui.assist.AssistModule; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.log.dagger.LogModule; import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.Recents; @@ -37,6 +38,7 @@ import com.android.systemui.statusbar.StatusBarWindowBlurController; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.dagger.NotificationsModule; import com.android.systemui.statusbar.notification.people.PeopleHubModule; import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent; import com.android.systemui.statusbar.phone.KeyguardLiftController; @@ -59,9 +61,13 @@ import dagger.Provides; * A dagger module for injecting components of System UI that are not overridden by the System UI * implementation. */ -@Module(includes = {AssistModule.class, - ConcurrencyModule.class, - PeopleHubModule.class}, +@Module(includes = { + AssistModule.class, + ConcurrencyModule.class, + LogModule.class, + NotificationsModule.class, + PeopleHubModule.class, + }, subcomponents = {StatusBarComponent.class, NotificationRowComponent.class}) public abstract class SystemUIModule { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java index e14581ffcde8..e8509b366c5b 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java @@ -26,6 +26,7 @@ import com.android.systemui.InitController; import com.android.systemui.SystemUIAppComponentFactory; import com.android.systemui.SystemUIFactory; import com.android.systemui.fragments.FragmentService; +import com.android.systemui.keyguard.KeyguardSliceProvider; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.util.InjectionInflationController; @@ -105,4 +106,9 @@ public interface SystemUIRootComponent { * Member injection into the supplied argument. */ void inject(ContentProvider contentProvider); + + /** + * Member injection into the supplied argument. + */ + void inject(KeyguardSliceProvider keyguardSliceProvider); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeEvent.java b/packages/SystemUI/src/com/android/systemui/doze/DozeEvent.java deleted file mode 100644 index d2fe39424875..000000000000 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeEvent.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.doze; - -import android.annotation.IntDef; - -import com.android.systemui.log.RichEvent; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * An event related to dozing. {@link DozeLog} stores and prints these events for debugging - * and triaging purposes. - */ -public class DozeEvent extends RichEvent { - /** - * Initializes a doze event - */ - public DozeEvent init(@EventType int type, String reason) { - super.init(DEBUG, type, reason); - return this; - } - - /** - * Event labels for each doze event - * Index corresponds to the integer associated with each {@link EventType} - */ - @Override - public String[] getEventLabels() { - final String[] events = new String[]{ - "PickupWakeup", - "PulseStart", - "PulseFinish", - "NotificationPulse", - "Dozing", - "Fling", - "EmergencyCall", - "KeyguardBouncerChanged", - "ScreenOn", - "ScreenOff", - "MissedTick", - "TimeTickScheduled", - "KeyguardVisibilityChanged", - "DozeStateChanged", - "WakeDisplay", - "ProximityResult", - "PulseDropped", - "PulseDisabledByProx", - "SensorTriggered" - }; - - if (events.length != TOTAL_EVENT_TYPES) { - throw new IllegalStateException("DozeEvent events.length should match TOTAL_EVENT_TYPES" - + " events.length=" + events.length - + " TOTAL_EVENT_LENGTH=" + TOTAL_EVENT_TYPES); - } - return events; - } - - /** - * Converts the reason (integer) to a user-readable string - */ - public static String reasonToString(@Reason int pulseReason) { - switch (pulseReason) { - case PULSE_REASON_INTENT: return "intent"; - case PULSE_REASON_NOTIFICATION: return "notification"; - case PULSE_REASON_SENSOR_SIGMOTION: return "sigmotion"; - case REASON_SENSOR_PICKUP: return "pickup"; - case REASON_SENSOR_DOUBLE_TAP: return "doubletap"; - case PULSE_REASON_SENSOR_LONG_PRESS: return "longpress"; - case PULSE_REASON_DOCKING: return "docking"; - case PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN: return "wakelockscreen"; - case REASON_SENSOR_WAKE_UP: return "wakeup"; - case REASON_SENSOR_TAP: return "tap"; - default: throw new IllegalArgumentException("invalid reason: " + pulseReason); - } - } - - @IntDef({PICKUP_WAKEUP, PULSE_START, PULSE_FINISH, NOTIFICATION_PULSE, DOZING, FLING, - EMERGENCY_CALL, KEYGUARD_BOUNCER_CHANGED, SCREEN_ON, SCREEN_OFF, MISSED_TICK, - TIME_TICK_SCHEDULED, KEYGUARD_VISIBILITY_CHANGE, DOZE_STATE_CHANGED, WAKE_DISPLAY, - PROXIMITY_RESULT, PULSE_DROPPED, PULSE_DISABLED_BY_PROX, SENSOR_TRIGGERED}) - /** - * Types of DozeEvents - */ - @Retention(RetentionPolicy.SOURCE) - public @interface EventType {} - public static final int PICKUP_WAKEUP = 0; - public static final int PULSE_START = 1; - public static final int PULSE_FINISH = 2; - public static final int NOTIFICATION_PULSE = 3; - public static final int DOZING = 4; - public static final int FLING = 5; - public static final int EMERGENCY_CALL = 6; - public static final int KEYGUARD_BOUNCER_CHANGED = 7; - public static final int SCREEN_ON = 8; - public static final int SCREEN_OFF = 9; - public static final int MISSED_TICK = 10; - public static final int TIME_TICK_SCHEDULED = 11; - public static final int KEYGUARD_VISIBILITY_CHANGE = 12; - public static final int DOZE_STATE_CHANGED = 13; - public static final int WAKE_DISPLAY = 14; - public static final int PROXIMITY_RESULT = 15; - public static final int PULSE_DROPPED = 16; - public static final int PULSE_DISABLED_BY_PROX = 17; - public static final int SENSOR_TRIGGERED = 18; - public static final int TOTAL_EVENT_TYPES = 19; - - public static final int TOTAL_REASONS = 10; - @IntDef({PULSE_REASON_NONE, PULSE_REASON_INTENT, PULSE_REASON_NOTIFICATION, - PULSE_REASON_SENSOR_SIGMOTION, REASON_SENSOR_PICKUP, REASON_SENSOR_DOUBLE_TAP, - PULSE_REASON_SENSOR_LONG_PRESS, PULSE_REASON_DOCKING, REASON_SENSOR_WAKE_UP, - PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN, REASON_SENSOR_TAP}) - @Retention(RetentionPolicy.SOURCE) - public @interface Reason {} - public static final int PULSE_REASON_NONE = -1; - public static final int PULSE_REASON_INTENT = 0; - public static final int PULSE_REASON_NOTIFICATION = 1; - public static final int PULSE_REASON_SENSOR_SIGMOTION = 2; - public static final int REASON_SENSOR_PICKUP = 3; - public static final int REASON_SENSOR_DOUBLE_TAP = 4; - public static final int PULSE_REASON_SENSOR_LONG_PRESS = 5; - public static final int PULSE_REASON_DOCKING = 6; - public static final int REASON_SENSOR_WAKE_UP = 7; - public static final int PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN = 8; - public static final int REASON_SENSOR_TAP = 9; -} diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java index 43db85bd91ec..6f655bb0b209 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java @@ -118,6 +118,7 @@ public class DozeFactory { new DozeWallpaperState(mWallpaperManager, mBiometricUnlockController, mDozeParameters), new DozeDockHandler(config, machine, mDockManager), + new DozeSuppressedHandler(dozeService, config, machine), new DozeAuthRemover(dozeService) }); diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java index fe504216b166..96ae6c9ec4a2 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java @@ -16,15 +16,20 @@ package com.android.systemui.doze; +import android.annotation.IntDef; import android.util.TimeUtils; +import androidx.annotation.NonNull; + import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.DumpController; -import com.android.systemui.log.SysuiLog; +import com.android.systemui.Dumpable; +import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.Date; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import javax.inject.Inject; import javax.inject.Singleton; @@ -32,13 +37,11 @@ import javax.inject.Singleton; /** * Logs doze events for debugging and triaging purposes. Logs are dumped in bugreports or on demand: * adb shell dumpsys activity service com.android.systemui/.SystemUIService \ - * dependency DumpController DozeLog + * dependency DumpController DozeLog,DozeStats */ @Singleton -public class DozeLog extends SysuiLog<DozeEvent> { - private static final String TAG = "DozeLog"; - - private DozeEvent mRecycledEvent; +public class DozeLog implements Dumpable { + private final DozeLogger mLogger; private boolean mPulsing; private long mSince; @@ -51,8 +54,11 @@ public class DozeLog extends SysuiLog<DozeEvent> { private SummaryStats[][] mProxStats; // [reason][near/far] @Inject - public DozeLog(KeyguardUpdateMonitor keyguardUpdateMonitor, DumpController dumpController) { - super(dumpController, TAG, MAX_DOZE_DEBUG_LOGS, MAX_DOZE_LOGS); + public DozeLog( + KeyguardUpdateMonitor keyguardUpdateMonitor, + DumpController dumpController, + DozeLogger logger) { + mLogger = logger; mSince = System.currentTimeMillis(); mPickupPulseNearVibrationStats = new SummaryStats(); mPickupPulseNotNearVibrationStats = new SummaryStats(); @@ -60,8 +66,8 @@ public class DozeLog extends SysuiLog<DozeEvent> { mScreenOnPulsingStats = new SummaryStats(); mScreenOnNotPulsingStats = new SummaryStats(); mEmergencyCallStats = new SummaryStats(); - mProxStats = new SummaryStats[DozeEvent.TOTAL_REASONS][2]; - for (int i = 0; i < DozeEvent.TOTAL_REASONS; i++) { + mProxStats = new SummaryStats[TOTAL_REASONS][2]; + for (int i = 0; i < TOTAL_REASONS; i++) { mProxStats[i][0] = new SummaryStats(); mProxStats[i][1] = new SummaryStats(); } @@ -69,42 +75,42 @@ public class DozeLog extends SysuiLog<DozeEvent> { if (keyguardUpdateMonitor != null) { keyguardUpdateMonitor.registerCallback(mKeyguardCallback); } + + dumpController.registerDumpable("DumpStats", this); } /** * Appends pickup wakeup event to the logs */ public void tracePickupWakeUp(boolean withinVibrationThreshold) { - log(DozeEvent.PICKUP_WAKEUP, "withinVibrationThreshold=" + withinVibrationThreshold); - if (mEnabled) { - (withinVibrationThreshold ? mPickupPulseNearVibrationStats - : mPickupPulseNotNearVibrationStats).append(); - } + mLogger.logPickupWakeup(withinVibrationThreshold); + (withinVibrationThreshold ? mPickupPulseNearVibrationStats + : mPickupPulseNotNearVibrationStats).append(); } /** * Appends pulse started event to the logs. * @param reason why the pulse started */ - public void tracePulseStart(@DozeEvent.Reason int reason) { - log(DozeEvent.PULSE_START, DozeEvent.reasonToString(reason)); - if (mEnabled) mPulsing = true; + public void tracePulseStart(@Reason int reason) { + mLogger.logPulseStart(reason); + mPulsing = true; } /** * Appends pulse finished event to the logs */ public void tracePulseFinish() { - log(DozeEvent.PULSE_FINISH); - if (mEnabled) mPulsing = false; + mLogger.logPulseFinish(); + mPulsing = false; } /** * Appends pulse event to the logs */ public void traceNotificationPulse() { - log(DozeEvent.NOTIFICATION_PULSE); - if (mEnabled) mNotificationPulseStats.append(); + mLogger.logNotificationPulse(); + mNotificationPulseStats.append(); } /** @@ -112,8 +118,8 @@ public class DozeLog extends SysuiLog<DozeEvent> { * @param dozing true if dozing, else false */ public void traceDozing(boolean dozing) { - log(DozeEvent.DOZING, "dozing=" + dozing); - if (mEnabled) mPulsing = false; + mLogger.logDozing(dozing); + mPulsing = false; } /** @@ -121,18 +127,15 @@ public class DozeLog extends SysuiLog<DozeEvent> { */ public void traceFling(boolean expand, boolean aboveThreshold, boolean thresholdNeeded, boolean screenOnFromTouch) { - log(DozeEvent.FLING, "expand=" + expand - + " aboveThreshold=" + aboveThreshold - + " thresholdNeeded=" + thresholdNeeded - + " screenOnFromTouch=" + screenOnFromTouch); + mLogger.logFling(expand, aboveThreshold, thresholdNeeded, screenOnFromTouch); } /** * Appends emergency call event to the logs */ public void traceEmergencyCall() { - log(DozeEvent.EMERGENCY_CALL); - if (mEnabled) mEmergencyCallStats.append(); + mLogger.logEmergencyCall(); + mEmergencyCallStats.append(); } /** @@ -140,18 +143,16 @@ public class DozeLog extends SysuiLog<DozeEvent> { * @param showing true if the keyguard bouncer is showing, else false */ public void traceKeyguardBouncerChanged(boolean showing) { - log(DozeEvent.KEYGUARD_BOUNCER_CHANGED, "showing=" + showing); + mLogger.logKeyguardBouncerChanged(showing); } /** * Appends screen-on event to the logs */ public void traceScreenOn() { - log(DozeEvent.SCREEN_ON, "pulsing=" + mPulsing); - if (mEnabled) { - (mPulsing ? mScreenOnPulsingStats : mScreenOnNotPulsingStats).append(); - mPulsing = false; - } + mLogger.logScreenOn(mPulsing); + (mPulsing ? mScreenOnPulsingStats : mScreenOnNotPulsingStats).append(); + mPulsing = false; } /** @@ -159,7 +160,7 @@ public class DozeLog extends SysuiLog<DozeEvent> { * @param why reason the screen is off */ public void traceScreenOff(int why) { - log(DozeEvent.SCREEN_OFF, "why=" + why); + mLogger.logScreenOff(why); } /** @@ -167,7 +168,7 @@ public class DozeLog extends SysuiLog<DozeEvent> { * @param delay of the missed tick */ public void traceMissedTick(String delay) { - log(DozeEvent.MISSED_TICK, "delay=" + delay); + mLogger.logMissedTick(delay); } /** @@ -176,9 +177,7 @@ public class DozeLog extends SysuiLog<DozeEvent> { * @param triggerAt time tick trigger at */ public void traceTimeTickScheduled(long when, long triggerAt) { - log(DozeEvent.TIME_TICK_SCHEDULED, - "scheduledAt=" + DATE_FORMAT.format(new Date(when)) - + " triggerAt=" + DATE_FORMAT.format(new Date(triggerAt))); + mLogger.logTimeTickScheduled(when, triggerAt); } /** @@ -186,8 +185,8 @@ public class DozeLog extends SysuiLog<DozeEvent> { * @param showing whether the keyguard is now showing */ public void traceKeyguard(boolean showing) { - log(DozeEvent.KEYGUARD_VISIBILITY_CHANGE, "showing=" + showing); - if (mEnabled && !showing) mPulsing = false; + mLogger.logKeyguardVisibilityChange(showing); + if (!showing) mPulsing = false; } /** @@ -195,7 +194,7 @@ public class DozeLog extends SysuiLog<DozeEvent> { * @param state new DozeMachine state */ public void traceState(DozeMachine.State state) { - log(DozeEvent.DOZE_STATE_CHANGED, state.name()); + mLogger.logDozeStateChanged(state); } /** @@ -203,31 +202,22 @@ public class DozeLog extends SysuiLog<DozeEvent> { * @param wake if we're waking up or sleeping. */ public void traceWakeDisplay(boolean wake) { - log(DozeEvent.WAKE_DISPLAY, "wake=" + wake); + mLogger.logWakeDisplay(wake); } /** * Appends proximity result event to the logs * @param near true if near, else false - * @param millis * @param reason why proximity result was triggered */ - public void traceProximityResult(boolean near, long millis, @DozeEvent.Reason int reason) { - log(DozeEvent.PROXIMITY_RESULT, - " reason=" + DozeEvent.reasonToString(reason) - + " near=" + near - + " millis=" + millis); - if (mEnabled) mProxStats[reason][near ? 0 : 1].append(); + public void traceProximityResult(boolean near, long millis, @Reason int reason) { + mLogger.logProximityResult(near, millis, reason); + mProxStats[reason][near ? 0 : 1].append(); } - /** - * Prints doze log timeline and consolidated stats - * @param pw - */ - public void dump(PrintWriter pw) { + @Override + public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { synchronized (DozeLog.class) { - super.dump(null, pw, null); // prints timeline - pw.print(" Doze summary stats (for "); TimeUtils.formatDuration(System.currentTimeMillis() - mSince, pw); pw.println("):"); @@ -237,32 +227,19 @@ public class DozeLog extends SysuiLog<DozeEvent> { mScreenOnPulsingStats.dump(pw, "Screen on (pulsing)"); mScreenOnNotPulsingStats.dump(pw, "Screen on (not pulsing)"); mEmergencyCallStats.dump(pw, "Emergency call"); - for (int i = 0; i < DozeEvent.TOTAL_REASONS; i++) { - final String reason = DozeEvent.reasonToString(i); + for (int i = 0; i < TOTAL_REASONS; i++) { + final String reason = reasonToString(i); mProxStats[i][0].dump(pw, "Proximity near (" + reason + ")"); mProxStats[i][1].dump(pw, "Proximity far (" + reason + ")"); } } } - private void log(@DozeEvent.EventType int eventType) { - log(eventType, ""); - } - - private void log(@DozeEvent.EventType int eventType, String msg) { - if (mRecycledEvent != null) { - mRecycledEvent = log(mRecycledEvent.init(eventType, msg)); - } else { - mRecycledEvent = log(new DozeEvent().init(eventType, msg)); - } - } - /** * Appends pulse dropped event to logs */ public void tracePulseDropped(boolean pulsePending, DozeMachine.State state, boolean blocked) { - log(DozeEvent.PULSE_DROPPED, "pulsePending=" + pulsePending + " state=" - + state.name() + " blocked=" + blocked); + mLogger.logPulseDropped(pulsePending, state, blocked); } /** @@ -270,7 +247,7 @@ public class DozeLog extends SysuiLog<DozeEvent> { * @param reason why the pulse was dropped */ public void tracePulseDropped(String reason) { - log(DozeEvent.PULSE_DROPPED, "why=" + reason); + mLogger.logPulseDropped(reason); } /** @@ -278,15 +255,23 @@ public class DozeLog extends SysuiLog<DozeEvent> { * @param disabled */ public void tracePulseTouchDisabledByProx(boolean disabled) { - log(DozeEvent.PULSE_DISABLED_BY_PROX, "disabled=" + disabled); + mLogger.logPulseTouchDisabledByProx(disabled); } /** * Appends sensor triggered event to logs * @param reason why the sensor was triggered */ - public void traceSensor(@DozeEvent.Reason int reason) { - log(DozeEvent.SENSOR_TRIGGERED, "type=" + DozeEvent.reasonToString(reason)); + public void traceSensor(@Reason int reason) { + mLogger.logSensorTriggered(reason); + } + + /** + * Appends doze suppressed event to the logs + * @param suppressedState The {@link DozeMachine.State} that was suppressed + */ + public void traceDozeSuppressed(DozeMachine.State suppressedState) { + mLogger.logDozeSuppressed(suppressedState); } private class SummaryStats { @@ -339,6 +324,42 @@ public class DozeLog extends SysuiLog<DozeEvent> { } }; - private static final int MAX_DOZE_DEBUG_LOGS = 400; - private static final int MAX_DOZE_LOGS = 50; + /** + * Converts the reason (integer) to a user-readable string + */ + public static String reasonToString(@Reason int pulseReason) { + switch (pulseReason) { + case PULSE_REASON_INTENT: return "intent"; + case PULSE_REASON_NOTIFICATION: return "notification"; + case PULSE_REASON_SENSOR_SIGMOTION: return "sigmotion"; + case REASON_SENSOR_PICKUP: return "pickup"; + case REASON_SENSOR_DOUBLE_TAP: return "doubletap"; + case PULSE_REASON_SENSOR_LONG_PRESS: return "longpress"; + case PULSE_REASON_DOCKING: return "docking"; + case PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN: return "wakelockscreen"; + case REASON_SENSOR_WAKE_UP: return "wakeup"; + case REASON_SENSOR_TAP: return "tap"; + default: throw new IllegalArgumentException("invalid reason: " + pulseReason); + } + } + + @Retention(RetentionPolicy.SOURCE) + @IntDef({PULSE_REASON_NONE, PULSE_REASON_INTENT, PULSE_REASON_NOTIFICATION, + PULSE_REASON_SENSOR_SIGMOTION, REASON_SENSOR_PICKUP, REASON_SENSOR_DOUBLE_TAP, + PULSE_REASON_SENSOR_LONG_PRESS, PULSE_REASON_DOCKING, REASON_SENSOR_WAKE_UP, + PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN, REASON_SENSOR_TAP}) + public @interface Reason {} + public static final int PULSE_REASON_NONE = -1; + public static final int PULSE_REASON_INTENT = 0; + public static final int PULSE_REASON_NOTIFICATION = 1; + public static final int PULSE_REASON_SENSOR_SIGMOTION = 2; + public static final int REASON_SENSOR_PICKUP = 3; + public static final int REASON_SENSOR_DOUBLE_TAP = 4; + public static final int PULSE_REASON_SENSOR_LONG_PRESS = 5; + public static final int PULSE_REASON_DOCKING = 6; + public static final int REASON_SENSOR_WAKE_UP = 7; + public static final int PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN = 8; + public static final int REASON_SENSOR_TAP = 9; + + public static final int TOTAL_REASONS = 10; } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt new file mode 100644 index 000000000000..732745a1158b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.doze + +import com.android.systemui.doze.DozeLog.Reason +import com.android.systemui.doze.DozeLog.reasonToString +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.LogLevel.ERROR +import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.dagger.DozeLog +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import javax.inject.Inject + +/** Interface for logging messages to the [DozeLog]. */ +class DozeLogger @Inject constructor( + @DozeLog private val buffer: LogBuffer +) { + fun logPickupWakeup(isWithinVibrationThreshold: Boolean) { + buffer.log(TAG, DEBUG, { + bool1 = isWithinVibrationThreshold + }, { + "PickupWakeup withinVibrationThreshold=$bool1" + }) + } + + fun logPulseStart(@Reason reason: Int) { + buffer.log(TAG, INFO, { + int1 = reason + }, { + "Pulse start, reason=${reasonToString(int1)}" + }) + } + + fun logPulseFinish() { + buffer.log(TAG, INFO, {}, { "Pulse finish" }) + } + + fun logNotificationPulse() { + buffer.log(TAG, INFO, {}, { "Notification pulse" }) + } + + fun logDozing(isDozing: Boolean) { + buffer.log(TAG, INFO, { + bool1 = isDozing + }, { + "Dozing=$bool1" + }) + } + + fun logFling( + expand: Boolean, + aboveThreshold: Boolean, + thresholdNeeded: Boolean, + screenOnFromTouch: Boolean + ) { + buffer.log(TAG, DEBUG, { + bool1 = expand + bool2 = aboveThreshold + bool3 = thresholdNeeded + bool4 = screenOnFromTouch + }, { + "Fling expand=$bool1 aboveThreshold=$bool2 thresholdNeeded=$bool3 " + + "screenOnFromTouch=$bool4" + }) + } + + fun logEmergencyCall() { + buffer.log(TAG, INFO, {}, { "Emergency call" }) + } + + fun logKeyguardBouncerChanged(isShowing: Boolean) { + buffer.log(TAG, INFO, { + bool1 = isShowing + }, { + "Keyguard bouncer changed, showing=$bool1" + }) + } + + fun logScreenOn(isPulsing: Boolean) { + buffer.log(TAG, INFO, { + bool1 = isPulsing + }, { + "Screen on, pulsing=$bool1" + }) + } + + fun logScreenOff(why: Int) { + buffer.log(TAG, INFO, { + int1 = why + }, { + "Screen off, why=$int1" + }) + } + + fun logMissedTick(delay: String) { + buffer.log(TAG, ERROR, { + str1 = delay + }, { + "Missed AOD time tick by $str1" + }) + } + + fun logTimeTickScheduled(whenAt: Long, triggerAt: Long) { + buffer.log(TAG, DEBUG, { + long1 = whenAt + long2 = triggerAt + }, { + "Time tick scheduledAt=${DATE_FORMAT.format(Date(long1))} " + + "triggerAt=${DATE_FORMAT.format(Date(long2))}" + }) + } + + fun logKeyguardVisibilityChange(isShowing: Boolean) { + buffer.log(TAG, INFO, { + bool1 = isShowing + }, { + "Keyguard visibility change, isShowing=$bool1" + }) + } + + fun logDozeStateChanged(state: DozeMachine.State) { + buffer.log(TAG, INFO, { + str1 = state.name + }, { + "Doze state changed to $str1" + }) + } + + fun logWakeDisplay(isAwake: Boolean) { + buffer.log(TAG, DEBUG, { + bool1 = isAwake + }, { + "Display wakefulness changed, isAwake=$bool1" + }) + } + + fun logProximityResult(isNear: Boolean, millis: Long, @Reason reason: Int) { + buffer.log(TAG, DEBUG, { + bool1 = isNear + long1 = millis + int1 = reason + }, { + "Proximity result reason=${reasonToString(int1)} near=$bool1 millis=$long1" + }) + } + + fun logPulseDropped(pulsePending: Boolean, state: DozeMachine.State, blocked: Boolean) { + buffer.log(TAG, INFO, { + bool1 = pulsePending + str1 = state.name + bool2 = blocked + }, { + "Pulse dropped, pulsePending=$bool1 state=$str1 blocked=$bool2" + }) + } + + fun logPulseDropped(reason: String) { + buffer.log(TAG, INFO, { + str1 = reason + }, { + "Pulse dropped, why=$str1" + }) + } + + fun logPulseTouchDisabledByProx(disabled: Boolean) { + buffer.log(TAG, DEBUG, { + bool1 = disabled + }, { + "Pulse touch modified by prox, disabled=$bool1" + }) + } + + fun logSensorTriggered(@Reason reason: Int) { + buffer.log(TAG, DEBUG, { + int1 = reason + }, { + "Sensor triggered, type=${reasonToString(int1)}" + }) + } + + fun logDozeSuppressed(state: DozeMachine.State) { + buffer.log(TAG, INFO, { + str1 = state.name + }, { + "Doze state suppressed, state=$str1" + }) + } +} + +private const val TAG = "DozeLog" + +val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.S", Locale.US) diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java index 40603ab24c1e..6e81d3a11098 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java @@ -102,6 +102,10 @@ public class DozeMachine { } } + boolean isAlwaysOn() { + return this == DOZE_AOD || this == DOZE_AOD_DOCKED; + } + int screenState(DozeParameters parameters) { switch (this) { case UNINITIALIZED: @@ -169,7 +173,7 @@ public class DozeMachine { @MainThread public void requestState(State requestedState) { Preconditions.checkArgument(requestedState != State.DOZE_REQUEST_PULSE); - requestState(requestedState, DozeEvent.PULSE_REASON_NONE); + requestState(requestedState, DozeLog.PULSE_REASON_NONE); } @MainThread @@ -271,7 +275,7 @@ public class DozeMachine { if (newState == State.DOZE_REQUEST_PULSE) { mPulseReason = pulseReason; } else if (oldState == State.DOZE_PULSE_DONE) { - mPulseReason = DozeEvent.PULSE_REASON_NONE; + mPulseReason = DozeLog.PULSE_REASON_NONE; } } @@ -324,6 +328,11 @@ public class DozeMachine { if (mState == State.FINISH) { return State.FINISH; } + if (mConfig.dozeSuppressed(UserHandle.USER_CURRENT) && requestedState.isAlwaysOn()) { + Log.i(TAG, "Doze is suppressed. Suppressing state: " + requestedState); + mDozeLog.traceDozeSuppressed(requestedState); + return State.DOZE; + } if ((mState == State.DOZE_AOD_PAUSED || mState == State.DOZE_AOD_PAUSING || mState == State.DOZE_AOD || mState == State.DOZE) && requestedState == State.DOZE_PULSE_DONE) { @@ -368,7 +377,7 @@ public class DozeMachine { nextState = State.DOZE; } - transitionTo(nextState, DozeEvent.PULSE_REASON_NONE); + transitionTo(nextState, DozeLog.PULSE_REASON_NONE); break; default: break; diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java index 3abeea91cdee..e50f1fb2a3ee 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java @@ -18,7 +18,6 @@ package com.android.systemui.doze; import static com.android.systemui.doze.DozeMachine.State.DOZE; import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD; -import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_DOCKED; import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED; import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING; import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE; @@ -90,10 +89,10 @@ public class DozeScreenState implements DozeMachine.Part { } final boolean messagePending = mHandler.hasCallbacks(mApplyPendingScreenState); - final boolean pulseEnding = oldState == DOZE_PULSE_DONE && isAlwaysOnState(newState); + final boolean pulseEnding = oldState == DOZE_PULSE_DONE && newState.isAlwaysOn(); final boolean turningOn = (oldState == DOZE_AOD_PAUSED || oldState == DOZE) - && isAlwaysOnState(newState); - final boolean turningOff = (isAlwaysOnState(oldState) && newState == DOZE) + && newState.isAlwaysOn(); + final boolean turningOff = (newState.isAlwaysOn() && newState == DOZE) || (oldState == DOZE_AOD_PAUSING && newState == DOZE_AOD_PAUSED); final boolean justInitialized = oldState == DozeMachine.State.INITIALIZED; if (messagePending || justInitialized || pulseEnding || turningOn) { @@ -132,10 +131,6 @@ public class DozeScreenState implements DozeMachine.Part { } } - private boolean isAlwaysOnState(DozeMachine.State state) { - return state == DOZE_AOD || state == DOZE_AOD_DOCKED; - } - private void applyPendingScreenState() { applyScreenState(mPendingScreenState); mPendingScreenState = Display.STATE_UNKNOWN; diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java index d008e665d171..44e5d3de5ca7 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java @@ -97,14 +97,14 @@ public class DozeSensors { mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION), null /* setting */, dozeParameters.getPulseOnSigMotion(), - DozeEvent.PULSE_REASON_SENSOR_SIGMOTION, false /* touchCoords */, + DozeLog.PULSE_REASON_SENSOR_SIGMOTION, false /* touchCoords */, false /* touchscreen */, dozeLog), mPickupSensor = new TriggerSensor( mSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE), Settings.Secure.DOZE_PICK_UP_GESTURE, true /* settingDef */, config.dozePickupSensorAvailable(), - DozeEvent.REASON_SENSOR_PICKUP, false /* touchCoords */, + DozeLog.REASON_SENSOR_PICKUP, false /* touchCoords */, false /* touchscreen */, false /* ignoresSetting */, dozeLog), @@ -112,7 +112,7 @@ public class DozeSensors { findSensorWithType(config.doubleTapSensorType()), Settings.Secure.DOZE_DOUBLE_TAP_GESTURE, true /* configured */, - DozeEvent.REASON_SENSOR_DOUBLE_TAP, + DozeLog.REASON_SENSOR_DOUBLE_TAP, dozeParameters.doubleTapReportsTouchCoordinates(), true /* touchscreen */, dozeLog), @@ -120,7 +120,7 @@ public class DozeSensors { findSensorWithType(config.tapSensorType()), Settings.Secure.DOZE_TAP_SCREEN_GESTURE, true /* configured */, - DozeEvent.REASON_SENSOR_TAP, + DozeLog.REASON_SENSOR_TAP, false /* reports touch coordinates */, true /* touchscreen */, dozeLog), @@ -129,7 +129,7 @@ public class DozeSensors { Settings.Secure.DOZE_PULSE_ON_LONG_PRESS, false /* settingDef */, true /* configured */, - DozeEvent.PULSE_REASON_SENSOR_LONG_PRESS, + DozeLog.PULSE_REASON_SENSOR_LONG_PRESS, true /* reports touch coordinates */, true /* touchscreen */, dozeLog), @@ -137,7 +137,7 @@ public class DozeSensors { new SensorManagerPlugin.Sensor(TYPE_WAKE_DISPLAY), Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE, mConfig.wakeScreenGestureAvailable() && alwaysOn, - DozeEvent.REASON_SENSOR_WAKE_UP, + DozeLog.REASON_SENSOR_WAKE_UP, false /* reports touch coordinates */, false /* touchscreen */, dozeLog), @@ -145,7 +145,7 @@ public class DozeSensors { new SensorManagerPlugin.Sensor(TYPE_WAKE_LOCK_SCREEN), Settings.Secure.DOZE_WAKE_LOCK_SCREEN_GESTURE, mConfig.wakeScreenGestureAvailable(), - DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN, + DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN, false /* reports touch coordinates */, false /* touchscreen */, mConfig.getWakeLockScreenDebounce(), @@ -525,7 +525,7 @@ public class DozeSensors { /** * Called when a sensor requests a pulse - * @param pulseReason Requesting sensor, e.g. {@link DozeEvent#REASON_SENSOR_PICKUP} + * @param pulseReason Requesting sensor, e.g. {@link DozeLog#REASON_SENSOR_PICKUP} * @param screenX the location on the screen where the sensor fired or -1 * if the sensor doesn't support reporting screen locations. * @param screenY the location on the screen where the sensor fired or -1 diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressedHandler.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressedHandler.java new file mode 100644 index 000000000000..3a5c1a0890f9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressedHandler.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.doze; + +import static java.util.Objects.requireNonNull; + +import android.app.ActivityManager; +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.hardware.display.AmbientDisplayConfiguration; +import android.net.Uri; +import android.os.Handler; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +/** Handles updating the doze state when doze is suppressed. */ +public final class DozeSuppressedHandler implements DozeMachine.Part { + + private static final String TAG = DozeSuppressedHandler.class.getSimpleName(); + private static final boolean DEBUG = DozeService.DEBUG; + + private final ContentResolver mResolver; + private final AmbientDisplayConfiguration mConfig; + private final DozeMachine mMachine; + private final DozeSuppressedSettingObserver mSettingObserver; + private final Handler mHandler = new Handler(); + + public DozeSuppressedHandler(Context context, AmbientDisplayConfiguration config, + DozeMachine machine) { + this(context, config, machine, null); + } + + @VisibleForTesting + DozeSuppressedHandler(Context context, AmbientDisplayConfiguration config, DozeMachine machine, + DozeSuppressedSettingObserver observer) { + mResolver = context.getContentResolver(); + mConfig = requireNonNull(config); + mMachine = requireNonNull(machine); + if (observer == null) { + mSettingObserver = new DozeSuppressedSettingObserver(mHandler); + } else { + mSettingObserver = observer; + } + } + + @Override + public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) { + switch (newState) { + case INITIALIZED: + mSettingObserver.register(); + break; + case FINISH: + mSettingObserver.unregister(); + break; + default: + // no-op + } + } + + /** + * Listens to changes to the DOZE_SUPPRESSED secure setting and updates the doze state + * accordingly. + */ + final class DozeSuppressedSettingObserver extends ContentObserver { + private boolean mRegistered; + + private DozeSuppressedSettingObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange, Uri uri, int userId) { + if (userId != ActivityManager.getCurrentUser()) { + return; + } + final DozeMachine.State nextState; + if (mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT) + && !mConfig.dozeSuppressed(UserHandle.USER_CURRENT)) { + nextState = DozeMachine.State.DOZE_AOD; + } else { + nextState = DozeMachine.State.DOZE; + } + mMachine.requestState(nextState); + } + + void register() { + if (mRegistered) { + return; + } + mResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.SUPPRESS_DOZE), + false, this, UserHandle.USER_CURRENT); + Log.d(TAG, "Register"); + mRegistered = true; + } + + void unregister() { + if (!mRegistered) { + return; + } + mResolver.unregisterContentObserver(this); + Log.d(TAG, "Unregister"); + mRegistered = false; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index 722dc038f853..305a4c870d91 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -127,7 +127,12 @@ public class DozeTriggers implements DozeMachine.Part { mDozeLog.tracePulseDropped("pulseOnNotificationsDisabled"); return; } - requestPulse(DozeEvent.PULSE_REASON_NOTIFICATION, false /* performedProxCheck */, + if (mConfig.dozeSuppressed(UserHandle.USER_CURRENT)) { + runIfNotNull(onPulseSuppressedListener); + mDozeLog.tracePulseDropped("dozeSuppressed"); + return; + } + requestPulse(DozeLog.PULSE_REASON_NOTIFICATION, false /* performedProxCheck */, onPulseSuppressedListener); mDozeLog.traceNotificationPulse(); } @@ -163,12 +168,12 @@ public class DozeTriggers implements DozeMachine.Part { @VisibleForTesting void onSensor(int pulseReason, float screenX, float screenY, float[] rawValues) { - boolean isDoubleTap = pulseReason == DozeEvent.REASON_SENSOR_DOUBLE_TAP; - boolean isTap = pulseReason == DozeEvent.REASON_SENSOR_TAP; - boolean isPickup = pulseReason == DozeEvent.REASON_SENSOR_PICKUP; - boolean isLongPress = pulseReason == DozeEvent.PULSE_REASON_SENSOR_LONG_PRESS; - boolean isWakeDisplay = pulseReason == DozeEvent.REASON_SENSOR_WAKE_UP; - boolean isWakeLockScreen = pulseReason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN; + boolean isDoubleTap = pulseReason == DozeLog.REASON_SENSOR_DOUBLE_TAP; + boolean isTap = pulseReason == DozeLog.REASON_SENSOR_TAP; + boolean isPickup = pulseReason == DozeLog.REASON_SENSOR_PICKUP; + boolean isLongPress = pulseReason == DozeLog.PULSE_REASON_SENSOR_LONG_PRESS; + boolean isWakeDisplay = pulseReason == DozeLog.REASON_SENSOR_WAKE_UP; + boolean isWakeLockScreen = pulseReason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN; boolean wakeEvent = rawValues != null && rawValues.length > 0 && rawValues[0] != 0; if (isWakeDisplay) { @@ -281,9 +286,9 @@ public class DozeTriggers implements DozeMachine.Part { // Logs AOD open due to sensor wake up. mMetricsLogger.write(new LogMaker(MetricsEvent.DOZING) .setType(MetricsEvent.TYPE_OPEN) - .setSubtype(DozeEvent.REASON_SENSOR_WAKE_UP)); + .setSubtype(DozeLog.REASON_SENSOR_WAKE_UP)); } - }, true /* alreadyPerformedProxCheck */, DozeEvent.REASON_SENSOR_WAKE_UP); + }, true /* alreadyPerformedProxCheck */, DozeLog.REASON_SENSOR_WAKE_UP); } else { boolean paused = (state == DozeMachine.State.DOZE_AOD_PAUSED); boolean pausing = (state == DozeMachine.State.DOZE_AOD_PAUSING); @@ -292,7 +297,7 @@ public class DozeTriggers implements DozeMachine.Part { // Logs AOD close due to sensor wake up. mMetricsLogger.write(new LogMaker(MetricsEvent.DOZING) .setType(MetricsEvent.TYPE_CLOSE) - .setSubtype(DozeEvent.REASON_SENSOR_WAKE_UP)); + .setSubtype(DozeLog.REASON_SENSOR_WAKE_UP)); } } } @@ -361,7 +366,7 @@ public class DozeTriggers implements DozeMachine.Part { // When already pulsing we're allowed to show the wallpaper directly without // requesting a new pulse. if (mMachine.getState() == DozeMachine.State.DOZE_PULSING - && reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) { + && reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) { mMachine.requestState(DozeMachine.State.DOZE_PULSING_BRIGHT); return; } @@ -426,7 +431,7 @@ public class DozeTriggers implements DozeMachine.Part { public void onReceive(Context context, Intent intent) { if (PULSE_ACTION.equals(intent.getAction())) { if (DozeMachine.DEBUG) Log.d(TAG, "Received pulse intent"); - requestPulse(DozeEvent.PULSE_REASON_INTENT, false, /* performedProxCheck */ + requestPulse(DozeLog.PULSE_REASON_INTENT, false, /* performedProxCheck */ null /* onPulseSupressedListener */); } if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(intent.getAction())) { diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java index a6aa90916c25..1c056215f1cb 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java @@ -100,7 +100,7 @@ public class DozeUi implements DozeMachine.Part { public void onPulseStarted() { try { mMachine.requestState( - reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN + reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN ? DozeMachine.State.DOZE_PULSING_BRIGHT : DozeMachine.State.DOZE_PULSING); } catch (IllegalStateException e) { diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 37351985b3bd..45c07a3e4693 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -40,6 +40,7 @@ import android.content.IntentFilter; import android.content.pm.UserInfo; import android.content.res.Resources; import android.database.ContentObserver; +import android.graphics.Color; import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.net.ConnectivityManager; @@ -93,13 +94,13 @@ import com.android.systemui.MultiListLayout; import com.android.systemui.MultiListLayout.MultiListAdapter; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.colorextraction.SysuiColorExtractor; +import com.android.systemui.controls.ui.ControlsUiController; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; import com.android.systemui.plugins.GlobalActionsPanelPlugin; -import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.BlurUtils; -import com.android.systemui.statusbar.phone.ScrimController; +import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.EmergencyDialerConstants; @@ -183,6 +184,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private final IStatusBarService mStatusBarService; private final NotificationShadeWindowController mNotificationShadeWindowController; private GlobalActionsPanelPlugin mPanelPlugin; + private ControlsUiController mControlsUiController; /** * @param context everything needs a context :( @@ -200,7 +202,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, TelecomManager telecomManager, MetricsLogger metricsLogger, BlurUtils blurUtils, SysuiColorExtractor colorExtractor, IStatusBarService statusBarService, - NotificationShadeWindowController notificationShadeWindowController) { + NotificationShadeWindowController notificationShadeWindowController, + ControlsUiController controlsUiController) { mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme); mWindowManagerFuncs = windowManagerFuncs; mAudioManager = audioManager; @@ -220,6 +223,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mSysuiColorExtractor = colorExtractor; mStatusBarService = statusBarService; mNotificationShadeWindowController = notificationShadeWindowController; + mControlsUiController = controlsUiController; // receive broadcasts IntentFilter filter = new IntentFilter(); @@ -457,7 +461,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, panelViewController, mBlurUtils, mSysuiColorExtractor, mStatusBarService, - mNotificationShadeWindowController, isControlsEnabled(mContext)); + mNotificationShadeWindowController, + shouldShowControls() ? mControlsUiController : null); dialog.setCanceledOnTouchOutside(false); // Handled by the custom class. dialog.setKeyguardShowing(mKeyguardShowing); @@ -531,7 +536,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, @Override public boolean shouldBeSeparated() { - return !isControlsEnabled(mContext); + return !shouldShowControls(); } @Override @@ -1128,7 +1133,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, * A single press action maintains no state, just responds to a press * and takes an action. */ - private static abstract class SinglePressAction implements Action { + + private abstract class SinglePressAction implements Action { private final int mIconResId; private final Drawable mIcon; private final int mMessageResId; @@ -1167,7 +1173,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } protected int getActionLayoutId(Context context) { - if (isControlsEnabled(context)) { + if (shouldShowControls()) { return com.android.systemui.R.layout.global_actions_grid_item_v2; } return com.android.systemui.R.layout.global_actions_grid_item; @@ -1543,13 +1549,15 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private boolean mHadTopUi; private final NotificationShadeWindowController mNotificationShadeWindowController; private final BlurUtils mBlurUtils; - private final boolean mControlsEnabled; + + private ControlsUiController mControlsUiController; + private ViewGroup mControlsView; ActionsDialog(Context context, MyAdapter adapter, GlobalActionsPanelPlugin.PanelViewController plugin, BlurUtils blurUtils, SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService, NotificationShadeWindowController notificationShadeWindowController, - boolean controlsEnabled) { + ControlsUiController controlsUiController) { super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions); mContext = context; mAdapter = adapter; @@ -1557,7 +1565,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mColorExtractor = sysuiColorExtractor; mStatusBarService = statusBarService; mNotificationShadeWindowController = notificationShadeWindowController; - mControlsEnabled = controlsEnabled; + mControlsUiController = controlsUiController; // Window initialization Window window = getWindow(); @@ -1577,7 +1585,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY); - window.setFitWindowInsetsTypes(0 /* types */); + window.getAttributes().setFitInsetsTypes(0 /* types */); setTitle(R.string.global_actions); mPanelController = plugin; @@ -1631,14 +1639,13 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT); panelContainer.addView(mPanelController.getPanelContent(), panelParams); - mBackgroundDrawable = mPanelController.getBackgroundDrawable(); - mScrimAlpha = 1f; } } private void initializeLayout() { setContentView(getGlobalActionsLayoutId(mContext)); fixNavBarClipping(); + mControlsView = findViewById(com.android.systemui.R.id.global_actions_controls); mGlobalActionsLayout = findViewById(com.android.systemui.R.id.global_actions_view); mGlobalActionsLayout.setOutsideTouchListener(view -> dismiss()); ((View) mGlobalActionsLayout.getParent()).setOnClickListener(view -> dismiss()); @@ -1659,7 +1666,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } if (mBackgroundDrawable == null) { mBackgroundDrawable = new ScrimDrawable(); - mScrimAlpha = ScrimController.GRADIENT_SCRIM_ALPHA; + mScrimAlpha = 0.8f; } getWindow().setBackgroundDrawable(mBackgroundDrawable); } @@ -1674,7 +1681,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } private int getGlobalActionsLayoutId(Context context) { - if (mControlsEnabled) { + if (mControlsUiController != null) { return com.android.systemui.R.layout.global_actions_grid_v2; } @@ -1718,7 +1725,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, if (!(mBackgroundDrawable instanceof ScrimDrawable)) { return; } - ((ScrimDrawable) mBackgroundDrawable).setColor(colors.getMainColor(), animate); + ((ScrimDrawable) mBackgroundDrawable).setColor(Color.BLACK, animate); View decorView = getWindow().getDecorView(); if (colors.supportsDarkText()) { decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR | @@ -1758,6 +1765,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mBlurUtils.radiusForRatio(animatedValue)); }) .start(); + if (mControlsUiController != null) { + mControlsUiController.show(mControlsView); + } } @Override @@ -1766,6 +1776,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, return; } mShowing = false; + if (mControlsUiController != null) mControlsUiController.hide(); mGlobalActionsLayout.setTranslationX(0); mGlobalActionsLayout.setTranslationY(0); mGlobalActionsLayout.setAlpha(1); @@ -1790,6 +1801,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, void dismissImmediately() { mShowing = false; + if (mControlsUiController != null) mControlsUiController.hide(); dismissPanel(); resetOrientation(); completeDismiss(); @@ -1886,8 +1898,10 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, return true; } - private static boolean isControlsEnabled(Context context) { - return Settings.Secure.getInt( - context.getContentResolver(), "systemui.controls_available", 0) == 1; + private boolean shouldShowControls() { + return isCurrentUserOwner() + && !mKeyguardManager.isDeviceLocked() + && Settings.Secure.getInt(mContext.getContentResolver(), + "systemui.controls_available", 0) == 1; } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java index c911bf28effd..c9c38d31e865 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java @@ -30,17 +30,17 @@ import android.widget.ProgressBar; import android.widget.TextView; import com.android.internal.R; -import com.android.internal.colorextraction.ColorExtractor.GradientColors; import com.android.internal.colorextraction.drawable.ScrimDrawable; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.settingslib.Utils; import com.android.systemui.Dependency; -import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.plugins.GlobalActions; import com.android.systemui.plugins.GlobalActionsPanelPlugin; import com.android.systemui.plugins.PluginListener; import com.android.systemui.shared.plugins.PluginManager; +import com.android.systemui.statusbar.BlurUtils; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -59,6 +59,7 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, private final KeyguardStateController mKeyguardStateController; private final DeviceProvisionedController mDeviceProvisionedController; private final ExtensionController.Extension<GlobalActionsPanelPlugin> mPanelExtension; + private final BlurUtils mBlurUtils; private GlobalActionsPanelPlugin mPlugin; private final CommandQueue mCommandQueue; private GlobalActionsDialog mGlobalActionsDialog; @@ -68,13 +69,14 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, @Inject public GlobalActionsImpl(Context context, CommandQueue commandQueue, - Lazy<GlobalActionsDialog> globalActionsDialogLazy) { + Lazy<GlobalActionsDialog> globalActionsDialogLazy, BlurUtils blurUtils) { mContext = context; mGlobalActionsDialogLazy = globalActionsDialogLazy; mKeyguardStateController = Dependency.get(KeyguardStateController.class); mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); mPluginManager = Dependency.get(PluginManager.class); mCommandQueue = commandQueue; + mBlurUtils = blurUtils; mCommandQueue.addCallback(this); mPanelExtension = Dependency.get(ExtensionController.class) .newExtension(GlobalActionsPanelPlugin.class) @@ -110,7 +112,6 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, @Override public void showShutdownUi(boolean isReboot, String reason) { ScrimDrawable background = new ScrimDrawable(); - background.setAlpha((int) (SHUTDOWN_SCRIM_ALPHA * 255)); Dialog d = new Dialog(mContext, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions); @@ -126,7 +127,7 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, window.getAttributes().height = ViewGroup.LayoutParams.MATCH_PARENT; window.getAttributes().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY); - window.setFitWindowInsetsTypes(0 /* types */); + window.getAttributes().setFitInsetsTypes(0 /* types */); window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); window.addFlags( WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN @@ -160,8 +161,13 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, reasonView.setText(rebootReasonMessage); } - GradientColors colors = Dependency.get(SysuiColorExtractor.class).getNeutralColors(); - background.setColor(colors.getMainColor(), false); + if (mBlurUtils.supportsBlursOnWindows()) { + background.setAlpha((int) (ScrimController.GRADIENT_SCRIM_ALPHA_BUSY * 255)); + mBlurUtils.applyBlur(d.getWindow().getDecorView().getViewRootImpl(), + mBlurUtils.radiusForRatio(1)); + } else { + background.setAlpha((int) (SHUTDOWN_SCRIM_ALPHA * 255)); + } d.show(); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java index e66a9fadb937..2cc3d9e22a7d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java @@ -52,13 +52,13 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SystemUIAppComponentFactory; +import com.android.systemui.SystemUIFactory; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.NextAlarmController; -import com.android.systemui.statusbar.policy.NextAlarmControllerImpl; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.util.wakelock.SettableWakeLock; import com.android.systemui.util.wakelock.WakeLock; @@ -68,6 +68,8 @@ import java.util.Locale; import java.util.TimeZone; import java.util.concurrent.TimeUnit; +import javax.inject.Inject; + /** * Simple Slice provider that shows the current date. */ @@ -108,26 +110,31 @@ public class KeyguardSliceProvider extends SliceProvider implements private final Handler mHandler; private final Handler mMediaHandler; private final AlarmManager.OnAlarmListener mUpdateNextAlarm = this::updateNextAlarm; - private DozeParameters mDozeParameters; + @Inject + public DozeParameters mDozeParameters; @VisibleForTesting protected SettableWakeLock mMediaWakeLock; - @VisibleForTesting - protected ZenModeController mZenModeController; + @Inject + public ZenModeController mZenModeController; private String mDatePattern; private DateFormat mDateFormat; private String mLastText; private boolean mRegistered; private String mNextAlarm; - private NextAlarmController mNextAlarmController; - @VisibleForTesting - protected AlarmManager mAlarmManager; - @VisibleForTesting - protected ContentResolver mContentResolver; + @Inject + public NextAlarmController mNextAlarmController; + @Inject + public AlarmManager mAlarmManager; + @Inject + public ContentResolver mContentResolver; private AlarmManager.AlarmClockInfo mNextAlarmInfo; private PendingIntent mPendingIntent; - protected NotificationMediaManager mMediaManager; - private StatusBarStateController mStatusBarStateController; - private KeyguardBypassController mKeyguardBypassController; + @Inject + public NotificationMediaManager mMediaManager; + @Inject + public StatusBarStateController mStatusBarStateController; + @Inject + public KeyguardBypassController mKeyguardBypassController; private CharSequence mMediaTitle; private CharSequence mMediaArtist; protected boolean mDozing; @@ -188,26 +195,6 @@ public class KeyguardSliceProvider extends SliceProvider implements mMediaUri = Uri.parse(KEYGUARD_MEDIA_URI); } - /** - * Initialize dependencies that don't exist during {@link android.content.ContentProvider} - * instantiation. - * - * @param mediaManager {@link NotificationMediaManager} singleton. - * @param statusBarStateController {@link StatusBarStateController} singleton. - */ - public void initDependencies( - NotificationMediaManager mediaManager, - StatusBarStateController statusBarStateController, - KeyguardBypassController keyguardBypassController, - DozeParameters dozeParameters) { - mMediaManager = mediaManager; - mMediaManager.addCallback(this); - mStatusBarStateController = statusBarStateController; - mStatusBarStateController.addCallback(this); - mKeyguardBypassController = keyguardBypassController; - mDozeParameters = dozeParameters; - } - @AnyThread @Override public Slice onBindSlice(Uri sliceUri) { @@ -310,25 +297,19 @@ public class KeyguardSliceProvider extends SliceProvider implements @Override public boolean onCreateSliceProvider() { - if (mContextAvailableCallback != null) { - mContextAvailableCallback.onContextAvailable(getContext()); - } + mContextAvailableCallback.onContextAvailable(getContext()); + inject(); synchronized (KeyguardSliceProvider.sInstanceLock) { KeyguardSliceProvider oldInstance = KeyguardSliceProvider.sInstance; if (oldInstance != null) { oldInstance.onDestroy(); } - - mAlarmManager = getContext().getSystemService(AlarmManager.class); - mContentResolver = getContext().getContentResolver(); - mNextAlarmController = new NextAlarmControllerImpl(getContext()); - mNextAlarmController.addCallback(this); - mZenModeController = Dependency.get(ZenModeController.class); - mZenModeController.addCallback(this); mDatePattern = getContext().getString(R.string.system_ui_aod_date_pattern); mPendingIntent = PendingIntent.getActivity(getContext(), 0, new Intent(), 0); - mMediaWakeLock = new SettableWakeLock(WakeLock.createPartial(getContext(), "media"), - "media"); + mMediaManager.addCallback(this); + mStatusBarStateController.addCallback(this); + mNextAlarmController.addCallback(this); + mZenModeController.addCallback(this); KeyguardSliceProvider.sInstance = this; registerClockUpdate(); updateClockLocked(); @@ -337,6 +318,13 @@ public class KeyguardSliceProvider extends SliceProvider implements } @VisibleForTesting + protected void inject() { + SystemUIFactory.getInstance().getRootComponent().inject(this); + mMediaWakeLock = new SettableWakeLock(WakeLock.createPartial(getContext(), "media"), + "media"); + } + + @VisibleForTesting protected void onDestroy() { synchronized (KeyguardSliceProvider.sInstanceLock) { mNextAlarmController.removeCallback(this); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 50a89ad9af10..15e3a0a2e023 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -18,6 +18,7 @@ package com.android.systemui.keyguard; import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; +import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT; @@ -53,6 +54,7 @@ import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; +import android.provider.DeviceConfig; import android.provider.Settings; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; @@ -371,6 +373,7 @@ public class KeyguardViewMediator extends SystemUI { private boolean mPulsing; private boolean mLockLater; + private boolean mShowHomeOverLockscreen; private boolean mWakeAndUnlocking; private IKeyguardDrawnCallback mDrawnCallback; @@ -703,6 +706,20 @@ public class KeyguardViewMediator extends SystemUI { mStatusBarKeyguardViewManagerLazy = statusBarKeyguardViewManagerLazy; mDismissCallbackRegistry = dismissCallbackRegistry; mUiBgExecutor = uiBgExecutor; + mShowHomeOverLockscreen = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_SYSTEMUI, + NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN, + /* defaultValue = */ true); + DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post, + new DeviceConfig.OnPropertiesChangedListener() { + @Override + public void onPropertiesChanged(DeviceConfig.Properties properties) { + if (properties.getKeyset().contains(NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN)) { + mShowHomeOverLockscreen = properties.getBoolean( + NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN, true /* defaultValue */); + } + } + }); } public void userActivity() { @@ -1973,7 +1990,10 @@ public class KeyguardViewMediator extends SystemUI { // windows that appear on top, ever int flags = StatusBarManager.DISABLE_NONE; if (forceHideHomeRecentsButtons || isShowingAndNotOccluded()) { - flags |= StatusBarManager.DISABLE_HOME | StatusBarManager.DISABLE_RECENT; + if (!mShowHomeOverLockscreen) { + flags |= StatusBarManager.DISABLE_HOME; + } + flags |= StatusBarManager.DISABLE_RECENT; } if (DEBUG) { diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt new file mode 100644 index 000000000000..18c7baec1f74 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log + +import android.util.Log +import com.android.systemui.DumpController +import com.android.systemui.Dumpable +import com.android.systemui.log.dagger.LogModule +import java.text.SimpleDateFormat +import java.util.ArrayDeque +import java.util.Locale + +/** + * A simple ring buffer of recyclable log messages + * + * The goal of this class is to enable logging that is both extremely chatty and extremely + * lightweight. If done properly, logging a message will not result in any heap allocations or + * string generation. Messages are only converted to strings if the log is actually dumped (usually + * as the result of taking a bug report). + * + * You can dump the entire buffer at any time by running: + * + * ``` + * $ adb shell dumpsys activity service com.android.systemui/.SystemUIService \ + * dependency DumpController <bufferName> + * ``` + * + * where `bufferName` is the (case-sensitive) [name] passed to the constructor. + * + * By default, only messages of WARN level or higher are echoed to logcat, but this can be adjusted + * locally (usually for debugging purposes). + * + * To enable logcat echoing for an entire buffer: + * + * ``` + * $ adb shell settings put global systemui/buffer/<bufferName> <level> + * ``` + * + * To enable logcat echoing for a specific tag: + * + * ``` + * $ adb shell settings put global systemui/tag/<tag> <level> + * ``` + * + * In either case, `level` can be any of `verbose`, `debug`, `info`, `warn`, `error`, `assert`, or + * the first letter of any of the previous. + * + * Buffers are provided by [LogModule]. + * + * @param name The name of this buffer + * @param maxLogs The maximum number of messages to keep in memory at any one time, including the + * unused pool. + * @param poolSize The maximum amount that the size of the buffer is allowed to flex in response to + * sequential calls to [document] that aren't immediately followed by a matching call to [push]. + */ +class LogBuffer( + private val name: String, + private val maxLogs: Int, + private val poolSize: Int, + private val logcatEchoTracker: LogcatEchoTracker +) { + private val buffer: ArrayDeque<LogMessageImpl> = ArrayDeque() + + fun attach(dumpController: DumpController) { + dumpController.registerDumpable(name, onDump) + } + + /** + * Logs a message to the log buffer + * + * May also log the message to logcat if echoing is enabled for this buffer or tag. + * + * The actual string of the log message is not constructed until it is needed. To accomplish + * this, logging a message is a two-step process. First, a fresh instance of [LogMessage] is + * obtained and is passed to the [initializer]. The initializer stores any relevant data on the + * message's fields. The message is then inserted into the buffer where it waits until it is + * either pushed out by newer messages or it needs to printed. If and when this latter moment + * occurs, the [printer] function is called on the message. It reads whatever data the + * initializer stored and converts it to a human-readable log message. + * + * @param tag A string of at most 23 characters, used for grouping logs into categories or + * subjects. If this message is echoed to logcat, this will be the tag that is used. + * @param level Which level to log the message at, both to the buffer and to logcat if it's + * echoed. In general, a module should split most of its logs into either INFO or DEBUG level. + * INFO level should be reserved for information that other parts of the system might care + * about, leaving the specifics of code's day-to-day operations to DEBUG. + * @param initializer A function that will be called immediately to store relevant data on the + * log message. The value of `this` will be the LogMessage to be initialized. + * @param printer A function that will be called if and when the message needs to be dumped to + * logcat or a bug report. It should read the data stored by the initializer and convert it to + * a human-readable string. The value of `this` will be the LogMessage to be printed. + * **IMPORTANT:** The printer should ONLY ever reference fields on the LogMessage and NEVER any + * variables in its enclosing scope. Otherwise, the runtime will need to allocate a new instance + * of the printer for each call, thwarting our attempts at avoiding any sort of allocation. + */ + inline fun log( + tag: String, + level: LogLevel, + initializer: LogMessage.() -> Unit, + noinline printer: LogMessage.() -> String + ) { + val message = obtain(tag, level, printer) + initializer(message) + push(message) + } + + /** + * Same as [log], but doesn't push the message to the buffer. Useful if you need to supply a + * "reason" for doing something (the thing you supply the reason to will presumably call [push] + * on that message at some point). + */ + inline fun document( + tag: String, + level: LogLevel, + initializer: LogMessage.() -> Unit, + noinline printer: LogMessage.() -> String + ): LogMessage { + val message = obtain(tag, level, printer) + initializer(message) + return message + } + + /** + * Obtains an instance of [LogMessageImpl], usually from the object pool. If the pool has been + * exhausted, creates a new instance. + * + * In general, you should call [log] or [document] instead of this method. + */ + fun obtain( + tag: String, + level: LogLevel, + printer: (LogMessage) -> String + ): LogMessageImpl { + val message = synchronized(buffer) { + if (buffer.size > maxLogs - poolSize) { + buffer.removeFirst() + } else { + LogMessageImpl.create() + } + } + message.reset(tag, level, System.currentTimeMillis(), printer) + return message + } + + /** + * Pushes a message into buffer, possibly evicting an older message if the buffer is full. + */ + fun push(message: LogMessage) { + synchronized(buffer) { + if (buffer.size == maxLogs) { + Log.e(TAG, "LogBuffer $name has exceeded its pool size") + buffer.removeFirst() + } + buffer.add(message as LogMessageImpl) + if (logcatEchoTracker.isBufferLoggable(name, message.level) || + logcatEchoTracker.isTagLoggable(message.tag, message.level)) { + echoToLogcat(message) + } + } + } + + /** Converts the entire buffer to a newline-delimited string */ + fun dump(): String { + synchronized(buffer) { + val sb = StringBuilder() + for (message in buffer) { + dumpMessage(message, sb) + } + return sb.toString() + } + } + + private fun dumpMessage(message: LogMessage, sb: StringBuilder) { + sb.append(DATE_FORMAT.format(message.timestamp)) + .append(" ").append(message.level) + .append(" ").append(message.tag) + .append(" ").append(message.printer(message)) + .append("\n") + } + + private fun echoToLogcat(message: LogMessage) { + val strMessage = message.printer(message) + when (message.level) { + LogLevel.VERBOSE -> Log.v(message.tag, strMessage) + LogLevel.DEBUG -> Log.d(message.tag, strMessage) + LogLevel.INFO -> Log.i(message.tag, strMessage) + LogLevel.WARNING -> Log.w(message.tag, strMessage) + LogLevel.ERROR -> Log.e(message.tag, strMessage) + LogLevel.WTF -> Log.wtf(message.tag, strMessage) + } + } + + private val onDump = Dumpable { _, pw, _ -> + pw.println(dump()) + } +} + +private const val TAG = "LogBuffer" +private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.S", Locale.US) diff --git a/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt b/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt new file mode 100644 index 000000000000..7b9af0f91200 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log + +import android.util.Log + +/** + * Enum version of @Log.Level + */ +enum class LogLevel(@Log.Level val nativeLevel: Int) { + VERBOSE(Log.VERBOSE), + DEBUG(Log.DEBUG), + INFO(Log.INFO), + WARNING(Log.WARN), + ERROR(Log.ERROR), + WTF(Log.ASSERT) +} diff --git a/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt b/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt new file mode 100644 index 000000000000..2a0a2aa6fb38 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log + +/** + * Generic data class for storing messages logged to a [LogBuffer] + * + * Each LogMessage has a few standard fields ([level], [tag], and [timestamp]). The rest are generic + * data slots that may or may not be used, depending on the nature of the specific message being + * logged. + * + * When a message is logged, the code doing the logging stores data in one or more of the generic + * fields ([str1], [int1], etc). When it comes time to dump the message to logcat/bugreport/etc, the + * [printer] function reads the data stored in the generic fields and converts that to a human- + * readable string. Thus, for every log type there must be a specialized initializer function that + * stores data specific to that log type and a specialized printer function that prints that data. + * + * See [LogBuffer.log] for more information. + */ +interface LogMessage { + val level: LogLevel + val tag: String + val timestamp: Long + val printer: LogMessage.() -> String + + var str1: String? + var str2: String? + var str3: String? + var int1: Int + var int2: Int + var long1: Long + var long2: Long + var double1: Double + var bool1: Boolean + var bool2: Boolean + var bool3: Boolean + var bool4: Boolean +} diff --git a/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt b/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt new file mode 100644 index 000000000000..d33ac4b4a80b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log + +/** + * Recyclable implementation of [LogMessage]. + */ +data class LogMessageImpl( + override var level: LogLevel, + override var tag: String, + override var timestamp: Long, + override var printer: LogMessage.() -> String, + override var str1: String?, + override var str2: String?, + override var str3: String?, + override var int1: Int, + override var int2: Int, + override var long1: Long, + override var long2: Long, + override var double1: Double, + override var bool1: Boolean, + override var bool2: Boolean, + override var bool3: Boolean, + override var bool4: Boolean +) : LogMessage { + + fun reset( + tag: String, + level: LogLevel, + timestamp: Long, + renderer: LogMessage.() -> String + ) { + this.level = level + this.tag = tag + this.timestamp = timestamp + this.printer = renderer + str1 = null + str2 = null + str3 = null + int1 = 0 + int2 = 0 + long1 = 0 + long2 = 0 + double1 = 0.0 + bool1 = false + bool2 = false + bool3 = false + bool4 = false + } + + companion object Factory { + fun create(): LogMessageImpl { + return LogMessageImpl( + LogLevel.DEBUG, + DEFAULT_TAG, + 0, + DEFAULT_RENDERER, + null, + null, + null, + 0, + 0, + 0, + 0, + 0.0, + false, + false, + false, + false) + } + } +} + +private const val DEFAULT_TAG = "UnknownTag" +private val DEFAULT_RENDERER: LogMessage.() -> String = { "Unknown message: $this" } diff --git a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTracker.kt b/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTracker.kt new file mode 100644 index 000000000000..3022f4b42a42 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTracker.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log + +/** + * Keeps track of which [LogBuffer] messages should also appear in logcat. + */ +interface LogcatEchoTracker { + /** + * Whether [bufferName] should echo messages of [level] or higher to logcat. + */ + fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean + + /** + * Whether [tagName] should echo messages of [level] or higher to logcat. + */ + fun isTagLoggable(tagName: String, level: LogLevel): Boolean +} diff --git a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt b/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt new file mode 100644 index 000000000000..23942e1d6e3c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log + +import android.content.ContentResolver +import android.database.ContentObserver +import android.net.Uri +import android.os.Handler +import android.os.Looper +import android.provider.Settings + +/** + * Version of [LogcatEchoTracker] for debuggable builds + * + * The log level of individual buffers or tags can be controlled via global settings: + * + * ``` + * # Echo any message to <bufferName> of <level> or higher + * $ adb shell settings put global systemui/buffer/<bufferName> <level> + * + * # Echo any message of <tag> and of <level> or higher + * $ adb shell settings put global systemui/tag/<tag> <level> + * ``` + */ +class LogcatEchoTrackerDebug private constructor( + private val contentResolver: ContentResolver +) : LogcatEchoTracker { + private val cachedBufferLevels: MutableMap<String, LogLevel> = mutableMapOf() + private val cachedTagLevels: MutableMap<String, LogLevel> = mutableMapOf() + + companion object Factory { + @JvmStatic + fun create( + contentResolver: ContentResolver, + mainLooper: Looper + ): LogcatEchoTrackerDebug { + val tracker = LogcatEchoTrackerDebug(contentResolver) + tracker.attach(mainLooper) + return tracker + } + } + + private fun attach(mainLooper: Looper) { + contentResolver.registerContentObserver( + Settings.Global.getUriFor(BUFFER_PATH), + true, + object : ContentObserver(Handler(mainLooper)) { + override fun onChange(selfChange: Boolean, uri: Uri) { + super.onChange(selfChange, uri) + cachedBufferLevels.clear() + } + }) + + contentResolver.registerContentObserver( + Settings.Global.getUriFor(TAG_PATH), + true, + object : ContentObserver(Handler(mainLooper)) { + override fun onChange(selfChange: Boolean, uri: Uri) { + super.onChange(selfChange, uri) + cachedTagLevels.clear() + } + }) + } + + /** + * Whether [bufferName] should echo messages of [level] or higher to logcat. + */ + @Synchronized + override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean { + return level.ordinal >= getLogLevel(bufferName, BUFFER_PATH, cachedBufferLevels).ordinal + } + + /** + * Whether [tagName] should echo messages of [level] or higher to logcat. + */ + @Synchronized + override fun isTagLoggable(tagName: String, level: LogLevel): Boolean { + return level >= getLogLevel(tagName, TAG_PATH, cachedTagLevels) + } + + private fun getLogLevel( + name: String, + path: String, + cache: MutableMap<String, LogLevel> + ): LogLevel { + return cache[name] ?: readSetting("$path/$name").also { cache[name] = it } + } + + private fun readSetting(path: String): LogLevel { + return try { + parseProp(Settings.Global.getString(contentResolver, path)) + } catch (_: Settings.SettingNotFoundException) { + DEFAULT_LEVEL + } + } + + private fun parseProp(propValue: String?): LogLevel { + return when (propValue?.toLowerCase()) { + "verbose" -> LogLevel.VERBOSE + "v" -> LogLevel.VERBOSE + "debug" -> LogLevel.DEBUG + "d" -> LogLevel.DEBUG + "info" -> LogLevel.INFO + "i" -> LogLevel.INFO + "warning" -> LogLevel.WARNING + "warn" -> LogLevel.WARNING + "w" -> LogLevel.WARNING + "error" -> LogLevel.ERROR + "e" -> LogLevel.ERROR + "assert" -> LogLevel.WTF + "wtf" -> LogLevel.WTF + else -> DEFAULT_LEVEL + } + } +} + +private val DEFAULT_LEVEL = LogLevel.WARNING +private const val BUFFER_PATH = "systemui/buffer" +private const val TAG_PATH = "systemui/tag" diff --git a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerProd.kt b/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerProd.kt new file mode 100644 index 000000000000..394f624a3e58 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerProd.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log + +/** + * Production version of [LogcatEchoTracker] that isn't configurable. + */ +class LogcatEchoTrackerProd : LogcatEchoTracker { + override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean { + return level >= LogLevel.WARNING + } + + override fun isTagLoggable(tagName: String, level: LogLevel): Boolean { + return level >= LogLevel.WARNING + } +} diff --git a/packages/SystemUI/src/com/android/systemui/log/SysuiLog.java b/packages/SystemUI/src/com/android/systemui/log/SysuiLog.java index 4e15668f6a34..9ee3e6765e4a 100644 --- a/packages/SystemUI/src/com/android/systemui/log/SysuiLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/SysuiLog.java @@ -48,7 +48,7 @@ import java.util.Locale; */ public class SysuiLog<E extends Event> implements Dumpable { public static final SimpleDateFormat DATE_FORMAT = - new SimpleDateFormat("MM-dd HH:mm:ss", Locale.US); + new SimpleDateFormat("MM-dd HH:mm:ss.S", Locale.US); protected final Object mDataLock = new Object(); private final String mId; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java new file mode 100644 index 000000000000..7c5f4025117f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.android.systemui.log.LogBuffer; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +/** A {@link LogBuffer} for dozing-related messages. */ +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface DozeLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java new file mode 100644 index 000000000000..e119beff3c6f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.dagger; + +import android.content.ContentResolver; +import android.os.Build; +import android.os.Looper; + +import com.android.systemui.DumpController; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.log.LogBuffer; +import com.android.systemui.log.LogcatEchoTracker; +import com.android.systemui.log.LogcatEchoTrackerDebug; +import com.android.systemui.log.LogcatEchoTrackerProd; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +/** + * Dagger module for providing instances of {@link LogBuffer}. + */ +@Module +public class LogModule { + /** Provides a logging buffer for doze-related logs. */ + @Provides + @Singleton + @DozeLog + public static LogBuffer provideDozeLogBuffer( + LogcatEchoTracker bufferFilter, + DumpController dumpController) { + LogBuffer buffer = new LogBuffer("DozeLog", 100, 10, bufferFilter); + buffer.attach(dumpController); + return buffer; + } + + /** Provides a logging buffer for all logs related to the data layer of notifications. */ + @Provides + @Singleton + @NotificationLog + public static LogBuffer provideNotificationsLogBuffer( + LogcatEchoTracker bufferFilter, + DumpController dumpController) { + LogBuffer buffer = new LogBuffer("NotifLog2", 1000, 10, bufferFilter); + buffer.attach(dumpController); + return buffer; + } + + /** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */ + @Provides + @Singleton + public static LogcatEchoTracker provideLogcatEchoTracker( + ContentResolver contentResolver, + @Main Looper looper) { + if (Build.IS_DEBUGGABLE) { + return LogcatEchoTrackerDebug.create(contentResolver, looper); + } else { + return new LogcatEchoTrackerProd(); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java new file mode 100644 index 000000000000..a0b686487bec --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.android.systemui.log.LogBuffer; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +/** A {@link LogBuffer} for notification-related messages. */ +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface NotificationLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/pip/BasePipManager.java b/packages/SystemUI/src/com/android/systemui/pip/BasePipManager.java index d1d9b3de7e69..92aa02050d28 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/BasePipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/BasePipManager.java @@ -20,13 +20,13 @@ import android.content.Context; import android.content.res.Configuration; import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.wm.DisplayWindowController; +import com.android.systemui.wm.DisplayController; import java.io.PrintWriter; public interface BasePipManager { void initialize(Context context, BroadcastDispatcher broadcastDispatcher, - DisplayWindowController displayWindowController); + DisplayController displayController); void showPictureInPictureMenu(); default void expandPip() {} default void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {} diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java index 8e34a90e7b51..6f03f18ef64b 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java @@ -261,8 +261,6 @@ public class PipBoundsHandler { mPinnedStackController.startAnimation(destinationBounds, sourceRectHint, -1 /* animationDuration */); mLastDestinationBounds.set(destinationBounds); - mPinnedStackController.reportBounds(defaultBounds, - getMovementBounds(defaultBounds)); } catch (RemoteException e) { Log.e(TAG, "Failed to start PiP animation from SysUI", e); } @@ -317,7 +315,6 @@ public class PipBoundsHandler { outBounds.set(postChangeStackBounds); mLastDestinationBounds.set(outBounds); mPinnedStackController.resetBoundsAnimation(outBounds); - mPinnedStackController.reportBounds(outBounds, getMovementBounds(outBounds)); t.setBounds(pinnedStackInfo.stackToken, outBounds); } catch (RemoteException e) { Log.e(TAG, "Failed to resize PiP on display rotation", e); diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipUI.java b/packages/SystemUI/src/com/android/systemui/pip/PipUI.java index 29de90bb2824..cecdc9cbb4f3 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipUI.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipUI.java @@ -28,7 +28,7 @@ import android.os.UserManager; import com.android.systemui.SystemUI; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.wm.DisplayWindowController; +import com.android.systemui.wm.DisplayController; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -45,17 +45,17 @@ public class PipUI extends SystemUI implements CommandQueue.Callbacks { private final CommandQueue mCommandQueue; private BasePipManager mPipManager; private final BroadcastDispatcher mBroadcastDispatcher; - private final DisplayWindowController mDisplayWindowController; + private final DisplayController mDisplayController; private boolean mSupportsPip; @Inject public PipUI(Context context, CommandQueue commandQueue, BroadcastDispatcher broadcastDispatcher, - DisplayWindowController displayWindowController) { + DisplayController displayController) { super(context); mBroadcastDispatcher = broadcastDispatcher; mCommandQueue = commandQueue; - mDisplayWindowController = displayWindowController; + mDisplayController = displayController; } @Override @@ -75,7 +75,7 @@ public class PipUI extends SystemUI implements CommandQueue.Callbacks { mPipManager = pm.hasSystemFeature(FEATURE_LEANBACK_ONLY) ? com.android.systemui.pip.tv.PipManager.getInstance() : com.android.systemui.pip.phone.PipManager.getInstance(); - mPipManager.initialize(mContext, mBroadcastDispatcher, mDisplayWindowController); + mPipManager.initialize(mContext, mBroadcastDispatcher, mDisplayController); mCommandQueue.addCallback(this); } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java index 750cc607abe3..b7258117c48c 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java @@ -109,7 +109,7 @@ public class PipDismissViewController { lp.setTitle("pip-dismiss-overlay"); lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL; - lp.setFitWindowInsetsTypes(0 /* types */); + lp.setFitInsetsTypes(0 /* types */); mWindowManager.addView(mDismissView, lp); } mDismissView.animate().cancel(); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java index f39d1ecb0585..239ef3638ff6 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java @@ -46,7 +46,8 @@ import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.WindowManagerWrapper; -import com.android.systemui.wm.DisplayWindowController; +import com.android.systemui.wm.DisplayChangeController; +import com.android.systemui.wm.DisplayController; import java.io.PrintWriter; @@ -79,7 +80,7 @@ public class PipManager implements BasePipManager { /** * Handler for display rotation changes. */ - private final DisplayWindowController.OnDisplayWindowRotationController mRotationController = ( + private final DisplayChangeController.OnDisplayChangingListener mRotationController = ( int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) -> { final boolean changed = mPipBoundsHandler.onDisplayRotationChanged(mTmpNormalBounds, displayId, fromRotation, toRotation, t); @@ -230,7 +231,7 @@ public class PipManager implements BasePipManager { * Initializes {@link PipManager}. */ public void initialize(Context context, BroadcastDispatcher broadcastDispatcher, - DisplayWindowController displayWindowController) { + DisplayController displayController) { mContext = context; mActivityManager = ActivityManager.getService(); mActivityTaskManager = ActivityTaskManager.getService(); @@ -251,7 +252,7 @@ public class PipManager implements BasePipManager { mMenuController, mInputConsumerController, mPipBoundsHandler); mAppOpsListener = new PipAppOpsListener(context, mActivityManager, mTouchHandler.getMotionHelper()); - displayWindowController.addRotationController(mRotationController); + displayController.addDisplayChangingController(mRotationController); // If SystemUI restart, and it already existed a pinned stack, // register the pip input consumer to ensure touch can send to it. @@ -297,6 +298,13 @@ public class PipManager implements BasePipManager { } /** + * Sets a customized touch gesture that replaces the default one. + */ + public void setTouchGesture(PipTouchGesture gesture) { + mTouchHandler.setTouchGesture(gesture); + } + + /** * Sets both shelf visibility and its height. */ @Override diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchGesture.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchGesture.java index e8e8a4d3215a..72335dbed115 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchGesture.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchGesture.java @@ -24,19 +24,19 @@ public abstract class PipTouchGesture { /** * Handle the touch down. */ - void onDown(PipTouchState touchState) {} + public void onDown(PipTouchState touchState) {} /** * Handle the touch move, and return whether the event was consumed. */ - boolean onMove(PipTouchState touchState) { + public boolean onMove(PipTouchState touchState) { return false; } /** * Handle the touch up, and return whether the gesture was consumed. */ - boolean onUp(PipTouchState touchState) { + public boolean onUp(PipTouchState touchState) { return false; } } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index 09f163810d27..65cc666d5164 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -126,8 +126,8 @@ public class PipTouchHandler { // Touch state private final PipTouchState mTouchState; private final FlingAnimationUtils mFlingAnimationUtils; - private final PipTouchGesture[] mGestures; private final PipMotionHelper mMotionHelper; + private PipTouchGesture mGesture; // Temp vars private final Rect mTmpBounds = new Rect(); @@ -185,9 +185,7 @@ public class PipTouchHandler { mSnapAlgorithm = new PipSnapAlgorithm(mContext); mFlingAnimationUtils = new FlingAnimationUtils(context.getResources().getDisplayMetrics(), 2.5f); - mGestures = new PipTouchGesture[] { - mDefaultMovementGesture - }; + mGesture = new DefaultPipTouchGesture(); mMotionHelper = new PipMotionHelper(mContext, mActivityManager, mActivityTaskManager, mMenuController, mSnapAlgorithm, mFlingAnimationUtils); mTouchState = new PipTouchState(mViewConfig, mHandler, @@ -210,6 +208,10 @@ public class PipTouchHandler { this::onAccessibilityShowMenu, mHandler); } + public void setTouchGesture(PipTouchGesture gesture) { + mGesture = gesture; + } + public void setTouchEnabled(boolean enabled) { mTouchState.setAllowTouches(enabled); } @@ -363,17 +365,12 @@ public class PipTouchHandler { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: { mMotionHelper.synchronizePinnedStackBounds(); - - for (PipTouchGesture gesture : mGestures) { - gesture.onDown(mTouchState); - } + mGesture.onDown(mTouchState); break; } case MotionEvent.ACTION_MOVE: { - for (PipTouchGesture gesture : mGestures) { - if (gesture.onMove(mTouchState)) { - break; - } + if (mGesture.onMove(mTouchState)) { + break; } shouldDeliverToMenu = !mTouchState.isDragging(); @@ -384,10 +381,8 @@ public class PipTouchHandler { // dragging (ie. when the IME shows) updateMovementBounds(mMenuState); - for (PipTouchGesture gesture : mGestures) { - if (gesture.onUp(mTouchState)) { - break; - } + if (mGesture.onUp(mTouchState)) { + break; } // Fall through to clean up @@ -591,7 +586,7 @@ public class PipTouchHandler { /** * Gesture controlling normal movement of the PIP. */ - private PipTouchGesture mDefaultMovementGesture = new PipTouchGesture() { + private class DefaultPipTouchGesture extends PipTouchGesture { // Whether the PiP was on the left side of the screen at the start of the gesture private boolean mStartedOnLeft; private final Point mStartPosition = new Point(); @@ -623,7 +618,7 @@ public class PipTouchHandler { } @Override - boolean onMove(PipTouchState touchState) { + public boolean onMove(PipTouchState touchState) { if (!touchState.isUserInteracting()) { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java index 1d92375a7541..7532f9f11296 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java @@ -55,7 +55,7 @@ import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.WindowManagerWrapper; -import com.android.systemui.wm.DisplayWindowController; +import com.android.systemui.wm.DisplayController; import java.util.ArrayList; import java.util.List; @@ -230,7 +230,7 @@ public class PipManager implements BasePipManager { * Initializes {@link PipManager}. */ public void initialize(Context context, BroadcastDispatcher broadcastDispatcher, - DisplayWindowController displayWindowController) { + DisplayController displayController) { if (mInitialized) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt index f66a1ece1868..f710f7fc47e2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt @@ -68,7 +68,7 @@ class DoubleLineTileLayout(context: Context) : ViewGroup(context), QSPanel.QSTil override fun updateResources(): Boolean { with(mContext.resources) { smallTileSize = getDimensionPixelSize(R.dimen.qs_quick_tile_size) - cellMarginHorizontal = getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal) / 2 + cellMarginHorizontal = getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal) cellMarginVertical = getDimensionPixelSize(R.dimen.new_qs_vertical_margin) } requestLayout() diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java index 1077834e7146..9e3e94ce4186 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java @@ -36,7 +36,6 @@ import android.media.MediaMetadata; import android.media.session.MediaController; import android.media.session.MediaSession; import android.media.session.PlaybackState; -import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -45,7 +44,6 @@ import android.view.ViewGroup; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.RemoteViews; import android.widget.TextView; import androidx.core.graphics.drawable.RoundedBitmapDrawable; @@ -72,8 +70,6 @@ public class QSMediaPlayer { private View mSeamless; private MediaSession.Token mToken; private MediaController mController; - private int mWidth; - private int mHeight; private int mForegroundColor; private int mBackgroundColor; private ComponentName mRecvComponent; @@ -158,16 +154,11 @@ public class QSMediaPlayer { * * @param context * @param parent - * @param width - * @param height */ - public QSMediaPlayer(Context context, ViewGroup parent, int width, int height) { + public QSMediaPlayer(Context context, ViewGroup parent) { mContext = context; LayoutInflater inflater = LayoutInflater.from(mContext); mMediaNotifView = (LinearLayout) inflater.inflate(R.layout.qs_media_panel, parent, false); - - mWidth = width; - mHeight = height; } public View getView() { @@ -217,27 +208,18 @@ public class QSMediaPlayer { Notification.Builder builder = Notification.Builder.recoverBuilder(mContext, notif); // Album art - addAlbumArtBackground(mMediaMetadata, bgColor, mWidth, mHeight); + addAlbumArt(mMediaMetadata, bgColor); - // Reuse notification header instead of reimplementing everything - RemoteViews headerRemoteView = builder.makeNotificationHeader(); LinearLayout headerView = mMediaNotifView.findViewById(R.id.header); - View result = headerRemoteView.apply(mContext, headerView); - result.setPadding(0, 0, 0, 0); - LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, 75); - result.setLayoutParams(lp); - headerView.removeAllViews(); - headerView.addView(result); // App icon - ImageView appIcon = headerView.findViewById(com.android.internal.R.id.icon); + ImageView appIcon = headerView.findViewById(R.id.icon); Drawable iconDrawable = icon.loadDrawable(mContext); iconDrawable.setTint(iconColor); appIcon.setImageDrawable(iconDrawable); // App title - TextView appName = headerView.findViewById(com.android.internal.R.id.app_name_text); + TextView appName = headerView.findViewById(R.id.app_name); String appNameString = builder.loadHeaderAppName(); appName.setText(appNameString); appName.setTextColor(iconColor); @@ -254,25 +236,8 @@ public class QSMediaPlayer { } }); - // Separator - TextView separator = headerView.findViewById(com.android.internal.R.id.header_text_divider); - separator.setTextColor(iconColor); - - // Album name - TextView albumName = headerView.findViewById(com.android.internal.R.id.header_text); - String albumString = mMediaMetadata.getString(MediaMetadata.METADATA_KEY_ALBUM); - if (TextUtils.isEmpty(albumString)) { - albumName.setVisibility(View.GONE); - separator.setVisibility(View.GONE); - } else { - albumName.setText(albumString); - albumName.setTextColor(iconColor); - albumName.setVisibility(View.VISIBLE); - separator.setVisibility(View.VISIBLE); - } - // Transfer chip - mSeamless = headerView.findViewById(com.android.internal.R.id.media_seamless); + mSeamless = headerView.findViewById(R.id.media_seamless); mSeamless.setVisibility(View.VISIBLE); updateChip(device); ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class); @@ -284,13 +249,13 @@ public class QSMediaPlayer { }); // Artist name - TextView artistText = mMediaNotifView.findViewById(R.id.header_title); + TextView artistText = headerView.findViewById(R.id.header_artist); String artistName = mMediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST); artistText.setText(artistName); artistText.setTextColor(iconColor); // Song name - TextView titleText = mMediaNotifView.findViewById(R.id.header_text); + TextView titleText = headerView.findViewById(R.id.header_text); String songName = mMediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE); titleText.setText(songName); titleText.setTextColor(iconColor); @@ -363,34 +328,25 @@ public class QSMediaPlayer { return (state.getState() == PlaybackState.STATE_PLAYING); } - private void addAlbumArtBackground(MediaMetadata metadata, int bgColor, int width, int height) { + private void addAlbumArt(MediaMetadata metadata, int bgColor) { Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART); float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius); + ImageView albumView = mMediaNotifView.findViewById(R.id.album_art); if (albumArt != null) { Log.d(TAG, "updating album art"); Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true); - Bitmap scaled = scaleBitmap(original, width, height); - Canvas canvas = new Canvas(scaled); - - // Add translucent layer over album art to improve contrast - Paint p = new Paint(); - p.setStyle(Paint.Style.FILL); - p.setColor(bgColor); - p.setAlpha(200); - canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), p); - + int albumSize = (int) mContext.getResources().getDimension(R.dimen.qs_media_album_size); + Bitmap scaled = scaleBitmap(original, albumSize, albumSize); RoundedBitmapDrawable roundedDrawable = RoundedBitmapDrawableFactory.create( mContext.getResources(), scaled); roundedDrawable.setCornerRadius(radius); - - mMediaNotifView.setBackground(roundedDrawable); + albumView.setImageDrawable(roundedDrawable); } else { Log.e(TAG, "No album art available"); - GradientDrawable rect = new GradientDrawable(); - rect.setCornerRadius(radius); - rect.setColor(bgColor); - mMediaNotifView.setBackground(rect); + albumView.setImageDrawable(null); } + + mMediaNotifView.setBackgroundTintList(ColorStateList.valueOf(bgColor)); } private Bitmap scaleBitmap(Bitmap original, int width, int height) { @@ -423,8 +379,8 @@ public class QSMediaPlayer { rect.setStroke(2, mForegroundColor); rect.setColor(mBackgroundColor); - ImageView iconView = mSeamless.findViewById(com.android.internal.R.id.media_seamless_image); - TextView deviceName = mSeamless.findViewById(com.android.internal.R.id.media_seamless_text); + ImageView iconView = mSeamless.findViewById(R.id.media_seamless_image); + TextView deviceName = mSeamless.findViewById(R.id.media_seamless_text); deviceName.setTextColor(fgTintList); if (device != null) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 4fcd9b7dd941..7f141a7d5568 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -31,7 +31,6 @@ import android.metrics.LogMaker; import android.os.Bundle; import android.os.Handler; import android.os.Message; -import android.provider.Settings; import android.service.notification.StatusBarNotification; import android.service.quicksettings.Tile; import android.util.AttributeSet; @@ -62,7 +61,6 @@ import com.android.systemui.qs.external.CustomTile; import com.android.systemui.settings.BrightnessController; import com.android.systemui.settings.ToggleSliderView; import com.android.systemui.shared.plugins.PluginManager; -import com.android.systemui.statusbar.phone.NPVPluginManager; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener; import com.android.systemui.tuner.TunerService; @@ -97,6 +95,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne private final ArrayList<QSMediaPlayer> mMediaPlayers = new ArrayList<>(); private LocalMediaManager mLocalMediaManager; private MediaDevice mDevice; + private boolean mUpdateCarousel = false; protected boolean mExpanded; protected boolean mListening; @@ -120,7 +119,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne private FrameLayout mPluginFrame; private final PluginManager mPluginManager; - private NPVPluginManager mNPVPluginManager; private final LocalMediaManager.DeviceCallback mDeviceCallback = new LocalMediaManager.DeviceCallback() { @@ -187,7 +185,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne HorizontalScrollView mediaScrollView = (HorizontalScrollView) LayoutInflater.from( mContext).inflate(R.layout.media_carousel, this, false); mMediaCarousel = mediaScrollView.findViewById(R.id.media_carousel); - addView(mediaScrollView); + addView(mediaScrollView, 0); } else { mMediaCarousel = null; } @@ -202,14 +200,23 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne updateResources(); mPluginManager = pluginManager; - if (mPluginManager != null && Settings.System.getInt( - mContext.getContentResolver(), "npv_plugin_flag", 0) == 2) { - mPluginFrame = (FrameLayout) LayoutInflater.from(mContext).inflate( - R.layout.status_bar_expanded_plugin_frame, this, false); - addView(mPluginFrame); - mNPVPluginManager = new NPVPluginManager(mPluginFrame, mPluginManager); - } + } + @Override + public void onVisibilityAggregated(boolean isVisible) { + super.onVisibilityAggregated(isVisible); + if (!isVisible && mUpdateCarousel) { + for (QSMediaPlayer player : mMediaPlayers) { + if (player.isPlaying()) { + LayoutParams lp = (LayoutParams) player.getView().getLayoutParams(); + mMediaCarousel.removeView(player.getView()); + mMediaCarousel.addView(player.getView(), 0, lp); + ((HorizontalScrollView) mMediaCarousel.getParent()).fullScroll(View.FOCUS_LEFT); + mUpdateCarousel = false; + break; + } + } + } } /** @@ -249,7 +256,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne } } - int playerHeight = (int) getResources().getDimension(R.dimen.qs_media_height); int playerWidth = (int) getResources().getDimension(R.dimen.qs_media_width); int padding = (int) getResources().getDimension(R.dimen.qs_media_padding); LayoutParams lp = new LayoutParams(playerWidth, ViewGroup.LayoutParams.MATCH_PARENT); @@ -258,8 +264,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne if (player == null) { Log.d(TAG, "creating new player"); - - player = new QSMediaPlayer(mContext, this, playerWidth, playerHeight); + player = new QSMediaPlayer(mContext, this); if (player.isPlaying()) { mMediaCarousel.addView(player.getView(), 0, lp); // add in front @@ -268,9 +273,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne } mMediaPlayers.add(player); } else if (player.isPlaying()) { - // move it to the front - mMediaCarousel.removeView(player.getView()); - mMediaCarousel.addView(player.getView(), 0, lp); + mUpdateCarousel = true; } Log.d(TAG, "setting player session"); @@ -560,7 +563,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne if (mListening) { refreshAllTiles(); } - if (mNPVPluginManager != null) mNPVPluginManager.setListening(listening); } public void setListening(boolean listening, boolean expanded) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java index b682cb09b598..476af20b78f4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java @@ -31,7 +31,6 @@ import android.text.SpannableStringBuilder; import android.text.method.LinkMovementMethod; import android.text.style.ClickableSpan; import android.util.Log; -import android.util.StatsLog; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; @@ -41,6 +40,7 @@ import android.view.Window; import android.widget.ImageView; import android.widget.TextView; +import com.android.internal.util.FrameworkStatsLog; import com.android.systemui.Dependency; import com.android.systemui.FontSizeUtils; import com.android.systemui.R; @@ -116,13 +116,14 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic @Override public void onClick(View v) { + if (!hasFooter()) return; mHandler.sendEmptyMessage(H.CLICK); } private void handleClick() { showDeviceMonitoringDialog(); DevicePolicyEventLogger - .createEvent(StatsLog.DEVICE_POLICY_EVENT__EVENT_ID__DO_USER_INFO_CLICKED) + .createEvent(FrameworkStatsLog.DEVICE_POLICY_EVENT__EVENT_ID__DO_USER_INFO_CLICKED) .write(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java index cec1cb2fb53b..9018a375c365 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java @@ -23,13 +23,7 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.ColorStateList; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.Icon; import android.media.MediaMetadata; import android.media.session.MediaController; @@ -45,9 +39,6 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; -import androidx.core.graphics.drawable.RoundedBitmapDrawable; -import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory; - import com.android.systemui.R; import java.util.List; @@ -63,7 +54,6 @@ public class QuickQSMediaPlayer { private LinearLayout mMediaNotifView; private MediaSession.Token mToken; private MediaController mController; - private int mBackgroundColor; private int mForegroundColor; private ComponentName mRecvComponent; @@ -142,7 +132,6 @@ public class QuickQSMediaPlayer { View actionsContainer, int[] actionsToShow, PendingIntent contentIntent) { mToken = token; mForegroundColor = iconColor; - mBackgroundColor = bgColor; String oldPackage = ""; if (mController != null) { @@ -185,8 +174,7 @@ public class QuickQSMediaPlayer { } }); - // Album art - addAlbumArtBackground(mMediaMetadata, mBackgroundColor); + mMediaNotifView.setBackgroundTintList(ColorStateList.valueOf(bgColor)); // App icon ImageView appIcon = mMediaNotifView.findViewById(R.id.icon); @@ -194,14 +182,8 @@ public class QuickQSMediaPlayer { iconDrawable.setTint(mForegroundColor); appIcon.setImageDrawable(iconDrawable); - // Artist name - TextView appText = mMediaNotifView.findViewById(R.id.header_title); - String artistName = mMediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST); - appText.setText(artistName); - appText.setTextColor(mForegroundColor); - // Song name - TextView titleText = mMediaNotifView.findViewById(R.id.header_text); + TextView titleText = mMediaNotifView.findViewById(R.id.header_title); String songName = mMediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE); titleText.setText(songName); titleText.setTextColor(mForegroundColor); @@ -277,54 +259,4 @@ public class QuickQSMediaPlayer { public boolean hasMediaSession() { return mController != null && mController.getPlaybackState() != null; } - - private void addAlbumArtBackground(MediaMetadata metadata, int bgColor) { - Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART); - float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius); - Rect bounds = new Rect(); - mMediaNotifView.getBoundsOnScreen(bounds); - int width = bounds.width(); - int height = bounds.height(); - if (albumArt != null && width > 0 && height > 0) { - Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true); - Bitmap scaled = scaleBitmap(original, width, height); - Canvas canvas = new Canvas(scaled); - - // Add translucent layer over album art to improve contrast - Paint p = new Paint(); - p.setStyle(Paint.Style.FILL); - p.setColor(bgColor); - p.setAlpha(200); - canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), p); - - RoundedBitmapDrawable roundedDrawable = RoundedBitmapDrawableFactory.create( - mContext.getResources(), scaled); - roundedDrawable.setCornerRadius(radius); - - mMediaNotifView.setBackground(roundedDrawable); - } else { - Log.e(TAG, "No album art available"); - GradientDrawable rect = new GradientDrawable(); - rect.setCornerRadius(radius); - rect.setColor(bgColor); - mMediaNotifView.setBackground(rect); - } - } - - private Bitmap scaleBitmap(Bitmap original, int width, int height) { - Bitmap cropped = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(cropped); - - float scale = (float) cropped.getWidth() / (float) original.getWidth(); - float dy = (cropped.getHeight() - original.getHeight() * scale) / 2.0f; - Matrix transformation = new Matrix(); - transformation.postTranslate(0, dy); - transformation.preScale(scale, scale); - - Paint paint = new Paint(); - paint.setFilterBitmap(true); - canvas.drawBitmap(original, transformation, paint); - - return cropped; - } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java index b05d4fdf7db7..e9207016eb68 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java @@ -85,20 +85,21 @@ public class QuickQSPanel extends QSPanel { mHorizontalLinearLayout.setClipChildren(false); mHorizontalLinearLayout.setClipToPadding(false); + int marginSize = (int) mContext.getResources().getDimension(R.dimen.qqs_media_spacing); + mMediaPlayer = new QuickQSMediaPlayer(mContext, mHorizontalLinearLayout); + LayoutParams lp2 = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1); + lp2.setMarginEnd(marginSize); + lp2.setMarginStart(0); + mHorizontalLinearLayout.addView(mMediaPlayer.getView(), lp2); + mTileLayout = new DoubleLineTileLayout(context); mMediaTileLayout = mTileLayout; mRegularTileLayout = new HeaderTileLayout(context); LayoutParams lp = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1); - lp.setMarginEnd(10); - lp.setMarginStart(0); + lp.setMarginEnd(0); + lp.setMarginStart(marginSize); mHorizontalLinearLayout.addView((View) mTileLayout, lp); - mMediaPlayer = new QuickQSMediaPlayer(mContext, mHorizontalLinearLayout); - LayoutParams lp2 = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1); - lp2.setMarginEnd(0); - lp2.setMarginStart(25); - mHorizontalLinearLayout.addView(mMediaPlayer.getView(), lp2); - sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns); mTileLayout.setListening(mListening); diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java index 37743ec55517..9f59277c918a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java @@ -1,5 +1,7 @@ package com.android.systemui.qs; +import static com.android.systemui.util.Utils.useQsMediaPlayer; + import android.content.Context; import android.content.res.Resources; import android.provider.Settings; @@ -42,7 +44,8 @@ public class TileLayout extends ViewGroup implements QSTileLayout { public TileLayout(Context context, AttributeSet attrs) { super(context, attrs); setFocusableInTouchMode(true); - mLessRows = Settings.System.getInt(context.getContentResolver(), "qs_less_rows", 0) != 0; + mLessRows = (Settings.System.getInt(context.getContentResolver(), "qs_less_rows", 0) != 0) + || useQsMediaPlayer(context); updateResources(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index 2b53727f237e..554672d88052 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -16,7 +16,6 @@ package com.android.systemui.qs.tileimpl; import android.content.Context; import android.os.Build; -import android.provider.Settings; import android.util.Log; import android.view.ContextThemeWrapper; @@ -33,7 +32,6 @@ import com.android.systemui.qs.tiles.BluetoothTile; import com.android.systemui.qs.tiles.CastTile; import com.android.systemui.qs.tiles.CellularTile; import com.android.systemui.qs.tiles.ColorInversionTile; -import com.android.systemui.qs.tiles.ControlsTile; import com.android.systemui.qs.tiles.DataSaverTile; import com.android.systemui.qs.tiles.DndTile; import com.android.systemui.qs.tiles.FlashlightTile; @@ -60,7 +58,6 @@ public class QSFactoryImpl implements QSFactory { private final Provider<WifiTile> mWifiTileProvider; private final Provider<BluetoothTile> mBluetoothTileProvider; - private final Provider<ControlsTile> mControlsTileProvider; private final Provider<CellularTile> mCellularTileProvider; private final Provider<DndTile> mDndTileProvider; private final Provider<ColorInversionTile> mColorInversionTileProvider; @@ -85,7 +82,6 @@ public class QSFactoryImpl implements QSFactory { @Inject public QSFactoryImpl(Provider<WifiTile> wifiTileProvider, Provider<BluetoothTile> bluetoothTileProvider, - Provider<ControlsTile> controlsTileProvider, Provider<CellularTile> cellularTileProvider, Provider<DndTile> dndTileProvider, Provider<ColorInversionTile> colorInversionTileProvider, @@ -106,7 +102,6 @@ public class QSFactoryImpl implements QSFactory { Provider<ScreenRecordTile> screenRecordTileProvider) { mWifiTileProvider = wifiTileProvider; mBluetoothTileProvider = bluetoothTileProvider; - mControlsTileProvider = controlsTileProvider; mCellularTileProvider = cellularTileProvider; mDndTileProvider = dndTileProvider; mColorInversionTileProvider = colorInversionTileProvider; @@ -146,11 +141,6 @@ public class QSFactoryImpl implements QSFactory { return mWifiTileProvider.get(); case "bt": return mBluetoothTileProvider.get(); - case "controls": - if (Settings.System.getInt(mHost.getContext().getContentResolver(), - "npv_plugin_flag", 0) == 3) { - return mControlsTileProvider.get(); - } else return null; case "cell": return mCellularTileProvider.get(); case "dnd": diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java index d4e9fdff1b24..781fdf076db8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -194,8 +194,8 @@ public class CastTile extends QSTileImpl<BooleanState> { if (device.state == CastDevice.STATE_CONNECTED) { state.value = true; state.secondaryLabel = getDeviceName(device); - state.contentDescription = state.contentDescription + "," + - mContext.getString(R.string.accessibility_cast_name, state.label); + state.contentDescription = state.contentDescription + "," + + mContext.getString(R.string.accessibility_cast_name, state.label); connecting = false; break; } else if (device.state == CastDevice.STATE_CONNECTING) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ControlsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ControlsTile.java deleted file mode 100644 index 39ae66e7607a..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ControlsTile.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.qs.tiles; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; - -import com.android.systemui.R; -import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.plugins.NPVPlugin; -import com.android.systemui.plugins.PluginListener; -import com.android.systemui.plugins.qs.DetailAdapter; -import com.android.systemui.plugins.qs.QSTile.BooleanState; -import com.android.systemui.qs.QSHost; -import com.android.systemui.qs.tileimpl.QSTileImpl; -import com.android.systemui.shared.plugins.PluginManager; - -import javax.inject.Inject; - - -/** - * Temporary control test for prototyping - */ -public class ControlsTile extends QSTileImpl<BooleanState> { - private ControlsDetailAdapter mDetailAdapter; - private final ActivityStarter mActivityStarter; - private PluginManager mPluginManager; - private NPVPlugin mPlugin; - private Intent mHomeAppIntent; - - @Inject - public ControlsTile(QSHost host, - ActivityStarter activityStarter, - PluginManager pluginManager) { - super(host); - mActivityStarter = activityStarter; - mPluginManager = pluginManager; - mDetailAdapter = (ControlsDetailAdapter) createDetailAdapter(); - - mHomeAppIntent = new Intent(Intent.ACTION_VIEW); - mHomeAppIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mHomeAppIntent.setComponent(new ComponentName("com.google.android.apps.chromecast.app", - "com.google.android.apps.chromecast.app.DiscoveryActivity")); - } - - @Override - public DetailAdapter getDetailAdapter() { - return mDetailAdapter; - } - - @Override - public BooleanState newTileState() { - return new BooleanState(); - } - - @Override - public void handleSetListening(boolean listening) { - - } - - @Override - public void setDetailListening(boolean listening) { - if (mPlugin == null) return; - - mPlugin.setListening(listening); - } - - @Override - protected void handleClick() { - showDetail(true); - } - - @Override - public Intent getLongClickIntent() { - return mHomeAppIntent; - } - - @Override - protected void handleSecondaryClick() { - showDetail(true); - } - - @Override - public CharSequence getTileLabel() { - return "Controls"; - } - - @Override - protected void handleUpdateState(BooleanState state, Object arg) { - state.icon = ResourceIcon.get(R.drawable.ic_lightbulb_outline_gm2_24px); - state.label = "Controls"; - } - - @Override - public boolean supportsDetailView() { - return getDetailAdapter() != null && mQSSettingsPanelOption == QSSettingsPanel.OPEN_CLICK; - } - - @Override - public int getMetricsCategory() { - return -1; - } - - @Override - protected String composeChangeAnnouncement() { - if (mState.value) { - return "On"; - } else { - return "Off"; - } - } - - @Override - public boolean isAvailable() { - return true; - } - - @Override - protected DetailAdapter createDetailAdapter() { - mDetailAdapter = new ControlsDetailAdapter(); - return mDetailAdapter; - } - - private class ControlsDetailAdapter implements DetailAdapter { - private View mDetailView; - protected FrameLayout mHomeControlsLayout; - - public CharSequence getTitle() { - return "Controls"; - } - - public Boolean getToggleState() { - return null; - } - - public boolean getToggleEnabled() { - return false; - } - - public View createDetailView(Context context, View convertView, final ViewGroup parent) { - if (convertView != null) return convertView; - - mHomeControlsLayout = (FrameLayout) LayoutInflater.from(context).inflate( - R.layout.home_controls, parent, false); - mHomeControlsLayout.setVisibility(View.VISIBLE); - parent.addView(mHomeControlsLayout); - - mPluginManager.addPluginListener( - new PluginListener<NPVPlugin>() { - @Override - public void onPluginConnected(NPVPlugin plugin, - Context pluginContext) { - mPlugin = plugin; - mPlugin.attachToRoot(mHomeControlsLayout); - mPlugin.setListening(true); - } - - @Override - public void onPluginDisconnected(NPVPlugin plugin) { - mPlugin.setListening(false); - mHomeControlsLayout.removeAllViews(); - - } - }, NPVPlugin.class, false); - return mHomeControlsLayout; - } - - public Intent getSettingsIntent() { - return mHomeAppIntent; - } - - public void setToggleState(boolean state) { - - } - - public int getMetricsCategory() { - return -1; - } - - public boolean hasHeader() { - return false; - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 569f660d1797..573ea4dd85de 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -29,6 +29,7 @@ import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WIN import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED; import android.annotation.FloatRange; import android.app.ActivityTaskManager; @@ -38,6 +39,8 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; +import android.graphics.Bitmap; +import android.graphics.Insets; import android.graphics.Rect; import android.graphics.Region; import android.hardware.input.InputManager; @@ -55,6 +58,7 @@ import android.view.MotionEvent; import android.view.accessibility.AccessibilityManager; import com.android.internal.policy.ScreenDecorationsUtils; +import com.android.internal.util.ScreenshotHelper; import com.android.systemui.Dumpable; import com.android.systemui.model.SysUiState; import com.android.systemui.pip.PipUI; @@ -64,6 +68,7 @@ import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.stackdivider.Divider; +import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NavigationBarController; import com.android.systemui.statusbar.phone.NavigationBarFragment; import com.android.systemui.statusbar.phone.NavigationBarView; @@ -115,6 +120,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private final DeviceProvisionedController mDeviceProvisionedController; private final List<OverviewProxyListener> mConnectionCallbacks = new ArrayList<>(); private final Intent mQuickStepIntent; + private final ScreenshotHelper mScreenshotHelper; private Region mActiveNavBarRegion; @@ -365,6 +371,13 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } + @Override + public void handleImageAsScreenshot(Bitmap screenImage, Rect locationInScreen, + Insets visibleInsets, int taskId) { + mScreenshotHelper.provideScreenshot(screenImage, locationInScreen, visibleInsets, + taskId, mHandler, null); + } + private boolean verifyCaller(String reason) { final int callerId = Binder.getCallingUserHandle().getIdentifier(); if (callerId != mCurrentBoundedUserId) { @@ -474,7 +487,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis @SuppressWarnings("OptionalUsedAsFieldOrParameterType") @Inject - public OverviewProxyService(Context context, DeviceProvisionedController provisionController, + public OverviewProxyService(Context context, CommandQueue commandQueue, + DeviceProvisionedController provisionController, NavigationBarController navBarController, NavigationModeController navModeController, NotificationShadeWindowController statusBarWinController, SysUiState sysUiState, PipUI pipUI, Optional<Divider> dividerOptional, @@ -518,6 +532,16 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis // Listen for status bar state changes statusBarWinController.registerCallback(mStatusBarWindowCallback); + mScreenshotHelper = new ScreenshotHelper(context); + + // Listen for tracing state changes + commandQueue.addCallback(new CommandQueue.Callbacks() { + @Override + public void onTracingStateChanged(boolean enabled) { + mSysUiState.setFlag(SYSUI_STATE_TRACING_ENABLED, enabled) + .commitUpdate(mContext.getDisplayId()); + } + }); } public void notifyBackAction(boolean completed, int downX, int downY, boolean isButton, @@ -561,14 +585,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private void onStatusBarStateChanged(boolean keyguardShowing, boolean keyguardOccluded, boolean bouncerShowing) { - int displayId = mContext.getDisplayId(); - mSysUiState.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING, keyguardShowing && !keyguardOccluded) .setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED, keyguardShowing && keyguardOccluded) .setFlag(SYSUI_STATE_BOUNCER_SHOWING, bouncerShowing) - .commitUpdate(displayId); + .commitUpdate(mContext.getDisplayId()); } /** @@ -589,10 +611,6 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } - public float getBackButtonAlpha() { - return mNavBarButtonAlpha; - } - public void cleanupAfterDeath() { if (mInputFocusTransferStarted) { mHandler.post(()-> { diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java index 1d649eee4d57..fe84d81836e8 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java +++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java @@ -138,7 +138,7 @@ public class ScreenPinningRequest implements View.OnClickListener, lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; lp.setTitle("ScreenPinningConfirmation"); lp.gravity = Gravity.FILL; - lp.setFitWindowInsetsTypes(0 /* types */); + lp.setFitInsetsTypes(0 /* types */); return lp; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/TriangleShape.java b/packages/SystemUI/src/com/android/systemui/recents/TriangleShape.java index de8e6ea4a0cb..7ebebaaef122 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/TriangleShape.java +++ b/packages/SystemUI/src/com/android/systemui/recents/TriangleShape.java @@ -70,6 +70,6 @@ public class TriangleShape extends PathShape { @Override public void getOutline(@NonNull Outline outline) { - outline.setConvexPath(mTriangularPath); + outline.setPath(mTriangularPath); } } diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java index 1b321685e88b..b091ad8c0db8 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java @@ -34,6 +34,7 @@ import android.media.MediaRecorder; import android.media.projection.MediaProjection; import android.media.projection.MediaProjectionManager; import android.net.Uri; +import android.os.Bundle; import android.os.IBinder; import android.provider.MediaStore; import android.provider.Settings; @@ -72,9 +73,6 @@ public class RecordingService extends Service { private static final String ACTION_START = "com.android.systemui.screenrecord.START"; private static final String ACTION_STOP = "com.android.systemui.screenrecord.STOP"; - private static final String ACTION_PAUSE = "com.android.systemui.screenrecord.PAUSE"; - private static final String ACTION_RESUME = "com.android.systemui.screenrecord.RESUME"; - private static final String ACTION_CANCEL = "com.android.systemui.screenrecord.CANCEL"; private static final String ACTION_SHARE = "com.android.systemui.screenrecord.SHARE"; private static final String ACTION_DELETE = "com.android.systemui.screenrecord.DELETE"; @@ -94,6 +92,7 @@ public class RecordingService extends Service { private boolean mUseAudio; private boolean mShowTaps; + private boolean mOriginalShowTaps; private File mTempFile; @Inject @@ -145,38 +144,11 @@ public class RecordingService extends Service { } break; - case ACTION_CANCEL: - stopRecording(); - - // Delete temp file - if (!mTempFile.delete()) { - Log.e(TAG, "Error canceling screen recording!"); - Toast.makeText(this, R.string.screenrecord_delete_error, Toast.LENGTH_LONG) - .show(); - } else { - Toast.makeText(this, R.string.screenrecord_cancel_success, Toast.LENGTH_LONG) - .show(); - } - - // Close quick shade - sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); - break; - case ACTION_STOP: stopRecording(); saveRecording(notificationManager); break; - case ACTION_PAUSE: - mMediaRecorder.pause(); - setNotificationActions(true, notificationManager); - break; - - case ACTION_RESUME: - mMediaRecorder.resume(); - setNotificationActions(false, notificationManager); - break; - case ACTION_SHARE: Uri shareUri = Uri.parse(intent.getStringExtra(EXTRA_PATH)); @@ -233,9 +205,14 @@ public class RecordingService extends Service { */ private void startRecording() { try { - mTempFile = File.createTempFile("temp", ".mp4"); + File cacheDir = getCacheDir(); + cacheDir.mkdirs(); + mTempFile = File.createTempFile("temp", ".mp4", cacheDir); Log.d(TAG, "Writing video output to: " + mTempFile.getAbsolutePath()); + mOriginalShowTaps = 1 == Settings.System.getInt( + getApplicationContext().getContentResolver(), + Settings.System.SHOW_TOUCHES, 0); setTapsVisible(mShowTaps); // Set up media recorder @@ -295,50 +272,33 @@ public class RecordingService extends Service { NotificationChannel channel = new NotificationChannel( CHANNEL_ID, getString(R.string.screenrecord_name), - NotificationManager.IMPORTANCE_LOW); + NotificationManager.IMPORTANCE_DEFAULT); channel.setDescription(getString(R.string.screenrecord_channel_description)); channel.enableVibration(true); NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.createNotificationChannel(channel); + Bundle extras = new Bundle(); + extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, + getResources().getString(R.string.screenrecord_name)); + mRecordingNotificationBuilder = new Notification.Builder(this, CHANNEL_ID) - .setSmallIcon(R.drawable.ic_android) + .setSmallIcon(R.drawable.ic_screenrecord) .setContentTitle(getResources().getString(R.string.screenrecord_name)) + .setContentText(getResources().getString(R.string.screenrecord_stop_text)) .setUsesChronometer(true) - .setOngoing(true); - setNotificationActions(false, notificationManager); - Notification notification = mRecordingNotificationBuilder.build(); - startForeground(NOTIFICATION_ID, notification); - } - - private void setNotificationActions(boolean isPaused, NotificationManager notificationManager) { - String pauseString = getResources() - .getString(isPaused ? R.string.screenrecord_resume_label - : R.string.screenrecord_pause_label); - Intent pauseIntent = isPaused ? getResumeIntent(this) : getPauseIntent(this); - - mRecordingNotificationBuilder.setActions( - new Notification.Action.Builder( - Icon.createWithResource(this, R.drawable.ic_android), - getResources().getString(R.string.screenrecord_stop_label), - PendingIntent - .getService(this, REQUEST_CODE, getStopIntent(this), - PendingIntent.FLAG_UPDATE_CURRENT)) - .build(), - new Notification.Action.Builder( - Icon.createWithResource(this, R.drawable.ic_android), pauseString, - PendingIntent.getService(this, REQUEST_CODE, pauseIntent, + .setColorized(true) + .setColor(getResources().getColor(R.color.GM2_red_700)) + .setOngoing(true) + .setContentIntent( + PendingIntent.getService( + this, REQUEST_CODE, getStopIntent(this), PendingIntent.FLAG_UPDATE_CURRENT)) - .build(), - new Notification.Action.Builder( - Icon.createWithResource(this, R.drawable.ic_android), - getResources().getString(R.string.screenrecord_cancel_label), - PendingIntent - .getService(this, REQUEST_CODE, getCancelIntent(this), - PendingIntent.FLAG_UPDATE_CURRENT)) - .build()); + .addExtras(extras); notificationManager.notify(NOTIFICATION_ID, mRecordingNotificationBuilder.build()); + Notification notification = mRecordingNotificationBuilder.build(); + startForeground(NOTIFICATION_ID, notification); } private Notification createSaveNotification(Uri uri) { @@ -347,7 +307,7 @@ public class RecordingService extends Service { .setDataAndType(uri, "video/mp4"); Notification.Action shareAction = new Notification.Action.Builder( - Icon.createWithResource(this, R.drawable.ic_android), + Icon.createWithResource(this, R.drawable.ic_screenrecord), getResources().getString(R.string.screenrecord_share_label), PendingIntent.getService( this, @@ -357,7 +317,7 @@ public class RecordingService extends Service { .build(); Notification.Action deleteAction = new Notification.Action.Builder( - Icon.createWithResource(this, R.drawable.ic_android), + Icon.createWithResource(this, R.drawable.ic_screenrecord), getResources().getString(R.string.screenrecord_delete_label), PendingIntent.getService( this, @@ -366,8 +326,12 @@ public class RecordingService extends Service { PendingIntent.FLAG_UPDATE_CURRENT)) .build(); + Bundle extras = new Bundle(); + extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, + getResources().getString(R.string.screenrecord_name)); + Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID) - .setSmallIcon(R.drawable.ic_android) + .setSmallIcon(R.drawable.ic_screenrecord) .setContentTitle(getResources().getString(R.string.screenrecord_name)) .setContentText(getResources().getString(R.string.screenrecord_save_message)) .setContentIntent(PendingIntent.getActivity( @@ -377,7 +341,8 @@ public class RecordingService extends Service { Intent.FLAG_GRANT_READ_URI_PERMISSION)) .addAction(shareAction) .addAction(deleteAction) - .setAutoCancel(true); + .setAutoCancel(true) + .addExtras(extras); // Add thumbnail if available Bitmap thumbnailBitmap = null; @@ -400,7 +365,7 @@ public class RecordingService extends Service { } private void stopRecording() { - setTapsVisible(false); + setTapsVisible(mOriginalShowTaps); mMediaRecorder.stop(); mMediaRecorder.release(); mMediaRecorder = null; @@ -459,18 +424,6 @@ public class RecordingService extends Service { return new Intent(context, RecordingService.class).setAction(ACTION_STOP); } - private static Intent getPauseIntent(Context context) { - return new Intent(context, RecordingService.class).setAction(ACTION_PAUSE); - } - - private static Intent getResumeIntent(Context context) { - return new Intent(context, RecordingService.class).setAction(ACTION_RESUME); - } - - private static Intent getCancelIntent(Context context) { - return new Intent(context, RecordingService.class).setAction(ACTION_CANCEL); - } - private static Intent getShareIntent(Context context, String path) { return new Intent(context, RecordingService.class).setAction(ACTION_SHARE) .putExtra(EXTRA_PATH, path); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 50e9a51478ed..880b8f8776e8 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -29,6 +29,7 @@ import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.Notification; @@ -38,6 +39,7 @@ import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.Insets; import android.graphics.Outline; import android.graphics.PixelFormat; import android.graphics.PointF; @@ -240,7 +242,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset PixelFormat.TRANSLUCENT); mWindowLayoutParams.setTitle("ScreenshotAnimation"); mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - mWindowLayoutParams.setFitWindowInsetsTypes(0 /* types */); + mWindowLayoutParams.setFitInsetsTypes(0 /* types */); mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); mDisplay = mWindowManager.getDefaultDisplay(); mDisplayMetrics = new DisplayMetrics(); @@ -300,8 +302,11 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset int width = crop.width(); int height = crop.height(); - // Take the screenshot - mScreenBitmap = SurfaceControl.screenshot(crop, width, height, rot); + takeScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, null); + } + + private void takeScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect) { + mScreenBitmap = screenshot; if (mScreenBitmap == null) { mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_capture_text); @@ -317,7 +322,8 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this); // Start the post-screenshot animation - startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels); + startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels, + screenRect); } void takeScreenshot(Consumer<Uri> finisher) { @@ -327,9 +333,16 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)); } + void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds, + Insets visibleInsets, int taskId, Consumer<Uri> finisher) { + // TODO use taskId and visibleInsets + takeScreenshot(screenshot, finisher, screenshotScreenBounds); + } + /** * Displays a screenshot selector */ + @SuppressLint("ClickableViewAccessibility") void takeScreenshotPartial(final Consumer<Uri> finisher) { mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() { @@ -402,7 +415,8 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset /** * Starts the animation after taking the screenshot */ - private void startAnimation(final Consumer<Uri> finisher, int w, int h) { + private void startAnimation(final Consumer<Uri> finisher, int w, int h, + @Nullable Rect screenRect) { // If power save is on, show a toast so there is some visual indication that a screenshot // has been taken. PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); @@ -422,7 +436,8 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mScreenshotAnimation.removeAllListeners(); } - ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation(); + ValueAnimator screenshotDropInAnim = screenRect != null ? createRectAnimation(screenRect) + : createScreenshotDropInAnimation(); ValueAnimator screenshotFadeOutAnim = createScreenshotToCornerAnimation(w, h); mScreenshotAnimation = new AnimatorSet(); mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim); @@ -460,6 +475,46 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset }); } + private ValueAnimator createRectAnimation(Rect rect) { + mScreenshotView.setAdjustViewBounds(true); + mScreenshotView.setMaxHeight(rect.height()); + mScreenshotView.setMaxWidth(rect.width()); + + final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION) + / SCREENSHOT_DROP_IN_DURATION); + final float flashDurationPct = 2f * flashPeakDurationPct; + final Interpolator scaleInterpolator = x -> { + // We start scaling when the flash is at it's peak + if (x < flashPeakDurationPct) { + return 0; + } + return (x - flashDurationPct) / (1f - flashDurationPct); + }; + + ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); + anim.setDuration(SCREENSHOT_DROP_IN_DURATION); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mBackgroundView.setAlpha(0f); + mBackgroundView.setVisibility(View.VISIBLE); + mScreenshotView.setAlpha(0f); + mScreenshotView.setElevation(0f); + mScreenshotView.setTranslationX(0f); + mScreenshotView.setTranslationY(0f); + mScreenshotView.setScaleX(1f); + mScreenshotView.setScaleY(1f); + mScreenshotView.setVisibility(View.VISIBLE); + } + }); + anim.addUpdateListener(animation -> { + float t = (Float) animation.getAnimatedValue(); + mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA); + mScreenshotView.setAlpha(t); + }); + return anim; + } + private ValueAnimator createScreenshotDropInAnimation() { final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION) / SCREENSHOT_DROP_IN_DURATION); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java index 16447fbdd4aa..f3614ffbdb1b 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java @@ -30,6 +30,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Color; +import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.PointF; import android.graphics.Rect; @@ -156,7 +157,7 @@ public class GlobalScreenshotLegacy { PixelFormat.TRANSLUCENT); mWindowLayoutParams.setTitle("ScreenshotAnimation"); mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - mWindowLayoutParams.setFitWindowInsetsTypes(0 /* types */); + mWindowLayoutParams.setFitInsetsTypes(0 /* types */); mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); mDisplay = mWindowManager.getDefaultDisplay(); mDisplayMetrics = new DisplayMetrics(); @@ -205,8 +206,13 @@ public class GlobalScreenshotLegacy { int width = crop.width(); int height = crop.height(); - // Take the screenshot - mScreenBitmap = SurfaceControl.screenshot(crop, width, height, rot); + takeScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, + statusBarVisible, navBarVisible, null); + } + + private void takeScreenshot(Bitmap screenshot, Consumer<Uri> finisher, boolean statusBarVisible, + boolean navBarVisible, Rect screenboundsOfBitmap) { + mScreenBitmap = screenshot; if (mScreenBitmap == null) { mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_capture_text); @@ -220,7 +226,7 @@ public class GlobalScreenshotLegacy { // Start the post-screenshot animation startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels, - statusBarVisible, navBarVisible); + statusBarVisible, navBarVisible, screenboundsOfBitmap); } void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible, boolean navBarVisible) { @@ -229,6 +235,12 @@ public class GlobalScreenshotLegacy { new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)); } + void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds, + Insets visibleInsets, int taskId, Consumer<Uri> finisher) { + // TODO use taskId and visibleInsets + takeScreenshot(screenshot, finisher, false, false, screenshotScreenBounds); + } + /** * Displays a screenshot selector */ @@ -302,7 +314,7 @@ public class GlobalScreenshotLegacy { * Starts the animation after taking the screenshot */ private void startAnimation(final Consumer<Uri> finisher, int w, int h, - boolean statusBarVisible, boolean navBarVisible) { + boolean statusBarVisible, boolean navBarVisible, @Nullable Rect screenBoundsOfBitmap) { // If power save is on, show a toast so there is some visual indication that a screenshot // has been taken. PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); @@ -323,7 +335,8 @@ public class GlobalScreenshotLegacy { } mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); - ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation(); + ValueAnimator screenshotDropInAnim = screenBoundsOfBitmap != null + ? createRectAnimation(screenBoundsOfBitmap) : createScreenshotDropInAnimation(); ValueAnimator screenshotFadeOutAnim = createScreenshotDropOutAnimation(w, h, statusBarVisible, navBarVisible); mScreenshotAnimation = new AnimatorSet(); @@ -430,6 +443,53 @@ public class GlobalScreenshotLegacy { return anim; } + /** + * If a bitmap was supplied to be used as the screenshot, animated from where that bitmap was + * on screen, rather than using the whole screen. + */ + private ValueAnimator createRectAnimation(Rect rect) { + mScreenshotView.setAdjustViewBounds(true); + mScreenshotView.setMaxHeight(rect.height()); + mScreenshotView.setMaxWidth(rect.width()); + + final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION) + / SCREENSHOT_DROP_IN_DURATION); + final float flashDurationPct = 2f * flashPeakDurationPct; + final Interpolator scaleInterpolator = x -> { + // We start scaling when the flash is at it's peak + if (x < flashPeakDurationPct) { + return 0; + } + return (x - flashDurationPct) / (1f - flashDurationPct); + }; + + ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); + anim.setDuration(SCREENSHOT_DROP_IN_DURATION); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mBackgroundView.setAlpha(0f); + mBackgroundView.setVisibility(View.VISIBLE); + mScreenshotView.setAlpha(0f); + mScreenshotView.setElevation(0f); + mScreenshotView.setTranslationX(0f); + mScreenshotView.setTranslationY(0f); + mScreenshotView.setScaleX(SCREENSHOT_SCALE + mBgPaddingScale); + mScreenshotView.setScaleY(SCREENSHOT_SCALE + mBgPaddingScale); + mScreenshotView.setVisibility(View.VISIBLE); + } + }); + anim.addUpdateListener(animation -> { + float t = (Float) animation.getAnimatedValue(); + float scaleT = (SCREENSHOT_SCALE + mBgPaddingScale) + - scaleInterpolator.getInterpolation(t) + * (SCREENSHOT_SCALE - SCREENSHOT_DROP_IN_MIN_SCALE); + mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA); + mScreenshotView.setAlpha(t); + }); + return anim; + } + private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible, boolean navBarVisible) { ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java index e6082dddd6c7..e7e1ba8c28b4 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java @@ -106,7 +106,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { // Initialize screenshot notification smart actions provider. mSmartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, false); + SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, true); if (mSmartActionsEnabled) { mSmartActionsProvider = SystemUIFactory.getInstance() diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 9570b5a3b57c..4ac59df07eb9 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -22,6 +22,9 @@ import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREEN import android.app.Service; import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Insets; +import android.graphics.Rect; import android.net.Uri; import android.os.Handler; import android.os.IBinder; @@ -85,6 +88,22 @@ public class TakeScreenshotService extends Service { finisher, msg.arg1 > 0, msg.arg2 > 0); } break; + case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE: + Bitmap screenshot = msg.getData().getParcelable( + WindowManager.PARCEL_KEY_SCREENSHOT_BITMAP); + Rect screenBounds = msg.getData().getParcelable( + WindowManager.PARCEL_KEY_SCREENSHOT_BOUNDS); + Insets insets = msg.getData().getParcelable( + WindowManager.PARCEL_KEY_SCREENSHOT_INSETS); + int taskId = msg.getData().getInt(WindowManager.PARCEL_KEY_SCREENSHOT_TASK_ID); + if (useCornerFlow) { + mScreenshot.handleImageAsScreenshot( + screenshot, screenBounds, insets, taskId, finisher); + } else { + mScreenshotLegacy.handleImageAsScreenshot( + screenshot, screenBounds, insets, taskId, finisher); + } + break; default: Log.d(TAG, "Invalid screenshot option: " + msg.what); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 7c0f4f942bce..64f083024ce1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -25,6 +25,8 @@ import static android.view.Display.INVALID_DISPLAY; import static com.android.systemui.statusbar.phone.StatusBar.ONLY_CORE_APPS; +import android.annotation.Nullable; +import android.app.ITransientNotificationCallback; import android.app.StatusBarManager; import android.app.StatusBarManager.Disable2Flags; import android.app.StatusBarManager.DisableFlags; @@ -51,6 +53,7 @@ import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.view.AppearanceRegion; import com.android.systemui.statusbar.CommandQueue.Callbacks; import com.android.systemui.statusbar.policy.CallbackController; +import com.android.systemui.tracing.ProtoTracer; import java.util.ArrayList; @@ -119,6 +122,9 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< private static final int MSG_TOP_APP_WINDOW_CHANGED = 50 << MSG_SHIFT; private static final int MSG_SHOW_INATTENTIVE_SLEEP_WARNING = 51 << MSG_SHIFT; private static final int MSG_DISMISS_INATTENTIVE_SLEEP_WARNING = 52 << MSG_SHIFT; + private static final int MSG_SHOW_TOAST = 53 << MSG_SHIFT; + private static final int MSG_HIDE_TOAST = 54 << MSG_SHIFT; + private static final int MSG_TRACING_STATE_CHANGED = 55 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; @@ -139,6 +145,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< * event. */ private int mLastUpdatedImeDisplayId = INVALID_DISPLAY; + private ProtoTracer mProtoTracer; /** * These methods are called back on the main thread. @@ -308,9 +315,32 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< * due to prolonged user inactivity should be dismissed. */ default void dismissInattentiveSleepWarning(boolean animated) { } + + /** + * @see IStatusBar#showToast(String, IBinder, CharSequence, IBinder, int, + * ITransientNotificationCallback) + */ + default void showToast(String packageName, IBinder token, CharSequence text, + IBinder windowToken, int duration, + @Nullable ITransientNotificationCallback callback) { } + + /** + * @see IStatusBar#hideToast(String, IBinder) (String, IBinder) + */ + default void hideToast(String packageName, IBinder token) { } + + /** + * @param enabled + */ + default void onTracingStateChanged(boolean enabled) { } } public CommandQueue(Context context) { + this(context, null); + } + + public CommandQueue(Context context, ProtoTracer protoTracer) { + mProtoTracer = protoTracer; context.getSystemService(DisplayManager.class).registerDisplayListener(this, mHandler); // We always have default display. setDisabled(DEFAULT_DISPLAY, DISABLE_NONE, DISABLE2_NONE); @@ -761,6 +791,31 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< } @Override + public void showToast(String packageName, IBinder token, CharSequence text, + IBinder windowToken, int duration, @Nullable ITransientNotificationCallback callback) { + synchronized (mLock) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = packageName; + args.arg2 = token; + args.arg3 = text; + args.arg4 = windowToken; + args.arg5 = callback; + args.argi1 = duration; + mHandler.obtainMessage(MSG_SHOW_TOAST, args).sendToTarget(); + } + } + + @Override + public void hideToast(String packageName, IBinder token) { + synchronized (mLock) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = packageName; + args.arg2 = token; + mHandler.obtainMessage(MSG_HIDE_TOAST, args).sendToTarget(); + } + } + + @Override public void onBiometricAuthenticated() { synchronized (mLock) { mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED).sendToTarget(); @@ -875,6 +930,26 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< } } + @Override + public void startTracing() { + synchronized (mLock) { + if (mProtoTracer != null) { + mProtoTracer.start(); + } + mHandler.obtainMessage(MSG_TRACING_STATE_CHANGED, true).sendToTarget(); + } + } + + @Override + public void stopTracing() { + synchronized (mLock) { + if (mProtoTracer != null) { + mProtoTracer.stop(); + } + mHandler.obtainMessage(MSG_TRACING_STATE_CHANGED, false).sendToTarget(); + } + } + private final class H extends Handler { private H(Looper l) { super(l); @@ -1178,6 +1253,35 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< mCallbacks.get(i).dismissInattentiveSleepWarning((Boolean) msg.obj); } break; + case MSG_SHOW_TOAST: { + args = (SomeArgs) msg.obj; + String packageName = (String) args.arg1; + IBinder token = (IBinder) args.arg2; + CharSequence text = (CharSequence) args.arg3; + IBinder windowToken = (IBinder) args.arg4; + ITransientNotificationCallback callback = + (ITransientNotificationCallback) args.arg5; + int duration = args.argi1; + for (Callbacks callbacks : mCallbacks) { + callbacks.showToast(packageName, token, text, windowToken, duration, + callback); + } + break; + } + case MSG_HIDE_TOAST: { + args = (SomeArgs) msg.obj; + String packageName = (String) args.arg1; + IBinder token = (IBinder) args.arg2; + for (Callbacks callbacks : mCallbacks) { + callbacks.hideToast(packageName, token); + } + break; + } + case MSG_TRACING_STATE_CHANGED: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).onTracingStateChanged((Boolean) msg.obj); + } + break; } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java index ac05c53c38dd..6839921e90a7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java @@ -56,7 +56,7 @@ public class FeatureFlags { } public boolean isNewNotifPipelineEnabled() { - return getDeviceConfigFlag("notification.newpipeline.enabled", false); + return getDeviceConfigFlag("notification.newpipeline.enabled", true); } public boolean isNewNotifPipelineRenderingEnabled() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index 976531d8b49d..12298817d5a6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -79,6 +79,7 @@ public class NotificationLockscreenUserManagerImpl implements private final DeviceProvisionedController mDeviceProvisionedController; private final KeyguardStateController mKeyguardStateController; + private final Object mLock = new Object(); // Lazy private NotificationEntryManager mEntryManager; @@ -181,6 +182,7 @@ public class NotificationLockscreenUserManagerImpl implements protected final Context mContext; private final Handler mMainHandler; protected final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>(); + protected final ArrayList<UserInfo> mCurrentManagedProfiles = new ArrayList<>(); protected int mCurrentUserId = 0; protected NotificationPresenter mPresenter; @@ -280,7 +282,8 @@ public class NotificationLockscreenUserManagerImpl implements filter.addAction(Intent.ACTION_USER_UNLOCKED); filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); - mBroadcastDispatcher.registerReceiver(mBaseBroadcastReceiver, filter); + mBroadcastDispatcher.registerReceiver(mBaseBroadcastReceiver, filter, + null /* executor */, UserHandle.ALL); IntentFilter internalFilter = new IntentFilter(); internalFilter.addAction(NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION); @@ -300,7 +303,7 @@ public class NotificationLockscreenUserManagerImpl implements } public boolean isCurrentProfile(int userId) { - synchronized (mCurrentProfiles) { + synchronized (mLock) { return userId == UserHandle.USER_ALL || mCurrentProfiles.get(userId) != null; } } @@ -417,6 +420,20 @@ public class NotificationLockscreenUserManagerImpl implements return mUsersAllowingPrivateNotifications.get(userHandle); } + /** + * If all managed profiles (work profiles) can show private data in public (secure & locked.) + */ + public boolean allowsManagedPrivateNotificationsInPublic() { + synchronized (mLock) { + for (UserInfo profile : mCurrentManagedProfiles) { + if (!userAllowsPrivateNotificationsInPublic(profile.id)) { + return false; + } + } + } + return true; + } + private boolean adminAllowsKeyguardFeature(int userHandle, int feature) { if (userHandle == UserHandle.USER_ALL) { return true; @@ -495,11 +512,15 @@ public class NotificationLockscreenUserManagerImpl implements } private void updateCurrentProfilesCache() { - synchronized (mCurrentProfiles) { + synchronized (mLock) { mCurrentProfiles.clear(); + mCurrentManagedProfiles.clear(); if (mUserManager != null) { for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) { mCurrentProfiles.put(user.id, user); + if (UserManager.USER_TYPE_PROFILE_MANAGED.equals(user.userType)) { + mCurrentManagedProfiles.add(user); + } } } } @@ -510,10 +531,29 @@ public class NotificationLockscreenUserManagerImpl implements }); } + /** + * If any of the profiles are in public mode. + */ public boolean isAnyProfilePublicMode() { - for (int i = mCurrentProfiles.size() - 1; i >= 0; i--) { - if (isLockscreenPublicMode(mCurrentProfiles.valueAt(i).id)) { - return true; + synchronized (mLock) { + for (int i = mCurrentProfiles.size() - 1; i >= 0; i--) { + if (isLockscreenPublicMode(mCurrentProfiles.valueAt(i).id)) { + return true; + } + } + } + return false; + } + + /** + * If any managed/work profiles are in public mode. + */ + public boolean isAnyManagedProfilePublicMode() { + synchronized (mLock) { + for (int i = mCurrentManagedProfiles.size() - 1; i >= 0; i--) { + if (isLockscreenPublicMode(mCurrentManagedProfiles.get(i).id)) { + return true; + } } } return false; @@ -620,9 +660,17 @@ public class NotificationLockscreenUserManagerImpl implements pw.print(" mAllowLockscreenRemoteInput="); pw.println(mAllowLockscreenRemoteInput); pw.print(" mCurrentProfiles="); - for (int i = mCurrentProfiles.size() - 1; i >= 0; i--) { - final int userId = mCurrentProfiles.valueAt(i).id; - pw.print("" + userId + " "); + synchronized (mLock) { + for (int i = mCurrentProfiles.size() - 1; i >= 0; i--) { + final int userId = mCurrentProfiles.valueAt(i).id; + pw.print("" + userId + " "); + } + } + pw.print(" mCurrentManagedProfiles="); + synchronized (mLock) { + for (UserInfo userInfo : mCurrentManagedProfiles) { + pw.print("" + userInfo.id + " "); + } } pw.println(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index 667e721ae37d..f3783c83a301 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -25,8 +25,10 @@ import android.app.KeyguardManager; import android.app.Notification; import android.app.PendingIntent; import android.app.RemoteInput; +import android.app.RemoteInputHistoryItem; import android.content.Context; import android.content.Intent; +import android.net.Uri; import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; @@ -345,7 +347,8 @@ public class NotificationRemoteInputManager implements Dumpable { }); mSmartReplyController.setCallback((entry, reply) -> { StatusBarNotification newSbn = - rebuildNotificationWithRemoteInput(entry, reply, true /* showSpinner */); + rebuildNotificationWithRemoteInput(entry, reply, true /* showSpinner */, + null /* mimeType */, null /* uri */); mEntryManager.updateNotification(newSbn, null /* ranking */); }); } @@ -527,28 +530,36 @@ public class NotificationRemoteInputManager implements Dumpable { StatusBarNotification rebuildNotificationForCanceledSmartReplies( NotificationEntry entry) { return rebuildNotificationWithRemoteInput(entry, null /* remoteInputTest */, - false /* showSpinner */); + false /* showSpinner */, null /* mimeType */, null /* uri */); } @VisibleForTesting StatusBarNotification rebuildNotificationWithRemoteInput(NotificationEntry entry, - CharSequence remoteInputText, boolean showSpinner) { + CharSequence remoteInputText, boolean showSpinner, String mimeType, Uri uri) { StatusBarNotification sbn = entry.getSbn(); Notification.Builder b = Notification.Builder .recoverBuilder(mContext, sbn.getNotification().clone()); - if (remoteInputText != null) { - CharSequence[] oldHistory = sbn.getNotification().extras - .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY); - CharSequence[] newHistory; - if (oldHistory == null) { - newHistory = new CharSequence[1]; + if (remoteInputText != null || uri != null) { + RemoteInputHistoryItem[] oldHistoryItems = (RemoteInputHistoryItem[]) + sbn.getNotification().extras.getParcelableArray( + Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); + RemoteInputHistoryItem[] newHistoryItems; + + if (oldHistoryItems == null) { + newHistoryItems = new RemoteInputHistoryItem[1]; } else { - newHistory = new CharSequence[oldHistory.length + 1]; - System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length); + newHistoryItems = new RemoteInputHistoryItem[oldHistoryItems.length + 1]; + System.arraycopy(oldHistoryItems, 0, newHistoryItems, 1, oldHistoryItems.length); } - newHistory[0] = String.valueOf(remoteInputText); - b.setRemoteInputHistory(newHistory); + RemoteInputHistoryItem newItem; + if (uri != null) { + newItem = new RemoteInputHistoryItem(mimeType, uri, remoteInputText); + } else { + newItem = new RemoteInputHistoryItem(remoteInputText); + } + newHistoryItems[0] = newItem; + b.setRemoteInputHistory(newHistoryItems); } b.setShowRemoteInputSpinner(showSpinner); b.setHideSmartReplies(true); @@ -631,8 +642,11 @@ public class NotificationRemoteInputManager implements Dumpable { if (TextUtils.isEmpty(remoteInputText)) { remoteInputText = entry.remoteInputTextWhenReset; } + String remoteInputMimeType = entry.remoteInputMimeType; + Uri remoteInputUri = entry.remoteInputUri; StatusBarNotification newSbn = rebuildNotificationWithRemoteInput(entry, - remoteInputText, false /* showSpinner */); + remoteInputText, false /* showSpinner */, remoteInputMimeType, + remoteInputUri); entry.onRemoteInputInserted(); if (newSbn == null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java index 6b0b5dfeaf40..8d4a9efbcd7a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -140,7 +140,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle boolean hideMedia = Utils.useQsMediaPlayer(mContext); if (ent.isRowDismissed() || ent.isRowRemoved() || (ent.isMediaNotification() && hideMedia) - || mBubbleController.isBubbleNotificationSuppressedFromShade(ent.getKey())) { + || mBubbleController.isBubbleNotificationSuppressedFromShade(ent)) { // we don't want to update removed notifications because they could // temporarily become children if they were isolated before. continue; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java index ec8dbead7de2..493482aacce5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java @@ -19,6 +19,8 @@ package com.android.systemui.statusbar; import android.content.Context; import com.android.systemui.statusbar.notification.row.NotificationRowModule; +import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.tracing.ProtoTracer; import javax.inject.Singleton; @@ -35,8 +37,8 @@ public class StatusBarDependenciesModule { */ @Provides @Singleton - public CommandQueue provideCommandQueue(Context context) { - return new CommandQueue(context); + public CommandQueue provideCommandQueue(Context context, ProtoTracer protoTracer) { + return new CommandQueue(context, protoTracer); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/BypassHeadsUpNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/BypassHeadsUpNotifier.kt index 015c32348bb0..269a7a59f1b7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/BypassHeadsUpNotifier.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/BypassHeadsUpNotifier.kt @@ -43,10 +43,10 @@ class BypassHeadsUpNotifier @Inject constructor( private val headsUpManager: HeadsUpManagerPhone, private val notificationLockscreenUserManager: NotificationLockscreenUserManager, private val mediaManager: NotificationMediaManager, + private val entryManager: NotificationEntryManager, tunerService: TunerService ) : StatusBarStateController.StateListener, NotificationMediaManager.MediaListener { - private lateinit var entryManager: NotificationEntryManager private var currentMediaEntry: NotificationEntry? = null private var enabled = true @@ -70,8 +70,7 @@ class BypassHeadsUpNotifier @Inject constructor( }, Settings.Secure.SHOW_MEDIA_WHEN_BYPASSING) } - fun setUp(entryManager: NotificationEntryManager) { - this.entryManager = entryManager + fun setUp() { mediaManager.addCallback(this) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java index 56ad0e1df36f..b048d032feaf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java @@ -44,10 +44,11 @@ public abstract class ListEntry { /** * Should return the "representative entry" for this ListEntry. For NotificationEntries, its - * the entry itself. For groups, it should be the summary. This method exists to interface with + * the entry itself. For groups, it should be the summary (but if a summary doesn't exist, + * this can return null). This method exists to interface with * legacy code that expects groups to also be NotificationEntries. */ - public abstract NotificationEntry getRepresentativeEntry(); + public abstract @Nullable NotificationEntry getRepresentativeEntry(); @Nullable public GroupEntry getParent() { return mParent; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index c488c6bb8721..1b6170326bac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -44,17 +44,18 @@ import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; -import android.util.Log; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.DumpController; import com.android.systemui.Dumpable; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.notification.collection.coalescer.CoalescedEvent; import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer; import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer.BatchableNotificationHandler; import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import com.android.systemui.util.Assert; @@ -91,13 +92,15 @@ import javax.inject.Singleton; * {@link #addNotificationLifetimeExtender(NotifLifetimeExtender)}). * * Interested parties can register listeners - * ({@link #addCollectionListener(NotifCollectionListener)}) to be informed when notifications are - * added, updated, or removed. + * ({@link #addCollectionListener(NotifCollectionListener)}) to be informed when notifications + * events occur. */ @MainThread @Singleton public class NotifCollection implements Dumpable { private final IStatusBarService mStatusBarService; + private final FeatureFlags mFeatureFlags; + private final NotifCollectionLogger mLogger; private final Map<String, NotificationEntry> mNotificationSet = new ArrayMap<>(); private final Collection<NotificationEntry> mReadOnlyNotificationSet = @@ -111,10 +114,16 @@ public class NotifCollection implements Dumpable { private boolean mAmDispatchingToOtherCode; @Inject - public NotifCollection(IStatusBarService statusBarService, DumpController dumpController) { + public NotifCollection( + IStatusBarService statusBarService, + DumpController dumpController, + FeatureFlags featureFlags, + NotifCollectionLogger logger) { Assert.isMainThread(); mStatusBarService = statusBarService; + mLogger = logger; dumpController.registerDumpable(TAG, this); + mFeatureFlags = featureFlags; } /** Initializes the NotifCollection and registers it to receive notification events. */ @@ -184,8 +193,8 @@ public class NotifCollection implements Dumpable { private void onNotificationGroupPosted(List<CoalescedEvent> batch) { Assert.isMainThread(); - Log.d(TAG, "POSTED GROUP " + batch.get(0).getSbn().getGroupKey() - + " (" + batch.size() + " events)"); + mLogger.logNotifGroupPosted(batch.get(0).getSbn().getGroupKey(), batch.size()); + for (CoalescedEvent event : batch) { postNotification(event.getSbn(), event.getRanking(), null); } @@ -198,13 +207,14 @@ public class NotifCollection implements Dumpable { int reason) { Assert.isMainThread(); - Log.d(TAG, "REMOVED " + sbn.getKey() + " reason=" + reason); + mLogger.logNotifRemoved(sbn.getKey(), reason); removeNotification(sbn.getKey(), rankingMap, reason, null); } private void onNotificationRankingUpdate(RankingMap rankingMap) { Assert.isMainThread(); applyRanking(rankingMap); + dispatchNotificationRankingUpdate(rankingMap); rebuildList(); } @@ -216,10 +226,12 @@ public class NotifCollection implements Dumpable { if (entry == null) { // A new notification! - Log.d(TAG, "POSTED " + sbn.getKey()); + mLogger.logNotifPosted(sbn.getKey()); entry = new NotificationEntry(sbn, ranking); mNotificationSet.put(sbn.getKey(), entry); + dispatchOnEntryInit(entry); + if (rankingMap != null) { applyRanking(rankingMap); } @@ -228,7 +240,7 @@ public class NotifCollection implements Dumpable { } else { // Update to an existing entry - Log.d(TAG, "UPDATED " + sbn.getKey()); + mLogger.logNotifUpdated(sbn.getKey()); // Notification is updated so it is essentially re-added and thus alive again. Don't // need to keep its lifetime extended. @@ -287,6 +299,7 @@ public class NotifCollection implements Dumpable { } dispatchOnEntryRemoved(entry, reason, dismissedByUserStats != null /* removedByUser */); + dispatchOnEntryCleanUp(entry); } rebuildList(); @@ -295,15 +308,26 @@ public class NotifCollection implements Dumpable { private void applyRanking(@NonNull RankingMap rankingMap) { for (NotificationEntry entry : mNotificationSet.values()) { if (!isLifetimeExtended(entry)) { - Ranking ranking = requireRanking(rankingMap, entry.getKey()); - entry.setRanking(ranking); - - // TODO: (b/145659174) update the sbn's overrideGroupKey in - // NotificationEntry.setRanking instead of here once we fully migrate to the - // NewNotifPipeline - final String newOverrideGroupKey = ranking.getOverrideGroupKey(); - if (!Objects.equals(entry.getSbn().getOverrideGroupKey(), newOverrideGroupKey)) { - entry.getSbn().setOverrideGroupKey(newOverrideGroupKey); + + // TODO: (b/148791039) We should crash if we are ever handed a ranking with + // incomplete entries. Right now, there's a race condition in NotificationListener + // that means this might occur when SystemUI is starting up. + Ranking ranking = new Ranking(); + if (rankingMap.getRanking(entry.getKey(), ranking)) { + entry.setRanking(ranking); + + // TODO: (b/145659174) update the sbn's overrideGroupKey in + // NotificationEntry.setRanking instead of here once we fully migrate to the + // NewNotifPipeline + if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { + final String newOverrideGroupKey = ranking.getOverrideGroupKey(); + if (!Objects.equals(entry.getSbn().getOverrideGroupKey(), + newOverrideGroupKey)) { + entry.getSbn().setOverrideGroupKey(newOverrideGroupKey); + } + } + } else { + mLogger.logRankingMissing(entry.getKey(), rankingMap); } } } @@ -365,6 +389,14 @@ public class NotifCollection implements Dumpable { return ranking; } + private void dispatchOnEntryInit(NotificationEntry entry) { + mAmDispatchingToOtherCode = true; + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onEntryInit(entry); + } + mAmDispatchingToOtherCode = false; + } + private void dispatchOnEntryAdded(NotificationEntry entry) { mAmDispatchingToOtherCode = true; for (NotifCollectionListener listener : mNotifCollectionListeners) { @@ -381,6 +413,14 @@ public class NotifCollection implements Dumpable { mAmDispatchingToOtherCode = false; } + private void dispatchNotificationRankingUpdate(RankingMap map) { + mAmDispatchingToOtherCode = true; + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onRankingUpdate(map); + } + mAmDispatchingToOtherCode = false; + } + private void dispatchOnEntryRemoved( NotificationEntry entry, @CancellationReason int reason, @@ -392,6 +432,14 @@ public class NotifCollection implements Dumpable { mAmDispatchingToOtherCode = false; } + private void dispatchOnEntryCleanUp(NotificationEntry entry) { + mAmDispatchingToOtherCode = true; + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onEntryCleanUp(entry); + } + mAmDispatchingToOtherCode = false; + } + private final BatchableNotificationHandler mNotifHandler = new BatchableNotificationHandler() { @Override public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java index 0377f57a7a9c..9142388374e3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java @@ -90,7 +90,8 @@ public class NotifPipeline { } /** - * Registers a listener to be informed when notifications are added, removed or updated. + * Registers a listener to be informed when there is a notification entry event such as an add, + * update, or remove. */ public void addCollectionListener(NotifCollectionListener listener) { mNotifCollection.addCollectionListener(listener); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 2fcfb8c811aa..1f77ec229041 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -37,8 +37,10 @@ import android.app.Notification.MessagingStyle.Message; import android.app.NotificationChannel; import android.app.NotificationManager.Policy; import android.app.Person; +import android.app.RemoteInputHistoryItem; import android.content.Context; import android.graphics.drawable.Icon; +import android.net.Uri; import android.os.Bundle; import android.os.SystemClock; import android.service.notification.NotificationListenerService.Ranking; @@ -120,6 +122,8 @@ public final class NotificationEntry extends ListEntry { public int targetSdk; private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET; public CharSequence remoteInputText; + public String remoteInputMimeType; + public Uri remoteInputUri; private Notification.BubbleMetadata mBubbleMetadata; /** @@ -595,8 +599,8 @@ public final class NotificationEntry extends ListEntry { return false; } Bundle extras = mSbn.getNotification().extras; - CharSequence[] replyTexts = extras.getCharSequenceArray( - Notification.EXTRA_REMOTE_INPUT_HISTORY); + RemoteInputHistoryItem[] replyTexts = (RemoteInputHistoryItem[]) extras.getParcelableArray( + Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); if (!ArrayUtils.isEmpty(replyTexts)) { return true; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index 97f8ec5f5bb7..be5c535b7f47 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -32,19 +32,20 @@ import android.annotation.Nullable; import android.util.ArrayMap; import android.util.Pair; +import androidx.annotation.NonNull; + import com.android.systemui.DumpController; import com.android.systemui.Dumpable; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener; import com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState; +import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderLogger; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection; import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; -import com.android.systemui.statusbar.notification.logging.NotifEvent; -import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.util.Assert; import com.android.systemui.util.time.SystemClock; @@ -69,7 +70,7 @@ import javax.inject.Singleton; @Singleton public class ShadeListBuilder implements Dumpable { private final SystemClock mSystemClock; - private final NotifLog mNotifLog; + private final ShadeListBuilderLogger mLogger; private List<ListEntry> mNotifList = new ArrayList<>(); private List<ListEntry> mNewNotifList = new ArrayList<>(); @@ -93,16 +94,17 @@ public class ShadeListBuilder implements Dumpable { new ArrayList<>(); @Nullable private OnRenderListListener mOnRenderListListener; - private final List<ListEntry> mReadOnlyNotifList = Collections.unmodifiableList(mNotifList); + private List<ListEntry> mReadOnlyNotifList = Collections.unmodifiableList(mNotifList); + private List<ListEntry> mReadOnlyNewNotifList = Collections.unmodifiableList(mNewNotifList); @Inject public ShadeListBuilder( SystemClock systemClock, - NotifLog notifLog, + ShadeListBuilderLogger logger, DumpController dumpController) { Assert.isMainThread(); mSystemClock = systemClock; - mNotifLog = notifLog; + mLogger = logger; dumpController.registerDumpable(TAG, this); } @@ -205,8 +207,7 @@ public class ShadeListBuilder implements Dumpable { Assert.isMainThread(); mPipelineState.requireIsBefore(STATE_BUILD_STARTED); - mNotifLog.log(NotifEvent.ON_BUILD_LIST, "Request received from " - + "NotifCollection"); + mLogger.logOnBuildList(); mAllEntries = entries; buildList(); } @@ -215,21 +216,15 @@ public class ShadeListBuilder implements Dumpable { private void onPreGroupFilterInvalidated(NotifFilter filter) { Assert.isMainThread(); - mNotifLog.log(NotifEvent.PRE_GROUP_FILTER_INVALIDATED, String.format( - "Filter \"%s\" invalidated; pipeline state is %d", - filter.getName(), - mPipelineState.getState())); + mLogger.logPreGroupFilterInvalidated(filter.getName(), mPipelineState.getState()); rebuildListIfBefore(STATE_PRE_GROUP_FILTERING); } - private void onPromoterInvalidated(NotifPromoter filter) { + private void onPromoterInvalidated(NotifPromoter promoter) { Assert.isMainThread(); - mNotifLog.log(NotifEvent.PROMOTER_INVALIDATED, String.format( - "NotifPromoter \"%s\" invalidated; pipeline state is %d", - filter.getName(), - mPipelineState.getState())); + mLogger.logPromoterInvalidated(promoter.getName(), mPipelineState.getState()); rebuildListIfBefore(STATE_TRANSFORMING); } @@ -237,10 +232,7 @@ public class ShadeListBuilder implements Dumpable { private void onNotifSectionInvalidated(NotifSection section) { Assert.isMainThread(); - mNotifLog.log(NotifEvent.SECTION_INVALIDATED, String.format( - "Section \"%s\" invalidated; pipeline state is %d", - section.getName(), - mPipelineState.getState())); + mLogger.logNotifSectionInvalidated(section.getName(), mPipelineState.getState()); rebuildListIfBefore(STATE_SORTING); } @@ -248,10 +240,7 @@ public class ShadeListBuilder implements Dumpable { private void onPreRenderFilterInvalidated(NotifFilter filter) { Assert.isMainThread(); - mNotifLog.log(NotifEvent.PRE_RENDER_FILTER_INVALIDATED, String.format( - "Filter \"%s\" invalidated; pipeline state is %d", - filter.getName(), - mPipelineState.getState())); + mLogger.logPreRenderFilterInvalidated(filter.getName(), mPipelineState.getState()); rebuildListIfBefore(STATE_PRE_RENDER_FILTERING); } @@ -259,26 +248,12 @@ public class ShadeListBuilder implements Dumpable { private void onNotifComparatorInvalidated(NotifComparator comparator) { Assert.isMainThread(); - mNotifLog.log(NotifEvent.COMPARATOR_INVALIDATED, String.format( - "Comparator \"%s\" invalidated; pipeline state is %d", - comparator.getName(), - mPipelineState.getState())); + mLogger.logNotifComparatorInvalidated(comparator.getName(), mPipelineState.getState()); rebuildListIfBefore(STATE_SORTING); } /** - * Points mNotifList to the list stored in mNewNotifList. - * Reuses the (emptied) mNotifList as mNewNotifList. - */ - private void applyNewNotifList() { - mNotifList.clear(); - List<ListEntry> emptyList = mNotifList; - mNotifList = mNewNotifList; - mNewNotifList = emptyList; - } - - /** * The core algorithm of the pipeline. See the top comment in {@link NotifPipeline} for * details on our contracts with other code. * @@ -288,7 +263,7 @@ public class ShadeListBuilder implements Dumpable { * if we detect that behavior, we should crash instantly. */ private void buildList() { - mNotifLog.log(NotifEvent.START_BUILD_LIST, "Run #" + mIterationCount + "..."); + mLogger.logStartBuildList(mIterationCount); mPipelineState.requireIsBefore(STATE_BUILD_STARTED); mPipelineState.setState(STATE_BUILD_STARTED); @@ -334,20 +309,37 @@ public class ShadeListBuilder implements Dumpable { freeEmptyGroups(); // Step 6: Dispatch the new list, first to any listeners and then to the view layer - mNotifLog.log(NotifEvent.DISPATCH_FINAL_LIST, "List finalized, is:\n" - + ListDumper.dumpTree(mNotifList, false, "\t\t")); + if (mIterationCount % 10 == 0) { + mLogger.logFinalList(mNotifList); + } dispatchOnBeforeRenderList(mReadOnlyNotifList); if (mOnRenderListListener != null) { mOnRenderListListener.onRenderList(mReadOnlyNotifList); } // Step 7: We're done! - mNotifLog.log(NotifEvent.LIST_BUILD_COMPLETE, - "Notif list build #" + mIterationCount + " completed"); + mLogger.logEndBuildList(mIterationCount); mPipelineState.setState(STATE_IDLE); mIterationCount++; } + /** + * Points mNotifList to the list stored in mNewNotifList. + * Reuses the (emptied) mNotifList as mNewNotifList. + * + * Accordingly, updates the ReadOnlyNotifList pointers. + */ + private void applyNewNotifList() { + mNotifList.clear(); + List<ListEntry> emptyList = mNotifList; + mNotifList = mNewNotifList; + mNewNotifList = emptyList; + + List<ListEntry> readOnlyNotifList = mReadOnlyNotifList; + mReadOnlyNotifList = mReadOnlyNewNotifList; + mReadOnlyNewNotifList = readOnlyNotifList; + } + private void resetNotifs() { for (GroupEntry group : mGroups.values()) { group.setPreviousParent(group.getParent()); @@ -429,11 +421,10 @@ public class ShadeListBuilder implements Dumpable { if (existingSummary == null) { group.setSummary(entry); } else { - mNotifLog.log(NotifEvent.WARN, String.format( - "Duplicate summary for group '%s': '%s' vs. '%s'", + mLogger.logDuplicateSummary( group.getKey(), existingSummary.getKey(), - entry.getKey())); + entry.getKey()); // Use whichever one was posted most recently if (entry.getSbn().getPostTime() @@ -452,8 +443,7 @@ public class ShadeListBuilder implements Dumpable { final String topLevelKey = entry.getKey(); if (mGroups.containsKey(topLevelKey)) { - mNotifLog.log(NotifEvent.WARN, - "Duplicate non-group top-level key: " + topLevelKey); + mLogger.logDuplicateTopLevelKey(topLevelKey); } else { entry.setParent(ROOT_ENTRY); out.add(entry); @@ -617,24 +607,22 @@ public class ShadeListBuilder implements Dumpable { private void logParentingChanges() { for (NotificationEntry entry : mAllEntries) { if (entry.getParent() != entry.getPreviousParent()) { - mNotifLog.log(NotifEvent.PARENT_CHANGED, String.format( - "%s: parent changed from %s to %s", + mLogger.logParentChanged( entry.getKey(), entry.getPreviousParent() == null - ? "null" : entry.getPreviousParent().getKey(), + ? null : entry.getPreviousParent().getKey(), entry.getParent() == null - ? "null" : entry.getParent().getKey())); + ? null : entry.getParent().getKey()); } } for (GroupEntry group : mGroups.values()) { if (group.getParent() != group.getPreviousParent()) { - mNotifLog.log(NotifEvent.PARENT_CHANGED, String.format( - "%s: parent changed from %s to %s", + mLogger.logParentChanged( group.getKey(), group.getPreviousParent() == null - ? "null" : group.getPreviousParent().getKey(), + ? null : group.getPreviousParent().getKey(), group.getParent() == null - ? "null" : group.getParent().getKey())); + ? null : group.getParent().getKey()); } } } @@ -684,23 +672,10 @@ public class ShadeListBuilder implements Dumpable { NotifFilter filter = findRejectingFilter(entry, now, filters); if (filter != entry.mExcludingFilter) { - if (entry.mExcludingFilter == null) { - mNotifLog.log(NotifEvent.FILTER_CHANGED, String.format( - "%s: filtered out by '%s'", - entry.getKey(), - filter.getName())); - } else if (filter == null) { - mNotifLog.log(NotifEvent.FILTER_CHANGED, String.format( - "%s: no longer filtered out (previous filter was '%s')", - entry.getKey(), - entry.mExcludingFilter.getName())); - } else { - mNotifLog.log(NotifEvent.FILTER_CHANGED, String.format( - "%s: filter changed: '%s' -> '%s'", - entry.getKey(), - entry.mExcludingFilter, - filter)); - } + mLogger.logFilterChanged( + entry.getKey(), + entry.mExcludingFilter != null ? entry.mExcludingFilter.getName() : null, + filter != null ? filter.getName() : null); // Note that groups and summaries can also be filtered out later if they're part of a // malformed group. We currently don't have a great way to track that beyond parenting @@ -728,23 +703,10 @@ public class ShadeListBuilder implements Dumpable { NotifPromoter promoter = findPromoter(entry); if (promoter != entry.mNotifPromoter) { - if (entry.mNotifPromoter == null) { - mNotifLog.log(NotifEvent.PROMOTER_CHANGED, String.format( - "%s: Entry promoted to top level by '%s'", - entry.getKey(), - promoter.getName())); - } else if (promoter == null) { - mNotifLog.log(NotifEvent.PROMOTER_CHANGED, String.format( - "%s: Entry is no longer promoted to top level (previous promoter was '%s')", - entry.getKey(), - entry.mNotifPromoter.getName())); - } else { - mNotifLog.log(NotifEvent.PROMOTER_CHANGED, String.format( - "%s: Top-level promoter changed: '%s' -> '%s'", - entry.getKey(), - entry.mNotifPromoter, - promoter)); - } + mLogger.logPromoterChanged( + entry.getKey(), + entry.mNotifPromoter != null ? entry.mNotifPromoter.getName() : null, + promoter != null ? promoter.getName() : null); entry.mNotifPromoter = promoter; } @@ -767,21 +729,12 @@ public class ShadeListBuilder implements Dumpable { final Integer sectionIndex = sectionWithIndex.second; if (section != entry.mNotifSection) { - if (entry.mNotifSection == null) { - mNotifLog.log(NotifEvent.SECTION_CHANGED, String.format( - "%s: sectioned by '%s' [index=%d].", - entry.getKey(), - section.getName(), - sectionIndex)); - } else { - mNotifLog.log(NotifEvent.SECTION_CHANGED, String.format( - "%s: section changed: '%s' [index=%d] -> '%s [index=%d]'.", - entry.getKey(), - entry.mNotifSection, - entry.getSection(), - section, - sectionIndex)); - } + mLogger.logSectionChanged( + entry.getKey(), + entry.mNotifSection != null ? entry.mNotifSection.getName() : null, + entry.getSection(), + section.getName(), + sectionIndex); entry.mNotifSection = section; entry.setSection(sectionIndex); @@ -826,7 +779,7 @@ public class ShadeListBuilder implements Dumpable { } @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + public void dump(@NonNull FileDescriptor fd, PrintWriter pw, @NonNull String[] args) { pw.println("\t" + TAG + " shade notifications:"); if (getShadeList().size() == 0) { pw.println("\t\t None"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java index f5890386a14f..98c45ffd6afb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java @@ -16,11 +16,6 @@ package com.android.systemui.statusbar.notification.collection.coalescer; -import static com.android.systemui.statusbar.notification.logging.NotifEvent.BATCH_MAX_TIMEOUT; -import static com.android.systemui.statusbar.notification.logging.NotifEvent.COALESCED_EVENT; -import static com.android.systemui.statusbar.notification.logging.NotifEvent.EARLY_BATCH_EMIT; -import static com.android.systemui.statusbar.notification.logging.NotifEvent.EMIT_EVENT_BATCH; - import static java.util.Objects.requireNonNull; import android.annotation.MainThread; @@ -35,7 +30,6 @@ import com.android.systemui.Dumpable; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationListener.NotificationHandler; -import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.time.SystemClock; @@ -71,7 +65,7 @@ import javax.inject.Inject; public class GroupCoalescer implements Dumpable { private final DelayableExecutor mMainExecutor; private final SystemClock mClock; - private final NotifLog mLog; + private final GroupCoalescerLogger mLogger; private final long mMinGroupLingerDuration; private final long mMaxGroupLingerDuration; @@ -83,8 +77,9 @@ public class GroupCoalescer implements Dumpable { @Inject public GroupCoalescer( @Main DelayableExecutor mainExecutor, - SystemClock clock, NotifLog log) { - this(mainExecutor, clock, log, MIN_GROUP_LINGER_DURATION, MAX_GROUP_LINGER_DURATION); + SystemClock clock, + GroupCoalescerLogger logger) { + this(mainExecutor, clock, logger, MIN_GROUP_LINGER_DURATION, MAX_GROUP_LINGER_DURATION); } /** @@ -98,12 +93,12 @@ public class GroupCoalescer implements Dumpable { GroupCoalescer( @Main DelayableExecutor mainExecutor, SystemClock clock, - NotifLog log, + GroupCoalescerLogger logger, long minGroupLingerDuration, long maxGroupLingerDuration) { mMainExecutor = mainExecutor; mClock = clock; - mLog = log; + mLogger = logger; mMinGroupLingerDuration = minGroupLingerDuration; mMaxGroupLingerDuration = maxGroupLingerDuration; } @@ -129,7 +124,7 @@ public class GroupCoalescer implements Dumpable { final boolean shouldCoalesce = handleNotificationPosted(sbn, rankingMap); if (shouldCoalesce) { - mLog.log(COALESCED_EVENT, String.format("Coalesced notification %s", sbn.getKey())); + mLogger.logEventCoalesced(sbn.getKey()); mHandler.onNotificationRankingUpdate(rankingMap); } else { mHandler.onNotificationPosted(sbn, rankingMap); @@ -164,15 +159,11 @@ public class GroupCoalescer implements Dumpable { final CoalescedEvent event = mCoalescedEvents.get(sbn.getKey()); final EventBatch batch = mBatches.get(sbn.getGroupKey()); if (event != null) { - mLog.log(EARLY_BATCH_EMIT, - String.format("Modification of %s triggered early emit of batched group %s", - sbn.getKey(), requireNonNull(event.getBatch()).mGroupKey)); + mLogger.logEarlyEmit(sbn.getKey(), requireNonNull(event.getBatch()).mGroupKey); emitBatch(requireNonNull(event.getBatch())); } else if (batch != null && mClock.uptimeMillis() - batch.mCreatedTimestamp >= mMaxGroupLingerDuration) { - mLog.log(BATCH_MAX_TIMEOUT, - String.format("Modification of %s triggered timeout emit of batched group %s", - sbn.getKey(), batch.mGroupKey)); + mLogger.logMaxBatchTimeout(sbn.getKey(), batch.mGroupKey); emitBatch(batch); } } @@ -253,7 +244,7 @@ public class GroupCoalescer implements Dumpable { } events.sort(mEventComparator); - mLog.log(EMIT_EVENT_BATCH, "Emitting event batch for group " + batch.mGroupKey); + mLogger.logEmitBatch(batch.mGroupKey); mHandler.onNotificationBatchPosted(events); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt new file mode 100644 index 000000000000..6e8788db59d7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.coalescer + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.dagger.NotificationLog +import javax.inject.Inject + +class GroupCoalescerLogger @Inject constructor( + @NotificationLog private val buffer: LogBuffer +) { + fun logEventCoalesced(key: String) { + buffer.log(TAG, LogLevel.INFO, { + str1 = key + }, { + "COALESCED: $str1" + }) + } + + fun logEmitBatch(groupKey: String) { + buffer.log(TAG, LogLevel.DEBUG, { + str1 = groupKey + }, { + "Emitting event batch for group $str1" + }) + } + + fun logEarlyEmit(modifiedKey: String, groupKey: String) { + buffer.log(TAG, LogLevel.DEBUG, { + str1 = modifiedKey + str2 = groupKey + }, { + "Modification of notif $str1 triggered early emit of batched group $str2" + }) + } + + fun logMaxBatchTimeout(modifiedKey: String, groupKey: String) { + buffer.log(TAG, LogLevel.INFO, { + str1 = modifiedKey + str2 = groupKey + }, { + "Modification of notif $str1 triggered TIMEOUT emit of batched group $str2" + }) + } +} + +private const val TAG = "GroupCoalescer"
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java index da119c1502c6..854444fc8bb7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import android.app.Notification; -import android.os.Handler; import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.util.ArraySet; @@ -30,6 +29,8 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; +import com.android.systemui.util.Assert; +import com.android.systemui.util.concurrency.DelayableExecutor; import java.util.HashMap; import java.util.Map; @@ -47,6 +48,8 @@ import javax.inject.Singleton; * frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceController * frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener * frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender + * + * TODO: AppOps stuff should be spun off into its own coordinator */ @Singleton public class ForegroundCoordinator implements Coordinator { @@ -54,7 +57,7 @@ public class ForegroundCoordinator implements Coordinator { private final ForegroundServiceController mForegroundServiceController; private final AppOpsController mAppOpsController; - private final Handler mMainHandler; + private final DelayableExecutor mMainExecutor; private NotifPipeline mNotifPipeline; @@ -62,10 +65,10 @@ public class ForegroundCoordinator implements Coordinator { public ForegroundCoordinator( ForegroundServiceController foregroundServiceController, AppOpsController appOpsController, - @Main Handler mainHandler) { + @Main DelayableExecutor mainExecutor) { mForegroundServiceController = foregroundServiceController; mAppOpsController = appOpsController; - mMainHandler = mainHandler; + mMainExecutor = mainExecutor; } @Override @@ -78,11 +81,11 @@ public class ForegroundCoordinator implements Coordinator { // listen for new notifications to add appOps mNotifPipeline.addCollectionListener(mNotifCollectionListener); - // when appOps change, update any relevant notifications to update appOps for - mAppOpsController.addCallback(ForegroundServiceController.APP_OPS, this::onAppOpsChanged); - // filter out foreground service notifications that aren't necessary anymore mNotifPipeline.addPreGroupFilter(mNotifFilter); + + // when appOps change, update any relevant notifications to update appOps for + mAppOpsController.addCallback(ForegroundServiceController.APP_OPS, this::onAppOpsChanged); } /** @@ -93,7 +96,8 @@ public class ForegroundCoordinator implements Coordinator { public boolean shouldFilterOut(NotificationEntry entry, long now) { StatusBarNotification sbn = entry.getSbn(); if (mForegroundServiceController.isDisclosureNotification(sbn) - && !mForegroundServiceController.isDisclosureNeededForUser(sbn.getUserId())) { + && !mForegroundServiceController.isDisclosureNeededForUser( + sbn.getUser().getIdentifier())) { return true; } @@ -102,7 +106,7 @@ public class ForegroundCoordinator implements Coordinator { Notification.EXTRA_FOREGROUND_APPS); if (apps != null && apps.length >= 1) { if (!mForegroundServiceController.isSystemAlertWarningNeeded( - sbn.getUserId(), apps[0])) { + sbn.getUser().getIdentifier(), apps[0])) { return true; } } @@ -119,7 +123,7 @@ public class ForegroundCoordinator implements Coordinator { new NotifLifetimeExtender() { private static final int MIN_FGS_TIME_MS = 5000; private OnEndLifetimeExtensionCallback mEndCallback; - private Map<String, Runnable> mEndRunnables = new HashMap<>(); + private Map<NotificationEntry, Runnable> mEndRunnables = new HashMap<>(); @Override public String getName() { @@ -142,16 +146,18 @@ public class ForegroundCoordinator implements Coordinator { final boolean extendLife = currTime - entry.getSbn().getPostTime() < MIN_FGS_TIME_MS; if (extendLife) { - if (!mEndRunnables.containsKey(entry.getKey())) { - final Runnable runnable = new Runnable() { - @Override - public void run() { - mEndCallback.onEndLifetimeExtension(mForegroundLifetimeExtender, entry); - } + if (!mEndRunnables.containsKey(entry)) { + final Runnable endExtensionRunnable = () -> { + mEndRunnables.remove(entry); + mEndCallback.onEndLifetimeExtension( + mForegroundLifetimeExtender, + entry); }; - mEndRunnables.put(entry.getKey(), runnable); - mMainHandler.postDelayed(runnable, + + final Runnable cancelRunnable = mMainExecutor.executeDelayed( + endExtensionRunnable, MIN_FGS_TIME_MS - (currTime - entry.getSbn().getPostTime())); + mEndRunnables.put(entry, cancelRunnable); } } @@ -160,9 +166,9 @@ public class ForegroundCoordinator implements Coordinator { @Override public void cancelLifetimeExtension(NotificationEntry entry) { - if (mEndRunnables.containsKey(entry.getKey())) { - Runnable endRunnable = mEndRunnables.remove(entry.getKey()); - mMainHandler.removeCallbacks(endRunnable); + Runnable cancelRunnable = mEndRunnables.remove(entry); + if (cancelRunnable != null) { + cancelRunnable.run(); } } }; @@ -184,25 +190,32 @@ public class ForegroundCoordinator implements Coordinator { private void tagForeground(NotificationEntry entry) { final StatusBarNotification sbn = entry.getSbn(); // note: requires that the ForegroundServiceController is updating their appOps first - ArraySet<Integer> activeOps = mForegroundServiceController.getAppOps(sbn.getUserId(), - sbn.getPackageName()); + ArraySet<Integer> activeOps = + mForegroundServiceController.getAppOps( + sbn.getUser().getIdentifier(), + sbn.getPackageName()); if (activeOps != null) { - synchronized (entry.mActiveAppOps) { - entry.mActiveAppOps.clear(); - entry.mActiveAppOps.addAll(activeOps); - } + entry.mActiveAppOps.clear(); + entry.mActiveAppOps.addAll(activeOps); } } }; + private void onAppOpsChanged(int code, int uid, String packageName, boolean active) { + mMainExecutor.execute(() -> handleAppOpsChanged(code, uid, packageName, active)); + } + /** * Update the appOp for the posted notification associated with the current foreground service + * * @param code code for appOp to add/remove * @param uid of user the notification is sent to * @param packageName package that created the notification * @param active whether the appOpCode is active or not */ - private void onAppOpsChanged(int code, int uid, String packageName, boolean active) { + private void handleAppOpsChanged(int code, int uid, String packageName, boolean active) { + Assert.isMainThread(); + int userId = UserHandle.getUserId(uid); // Update appOp if there's an associated posted notification: @@ -214,15 +227,13 @@ public class ForegroundCoordinator implements Coordinator { && uid == entry.getSbn().getUid() && packageName.equals(entry.getSbn().getPackageName())) { boolean changed; - synchronized (entry.mActiveAppOps) { - if (active) { - changed = entry.mActiveAppOps.add(code); - } else { - changed = entry.mActiveAppOps.remove(code); - } + if (active) { + changed = entry.mActiveAppOps.add(code); + } else { + changed = entry.mActiveAppOps.remove(code); } if (changed) { - mMainHandler.post(mNotifFilter::invalidateList); + mNotifFilter.invalidateList(); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt new file mode 100644 index 000000000000..6e15043973f7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.listbuilder + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.LogLevel.WARNING +import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.statusbar.notification.collection.GroupEntry +import com.android.systemui.statusbar.notification.collection.ListEntry +import javax.inject.Inject + +class ShadeListBuilderLogger @Inject constructor( + @NotificationLog private val buffer: LogBuffer +) { + fun logOnBuildList() { + buffer.log(TAG, INFO, { + }, { + "Request received from NotifCollection" + }) + } + + fun logStartBuildList(iterationCount: Int) { + buffer.log(TAG, INFO, { + int1 = iterationCount + }, { + "Starting to build shade list (run #$int1)" + }) + } + + fun logEndBuildList(iterationCount: Int) { + buffer.log(TAG, INFO, { + int1 = iterationCount + }, { + "Finished building shade list (run #$int1)" + }) + } + + fun logPreGroupFilterInvalidated(filterName: String, pipelineState: Int) { + buffer.log(TAG, DEBUG, { + str1 = filterName + int1 = pipelineState + }, { + """Pre-group NotifFilter "$str1" invalidated; pipeline state is $int1""" + }) + } + + fun logPromoterInvalidated(name: String, pipelineState: Int) { + buffer.log(TAG, DEBUG, { + str1 = name + int1 = pipelineState + }, { + """NotifPromoter "$str1" invalidated; pipeline state is $int1""" + }) + } + + fun logNotifSectionInvalidated(name: String, pipelineState: Int) { + buffer.log(TAG, DEBUG, { + str1 = name + int1 = pipelineState + }, { + """NotifSection "$str1" invalidated; pipeline state is $int1""" + }) + } + + fun logNotifComparatorInvalidated(name: String, pipelineState: Int) { + buffer.log(TAG, DEBUG, { + str1 = name + int1 = pipelineState + }, { + """NotifComparator "$str1" invalidated; pipeline state is $int1""" + }) + } + + fun logPreRenderFilterInvalidated(name: String, pipelineState: Int) { + buffer.log(TAG, DEBUG, { + str1 = name + int1 = pipelineState + }, { + """Pre-render NotifFilter "$str1" invalidated; pipeline state is $int1""" + }) + } + + fun logDuplicateSummary(groupKey: String, existingKey: String, newKey: String) { + buffer.log(TAG, WARNING, { + str1 = groupKey + str2 = existingKey + str3 = newKey + }, { + """Duplicate summary for group "$str1": "$str2" vs. "$str3"""" + }) + } + + fun logDuplicateTopLevelKey(topLevelKey: String) { + buffer.log(TAG, WARNING, { + str1 = topLevelKey + }, { + "Duplicate top-level key: $str1" + }) + } + + fun logParentChanged(key: String, prevParent: String?, newParent: String?) { + buffer.log(TAG, INFO, { + str1 = key + str2 = prevParent + str3 = newParent + }, { + "Parent change for $str1: $str2 -> $str3" + }) + } + + fun logFilterChanged( + key: String, + prevFilter: String?, + newFilter: String? + ) { + buffer.log(TAG, INFO, { + str1 = key + str2 = prevFilter + str3 = newFilter + }, { + "Filter changed for $str1: $str2 -> $str3" + }) + } + + fun logPromoterChanged( + key: String, + prevPromoter: String?, + newPromoter: String? + ) { + buffer.log(TAG, INFO, { + str1 = key + str2 = prevPromoter + str3 = newPromoter + }, { + "Promoter changed for $str1: $str2 -> $str3" + }) + } + + fun logSectionChanged( + key: String, + prevSection: String?, + prevIndex: Int, + section: String, + index: Int + ) { + buffer.log(TAG, INFO, { + str1 = key + str2 = section + int1 = index + str3 = prevSection + int2 = prevIndex + }, { + if (str3 == null) { + "Section assigned for $str1: '$str2' (#$int1)" + } else { + "Section changed for $str1: '$str3' (#$int2) -> '$str2' (#$int1)" + } + }) + } + + fun logFinalList(entries: List<ListEntry>) { + buffer.log(TAG, DEBUG, { + int1 = entries.size + }, { + "List is finalized ($int1 top-level entries):" + }) + if (entries.isEmpty()) { + buffer.log(TAG, DEBUG, {}, { "(empty list)" }) + } + for (i in entries.indices) { + val entry = entries[i] + buffer.log(TAG, DEBUG, { + int1 = i + str1 = entry.key + }, { + "[$int1] $str1" + }) + + if (entry is GroupEntry) { + entry.summary?.let { + buffer.log(TAG, DEBUG, { + str1 = it.key + }, { + " [*] $str1 (summary)" + }) + } + for (j in entry.children.indices) { + val child = entry.children[j] + buffer.log(TAG, DEBUG, { + int1 = j + str1 = child.key + }, { + " [$int1] $str1" + }) + } + } + } + } +} + +private const val TAG = "ShadeListBuilder"
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java index 9cbc7d7efa66..ff6da12bd0bc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java @@ -16,15 +16,27 @@ package com.android.systemui.statusbar.notification.collection.notifcollection; -import com.android.systemui.statusbar.notification.collection.NotifCollection; +import android.service.notification.NotificationListenerService; + import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason; import com.android.systemui.statusbar.notification.collection.NotificationEntry; /** - * Listener interface for {@link NotifCollection}. + * Listener interface for {@link NotificationEntry} events. */ public interface NotifCollectionListener { /** + * Called whenever a new {@link NotificationEntry} is initialized. This should be used for + * initializing any decorated state tied to the notification. + * + * Do not reference other registered {@link NotifCollectionListener} implementations here as + * there is no guarantee of order and they may not have had a chance to initialize yet. Instead, + * use {@link #onEntryAdded} which is called after all initialization. + */ + default void onEntryInit(NotificationEntry entry) { + } + + /** * Called whenever a notification with a new key is posted. */ default void onEntryAdded(NotificationEntry entry) { @@ -47,4 +59,23 @@ public interface NotifCollectionListener { @CancellationReason int reason, boolean removedByUser) { } + + /** + * Called whenever a {@link NotificationEntry} is considered deleted. This should be used for + * cleaning up any state tied to the notification. + * + * This is the deletion parallel of {@link #onEntryInit} and similarly means that you cannot + * expect other {@link NotifCollectionListener} implementations to still have valid state for + * the entry during this call. Instead, use {@link #onEntryRemoved} which will be called before + * deletion. + */ + default void onEntryCleanUp(NotificationEntry entry) { + } + + /** + * Called whenever the RankingMap is updated by system server. By the time this listener is + * called, the Rankings of all entries will have been updated. + */ + default void onRankingUpdate(NotificationListenerService.RankingMap rankingMap) { + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt new file mode 100644 index 000000000000..0d0a46adb41f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.notifcollection + +import android.service.notification.NotificationListenerService.RankingMap +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.LogLevel.WARNING +import com.android.systemui.log.dagger.NotificationLog +import javax.inject.Inject + +class NotifCollectionLogger @Inject constructor( + @NotificationLog private val buffer: LogBuffer +) { + fun logNotifPosted(key: String) { + buffer.log(TAG, INFO, { + str1 = key + }, { + "POSTED $str1" + }) + } + + fun logNotifGroupPosted(groupKey: String, batchSize: Int) { + buffer.log(TAG, INFO, { + str1 = groupKey + int1 = batchSize + }, { + "POSTED GROUP $str1 ($int1 events)" + }) + } + + fun logNotifUpdated(key: String) { + buffer.log(TAG, INFO, { + str1 = key + }, { + "UPDATED $str1" + }) + } + + fun logNotifRemoved(key: String, reason: Int) { + buffer.log(TAG, INFO, { + str1 = key + int1 = reason + }, { + "REMOVED $str1 reason=$int1" + }) + } + + fun logRankingMissing(key: String, rankingMap: RankingMap) { + buffer.log(TAG, WARNING, { str1 = key }, { "Ranking update is missing ranking for $str1" }) + buffer.log(TAG, DEBUG, {}, { "Ranking map contents:" }) + for (entry in rankingMap.orderedKeys) { + buffer.log(TAG, DEBUG, { str1 = entry }, { " $str1" }) + } + } +} + +private const val TAG = "NotifCollection"
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java index b49611688bc7..0d9beaefbf8d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java @@ -63,6 +63,10 @@ public class HighPriorityProvider { } final NotificationEntry notifEntry = entry.getRepresentativeEntry(); + if (notifEntry == null) { + return false; + } + return notifEntry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_DEFAULT || hasHighPriorityCharacteristics(notifEntry) || hasHighPriorityChild(entry); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java new file mode 100644 index 000000000000..c7666e47d4b4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.dagger; + +import android.content.Context; + +import com.android.systemui.R; +import com.android.systemui.statusbar.notification.init.NotificationsController; +import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl; +import com.android.systemui.statusbar.notification.init.NotificationsControllerStub; + +import javax.inject.Singleton; + +import dagger.Lazy; +import dagger.Module; +import dagger.Provides; + +/** Module for classes related to the notifications data pipeline */ +@Module +public class NotificationsModule { + /** Initializes the notification data pipeline (can be disabled via config). */ + @Singleton + @Provides + static NotificationsController provideNotificationsController( + Context context, + Lazy<NotificationsControllerImpl> realController, + Lazy<NotificationsControllerStub> stubController) { + if (context.getResources().getBoolean(R.bool.config_renderNotifications)) { + return realController.get(); + } else { + return stubController.get(); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt new file mode 100644 index 000000000000..9da8b8a3fd92 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.init + +import android.service.notification.StatusBarNotification +import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption +import com.android.systemui.statusbar.NotificationPresenter +import com.android.systemui.statusbar.notification.NotificationActivityStarter +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl +import com.android.systemui.statusbar.notification.stack.NotificationListContainer +import com.android.systemui.statusbar.phone.StatusBar +import java.io.FileDescriptor +import java.io.PrintWriter + +/** + * The master controller for all notifications-related work + * + * Split into two implementations: [NotificationsControllerImpl] (most cases) and + * [NotificationsControllerStub] (for builds that disable notification rendering). + */ +interface NotificationsController { + fun initialize( + statusBar: StatusBar, + presenter: NotificationPresenter, + listContainer: NotificationListContainer, + notificationActivityStarter: NotificationActivityStarter, + bindRowCallback: NotificationRowBinderImpl.BindRowCallback + ) + + fun requestNotificationUpdate(reason: String) + fun resetUserExpandedStates() + fun setNotificationSnoozed(sbn: StatusBarNotification, snoozeOption: SnoozeOption) + fun getActiveNotificationsCount(): Int + fun setNotificationSnoozed(sbn: StatusBarNotification, hoursToSnooze: Int) + fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>, dumpTruck: Boolean) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt new file mode 100644 index 000000000000..61e3192eba3c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.init + +import android.service.notification.StatusBarNotification +import com.android.systemui.bubbles.BubbleController +import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption +import com.android.systemui.statusbar.FeatureFlags +import com.android.systemui.statusbar.NotificationListener +import com.android.systemui.statusbar.NotificationPresenter +import com.android.systemui.statusbar.notification.NotificationActivityStarter +import com.android.systemui.statusbar.notification.NotificationClicker +import com.android.systemui.statusbar.notification.NotificationEntryManager +import com.android.systemui.statusbar.notification.NotificationListController +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl +import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer +import com.android.systemui.statusbar.notification.stack.NotificationListContainer +import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper +import com.android.systemui.statusbar.phone.NotificationGroupManager +import com.android.systemui.statusbar.phone.StatusBar +import com.android.systemui.statusbar.policy.DeviceProvisionedController +import com.android.systemui.statusbar.policy.HeadsUpManager +import com.android.systemui.statusbar.policy.RemoteInputUriController +import dagger.Lazy +import java.io.FileDescriptor +import java.io.PrintWriter +import java.util.Optional +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Master controller for all notifications-related work + * + * At the moment exposes a number of event-handler-esque methods; these are for historical reasons. + * Once we migrate away from the need for such things, this class becomes primarily a place to do + * any initialization work that notifications require. + */ +@Singleton +class NotificationsControllerImpl @Inject constructor( + private val featureFlags: FeatureFlags, + private val notificationListener: NotificationListener, + private val entryManager: NotificationEntryManager, + private val newNotifPipeline: Lazy<NotifPipelineInitializer>, + private val deviceProvisionedController: DeviceProvisionedController, + private val notificationRowBinder: NotificationRowBinderImpl, + private val remoteInputUriController: RemoteInputUriController, + private val bubbleController: BubbleController, + private val groupManager: NotificationGroupManager, + private val groupAlertTransferHelper: NotificationGroupAlertTransferHelper, + private val headsUpManager: HeadsUpManager +) : NotificationsController { + + override fun initialize( + statusBar: StatusBar, + presenter: NotificationPresenter, + listContainer: NotificationListContainer, + notificationActivityStarter: NotificationActivityStarter, + bindRowCallback: NotificationRowBinderImpl.BindRowCallback + ) { + notificationListener.registerAsSystemService() + + val listController = + NotificationListController( + entryManager, + listContainer, + deviceProvisionedController) + listController.bind() + + notificationRowBinder.setNotificationClicker( + NotificationClicker( + Optional.of(statusBar), + bubbleController, + notificationActivityStarter)) + notificationRowBinder.setUpWithPresenter( + presenter, + listContainer, + headsUpManager, + bindRowCallback) + + if (featureFlags.isNewNotifPipelineEnabled) { + newNotifPipeline.get().initialize(notificationListener, notificationRowBinder) + } + + if (featureFlags.isNewNotifPipelineRenderingEnabled) { + // TODO + } else { + notificationRowBinder.setInflationCallback(entryManager) + + remoteInputUriController.attach(entryManager) + groupAlertTransferHelper.bind(entryManager, groupManager) + headsUpManager.addListener(groupManager) + headsUpManager.addListener(groupAlertTransferHelper) + groupManager.setHeadsUpManager(headsUpManager) + groupAlertTransferHelper.setHeadsUpManager(headsUpManager) + + entryManager.attach(notificationListener) + } + } + + override fun dump( + fd: FileDescriptor, + pw: PrintWriter, + args: Array<String>, + dumpTruck: Boolean + ) { + if (dumpTruck) { + entryManager.dump(pw, " ") + } + groupManager.dump(fd, pw, args) + } + + // TODO: Convert all functions below this line into listeners instead of public methods + + override fun requestNotificationUpdate(reason: String) { + entryManager.updateNotifications(reason) + } + + override fun resetUserExpandedStates() { + for (entry in entryManager.visibleNotifications) { + entry.resetUserExpansion() + } + } + + override fun setNotificationSnoozed(sbn: StatusBarNotification, snoozeOption: SnoozeOption) { + if (snoozeOption.snoozeCriterion != null) { + notificationListener.snoozeNotification(sbn.key, snoozeOption.snoozeCriterion.id) + } else { + notificationListener.snoozeNotification( + sbn.key, + snoozeOption.minutesToSnoozeFor * 60 * 1000.toLong()) + } + } + + override fun getActiveNotificationsCount(): Int { + return entryManager.activeNotificationsCount + } + + override fun setNotificationSnoozed(sbn: StatusBarNotification, hoursToSnooze: Int) { + notificationListener.snoozeNotification( + sbn.key, + hoursToSnooze * 60 * 60 * 1000.toLong()) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt new file mode 100644 index 000000000000..ded855dd84f9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.init + +import android.service.notification.StatusBarNotification +import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption +import com.android.systemui.statusbar.NotificationListener +import com.android.systemui.statusbar.NotificationPresenter +import com.android.systemui.statusbar.notification.NotificationActivityStarter +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl +import com.android.systemui.statusbar.notification.stack.NotificationListContainer +import com.android.systemui.statusbar.phone.StatusBar +import java.io.FileDescriptor +import java.io.PrintWriter +import javax.inject.Inject + +/** + * Implementation of [NotificationsController] that's used when notifications rendering is disabled. + */ +class NotificationsControllerStub @Inject constructor( + private val notificationListener: NotificationListener +) : NotificationsController { + + override fun initialize( + statusBar: StatusBar, + presenter: NotificationPresenter, + listContainer: NotificationListContainer, + notificationActivityStarter: NotificationActivityStarter, + bindRowCallback: NotificationRowBinderImpl.BindRowCallback + ) { + // Always connect the listener even if notification-handling is disabled. Being a listener + // grants special permissions and it's not clear if other things will break if we lose those + notificationListener.registerAsSystemService() + } + + override fun requestNotificationUpdate(reason: String) { + } + + override fun resetUserExpandedStates() { + } + + override fun setNotificationSnoozed(sbn: StatusBarNotification, snoozeOption: SnoozeOption) { + } + + override fun setNotificationSnoozed(sbn: StatusBarNotification, hoursToSnooze: Int) { + } + + override fun getActiveNotificationsCount(): Int { + return 0 + } + + override fun dump( + fd: FileDescriptor, + pw: PrintWriter, + args: Array<String>, + dumpTruck: Boolean + ) { + pw.println() + pw.println("Notification handling disabled") + pw.println() + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt index 315ea0a49bb7..4f27c0f04c3f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt @@ -35,6 +35,7 @@ import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.Window import android.view.WindowInsets.Type +import android.view.WindowInsets.Type.statusBars import android.view.WindowManager import android.widget.TextView import com.android.internal.annotations.VisibleForTesting @@ -288,13 +289,13 @@ class ChannelEditorDialogController @Inject constructor( setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) addFlags(wmFlags) setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL) - setFitWindowInsetsTypes(getFitWindowInsetsTypes() and Type.statusBars().inv()) setWindowAnimations(com.android.internal.R.style.Animation_InputMethod) attributes = attributes.apply { format = PixelFormat.TRANSLUCENT title = ChannelEditorDialogController::class.java.simpleName gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL + fitInsetsTypes = attributes.fitInsetsTypes and statusBars().inv() width = MATCH_PARENT height = WRAP_CONTENT } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java index 6aadcb7b30e1..049cafa4ccde 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java @@ -92,10 +92,8 @@ public abstract class ExpandableOutlineView extends ExpandableView { outline.setRect(left, top, right, bottom); } else { Path clipPath = getClipPath(false /* ignoreTranslation */); - if (clipPath != null && clipPath.isConvex()) { - // The path might not be convex in border cases where the view is small and - // clipped - outline.setConvexPath(clipPath); + if (clipPath != null) { + outline.setPath(clipPath); } } outline.setAlpha(mOutlineAlpha); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java index 18c755da53ff..6045524f30b6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java @@ -29,6 +29,7 @@ import static com.android.systemui.statusbar.notification.row.NotificationConver import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_HOME; import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_MUTE; import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_SNOOZE; +import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_UNBUBBLE; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -121,7 +122,7 @@ public class NotificationConversationInfo extends LinearLayout implements boolean mSkipPost = false; private OnClickListener mOnBubbleClick = v -> { - mSelectedAction = ACTION_BUBBLE; + mSelectedAction = mStartedAsBubble ? ACTION_UNBUBBLE : ACTION_BUBBLE; if (mStartedAsBubble) { mBubbleController.onUserDemotedBubbleFromNotification(mEntry); } else { @@ -586,6 +587,7 @@ public class NotificationConversationInfo extends LinearLayout implements static final int ACTION_SNOOZE = 3; static final int ACTION_MUTE = 4; static final int ACTION_DEMOTE = 5; + static final int ACTION_UNBUBBLE = 6; private final INotificationManager mINotificationManager; private final String mAppPkg; @@ -606,9 +608,17 @@ public class NotificationConversationInfo extends LinearLayout implements @Override public void run() { try { + boolean channelSettingChanged = mAction != ACTION_HOME && mAction != ACTION_SNOOZE; switch (mAction) { case ACTION_BUBBLE: - mChannelToUpdate.setAllowBubbles(!mChannelToUpdate.canBubble()); + case ACTION_UNBUBBLE: + boolean canBubble = mAction == ACTION_BUBBLE; + if (mChannelToUpdate.canBubble() != canBubble) { + channelSettingChanged = true; + mChannelToUpdate.setAllowBubbles(canBubble); + } else { + channelSettingChanged = false; + } break; case ACTION_FAVORITE: // TODO: extend beyond DND @@ -629,7 +639,7 @@ public class NotificationConversationInfo extends LinearLayout implements } - if (mAction != ACTION_HOME && mAction != ACTION_SNOOZE) { + if (channelSettingChanged) { mINotificationManager.updateNotificationChannelForPackage( mAppPkg, mAppUid, mChannelToUpdate); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java index a3e13053d169..fa4bc2aba21a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java @@ -80,7 +80,15 @@ public class NotificationInlineImageResolver implements ImageResolver { public Drawable loadImage(Uri uri) { Drawable result = null; try { - result = hasCache() ? mImageCache.get(uri) : resolveImage(uri); + if (hasCache()) { + // if the uri isn't currently cached, try caching it first + if (!mImageCache.hasEntry(uri)) { + mImageCache.preload((uri)); + } + result = mImageCache.get(uri); + } else { + result = resolveImage(uri); + } } catch (IOException | SecurityException ex) { Log.d(TAG, "loadImage: Can't load image from " + uri, ex); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 11ead8bd2576..84a293eebc95 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -35,6 +35,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.Intent; +import android.content.pm.UserInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Canvas; @@ -56,6 +57,7 @@ import android.util.DisplayMetrics; import android.util.Log; import android.util.MathUtils; import android.util.Pair; +import android.util.SparseArray; import android.view.ContextThemeWrapper; import android.view.InputDevice; import android.view.LayoutInflater; @@ -99,6 +101,7 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DragDownHelper.DragDownCallback; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.RemoteInputController; @@ -328,6 +331,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd return true; } }; + private final UserChangedListener mLockscreenUserChangeListener = new UserChangedListener() { + @Override + public void onUserChanged(int userId) { + updateSensitiveness(false /* animated */); + } + }; private StatusBar mStatusBar; private int[] mTempInt2 = new int[2]; private boolean mGenerateChildOrderChangedEvent; @@ -561,6 +570,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd mRoundnessManager.setAnimatedChildren(mChildrenToAddAnimated); mRoundnessManager.setOnRoundingChangedCallback(this::invalidate); addOnExpandedHeightChangedListener(mRoundnessManager::setExpanded); + mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener); setOutlineProvider(mOutlineProvider); // Blocking helper manager wants to know the expanded state, update as well. @@ -4611,7 +4621,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - private void setHideSensitive(boolean hideSensitive, boolean animate) { + private void updateSensitiveness(boolean animate) { + boolean hideSensitive = mLockscreenUserManager.isAnyProfilePublicMode(); if (hideSensitive != mAmbientState.isHideSensitive()) { int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { @@ -5306,7 +5317,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd private void onStatePostChange() { boolean onKeyguard = onKeyguard(); - boolean publicMode = mLockscreenUserManager.isAnyProfilePublicMode(); if (mHeadsUpAppearanceController != null) { mHeadsUpAppearanceController.onStateChanged(); @@ -5314,7 +5324,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd SysuiStatusBarStateController state = (SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class); - setHideSensitive(publicMode, state.goingToFullShade() /* animate */); + updateSensitiveness(state.goingToFullShade() /* animate */); setDimmed(onKeyguard, state.fromShadeLocked() /* animate */); setExpandingEnabled(!onKeyguard); ActivatableNotificationView activatedChild = getActivatedChild(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java index afaa593b3bb9..e7d6eba1dcb3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java @@ -22,7 +22,6 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dependency; -import com.android.systemui.doze.DozeEvent; import com.android.systemui.doze.DozeHost; import com.android.systemui.doze.DozeLog; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -53,7 +52,7 @@ public class DozeScrimController implements StateListener { public void onDisplayBlanked() { if (DEBUG) { Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason=" - + DozeEvent.reasonToString(mPulseReason)); + + DozeLog.reasonToString(mPulseReason)); } if (!mDozing) { return; @@ -74,8 +73,8 @@ public class DozeScrimController implements StateListener { // Notifications should time out on their own. Pulses due to notifications should // instead be managed externally based off the notification's lifetime. // Dock also controls the time out by self. - if (mPulseReason != DozeEvent.PULSE_REASON_NOTIFICATION - && mPulseReason != DozeEvent.PULSE_REASON_DOCKING) { + if (mPulseReason != DozeLog.PULSE_REASON_NOTIFICATION + && mPulseReason != DozeLog.PULSE_REASON_DOCKING) { mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration()); mHandler.postDelayed(mPulseOutExtended, mDozeParameters.getPulseVisibleDurationExtended()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java index dcf7d9873ef9..02d6c2e7f758 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java @@ -31,7 +31,6 @@ import android.view.View; import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.assist.AssistManager; -import com.android.systemui.doze.DozeEvent; import com.android.systemui.doze.DozeHost; import com.android.systemui.doze.DozeLog; import com.android.systemui.doze.DozeReceiver; @@ -223,18 +222,18 @@ public final class DozeServiceHost implements DozeHost { @Override public void pulseWhileDozing(@NonNull PulseCallback callback, int reason) { - if (reason == DozeEvent.PULSE_REASON_SENSOR_LONG_PRESS) { + if (reason == DozeLog.PULSE_REASON_SENSOR_LONG_PRESS) { mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE, "com.android.systemui:LONG_PRESS"); mAssistManagerLazy.get().startAssist(new Bundle()); return; } - if (reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) { + if (reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) { mScrimController.setWakeLockScreenSensorActive(true); } - boolean passiveAuthInterrupt = reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN + boolean passiveAuthInterrupt = reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN && mWakeLockScreenPerformsAuth; // Set the state to pulsing, so ScrimController will know what to do once we ask it to // execute the transition. The pulse callback will then be invoked when the scrims @@ -334,7 +333,7 @@ public final class DozeServiceHost implements DozeHost { @Override public void extendPulse(int reason) { - if (reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) { + if (reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) { mScrimController.setWakeLockScreenSensorActive(true); } if (mDozeScrimController.isPulsing() && mHeadsUpManagerPhone.hasNotifications()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java index ef581db0b053..db692c8a8c89 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java @@ -54,6 +54,10 @@ import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.SysUiStatsLog; +import com.android.systemui.shared.tracing.ProtoTraceable; +import com.android.systemui.tracing.ProtoTracer; +import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto; +import com.android.systemui.tracing.nano.SystemUiTraceProto; import java.io.PrintWriter; import java.util.concurrent.Executor; @@ -62,7 +66,7 @@ import java.util.concurrent.Executor; * Utility class to handle edge swipes for back gesture */ public class EdgeBackGestureHandler implements DisplayListener, - PluginListener<NavigationEdgeBackPlugin> { + PluginListener<NavigationEdgeBackPlugin>, ProtoTraceable<SystemUiTraceProto> { private static final String TAG = "EdgeBackGestureHandler"; private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt( @@ -161,6 +165,7 @@ public class EdgeBackGestureHandler implements DisplayListener, mMainExecutor = context.getMainExecutor(); mOverviewProxyService = overviewProxyService; mPluginManager = pluginManager; + Dependency.get(ProtoTracer.class).add(this); // Reduce the default touch slop to ensure that we can intercept the gesture // before the app starts to react to it. @@ -304,7 +309,7 @@ public class EdgeBackGestureHandler implements DisplayListener, layoutParams.setTitle(TAG + mContext.getDisplayId()); layoutParams.accessibilityTitle = mContext.getString(R.string.nav_bar_edge_panel); layoutParams.windowAnimations = 0; - layoutParams.setFitWindowInsetsTypes(0 /* types */); + layoutParams.setFitInsetsTypes(0 /* types */); return layoutParams; } @@ -399,6 +404,8 @@ public class EdgeBackGestureHandler implements DisplayListener, // forward touch mEdgeBackPlugin.onMotionEvent(ev); } + + Dependency.get(ProtoTracer.class).update(); } @Override @@ -458,6 +465,14 @@ public class EdgeBackGestureHandler implements DisplayListener, pw.println(" mEdgeWidth=" + mEdgeWidth); } + @Override + public void writeToProto(SystemUiTraceProto proto) { + if (proto.edgeBackGestureHandler == null) { + proto.edgeBackGestureHandler = new EdgeBackGestureHandlerProto(); + } + proto.edgeBackGestureHandler.allowGesture = mAllowGesture; + } + class SysUiInputEventReceiver extends InputEventReceiver { SysUiInputEventReceiver(InputChannel channel, Looper looper) { super(channel, looper); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java index 783e7adf2a8b..16b5a2389ec6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java @@ -84,7 +84,7 @@ public class FloatingRotationButton implements RotationButton { PixelFormat.TRANSLUCENT); lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; lp.setTitle("FloatingRotationButton"); - lp.setFitWindowInsetsTypes(0 /*types */); + lp.setFitInsetsTypes(0 /*types */); switch (mWindowManager.getDefaultDisplay().getRotation()) { case Surface.ROTATION_0: lp.gravity = Gravity.BOTTOM | Gravity.LEFT; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt index ad1aa8370495..b4d0d479ff39 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt @@ -33,7 +33,7 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -class KeyguardBypassController : Dumpable { +open class KeyguardBypassController : Dumpable { private val mKeyguardStateController: KeyguardStateController private val statusBarStateController: StatusBarStateController diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NPVPluginManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NPVPluginManager.kt deleted file mode 100644 index 53601babfd56..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NPVPluginManager.kt +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.phone - -import android.content.Context -import android.view.View -import android.view.ViewGroup.MarginLayoutParams -import android.widget.FrameLayout -import com.android.systemui.plugins.NPVPlugin -import com.android.systemui.plugins.PluginListener -import com.android.systemui.qs.TouchAnimator -import com.android.systemui.shared.plugins.PluginManager - -/** - * Manages the NPVPlugin view and state - * - * Abstracts NPVPlugin from NPV and helps animate on expansion and respond to changes in Config. - */ -class NPVPluginManager( - var parent: FrameLayout, - val pluginManager: PluginManager -) : PluginListener<NPVPlugin> { - - private var plugin: NPVPlugin? = null - private var animator = createAnimator() - private var yOffset = 0f - - private fun createAnimator() = TouchAnimator.Builder() - .addFloat(parent, "alpha", 1f, 0f) - .addFloat(parent, "scaleY", 1f, 0f) - .build() - - init { - pluginManager.addPluginListener(NPVPlugin.ACTION, this, NPVPlugin::class.java, false) - parent.pivotY = 0f - } - - override fun onPluginConnected(plugin: NPVPlugin, pluginContext: Context) { - parent.removeAllViews() - plugin.attachToRoot(parent) - this.plugin = plugin - parent.visibility = View.VISIBLE - } - - fun changeVisibility(visibility: Int) { - parent.visibility = if (plugin != null) visibility else View.GONE - } - - fun destroy() { - plugin?.onDestroy() - pluginManager.removePluginListener(this) - } - - override fun onPluginDisconnected(plugin: NPVPlugin) { - if (this.plugin == plugin) { - this.plugin = null - parent.removeAllViews() - parent.visibility = View.GONE - } - } - - fun setListening(listening: Boolean) { - plugin?.setListening(listening) - } - - fun setExpansion(expansion: Float, headerTranslation: Float, heightDiff: Float) { - parent.setTranslationY(expansion * heightDiff + headerTranslation + yOffset) - if (!expansion.isNaN()) animator.setPosition(expansion) - } - - fun replaceFrameLayout(newParent: FrameLayout) { - newParent.visibility = parent.visibility - parent.removeAllViews() - plugin?.attachToRoot(newParent) - parent = newParent - animator = createAnimator() - } - - fun getHeight() = - if (plugin != null) { - parent.height + (parent.getLayoutParams() as MarginLayoutParams).topMargin - } else 0 - - fun setYOffset(y: Float) { - yOffset = y - parent.setTranslationY(yOffset) - } -} 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 3e3ef0ccb8ce..9e64748f2e65 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -302,14 +302,14 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback mForceNavBarHandleOpaque = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_SYSTEMUI, NAV_BAR_HANDLE_FORCE_OPAQUE, - /* defaultValue = */ false); + /* defaultValue = */ true); DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post, new DeviceConfig.OnPropertiesChangedListener() { @Override public void onPropertiesChanged(DeviceConfig.Properties properties) { if (properties.getKeyset().contains(NAV_BAR_HANDLE_FORCE_OPAQUE)) { mForceNavBarHandleOpaque = properties.getBoolean( - NAV_BAR_HANDLE_FORCE_OPAQUE, /* defaultValue = */ false); + NAV_BAR_HANDLE_FORCE_OPAQUE, /* defaultValue = */ true); } } }); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java index d6336ed3e18a..826af669cb39 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java @@ -387,7 +387,7 @@ public class NavigationModeController implements Dumpable { Log.d(TAG, "setModeOverlay: overlayPackage=" + overlayPkg + " userId=" + userId); } - } catch (RemoteException e) { + } catch (SecurityException | IllegalStateException | RemoteException e) { Log.e(TAG, "Failed to enable overlay " + overlayPkg + " for user " + userId); } }); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java index 8c947edd9a83..77337e95bb95 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java @@ -204,8 +204,8 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State } int childCount = 0; boolean hasBubbles = false; - for (String key : group.children.keySet()) { - if (!getBubbleController().isBubbleNotificationSuppressedFromShade(key)) { + for (NotificationEntry entry : group.children.values()) { + if (!getBubbleController().isBubbleNotificationSuppressedFromShade(entry)) { childCount++; } else { hasBubbles = true; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java index 707138ee8dc0..b09ccffdc4de 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -1,5 +1,6 @@ package com.android.systemui.statusbar.phone; +import android.app.NotificationManager; import android.content.Context; import android.content.res.Resources; import android.graphics.Color; @@ -10,6 +11,7 @@ import android.view.ViewGroup; import android.widget.FrameLayout; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import androidx.collection.ArrayMap; import com.android.internal.statusbar.StatusBarIcon; @@ -21,6 +23,7 @@ import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CrossFadeHelper; +import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StatusBarIconView; @@ -76,6 +79,19 @@ public class NotificationIconAreaController implements DarkReceiver, private boolean mFullyHidden; private boolean mAodIconsVisible; private boolean mIsPulsing; + private boolean mShowLowPriority = true; + + @VisibleForTesting + final NotificationListener.NotificationSettingsListener mSettingsListener = + new NotificationListener.NotificationSettingsListener() { + @Override + public void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons) { + mShowLowPriority = !hideSilentStatusIcons; + if (mNotificationScrollLayout != null) { + updateStatusBarIcons(); + } + } + }; public NotificationIconAreaController( Context context, @@ -84,6 +100,7 @@ public class NotificationIconAreaController implements DarkReceiver, NotificationWakeUpCoordinator wakeUpCoordinator, KeyguardBypassController keyguardBypassController, NotificationMediaManager notificationMediaManager, + NotificationListener notificationListener, DozeParameters dozeParameters) { mStatusBar = statusBar; mContrastColorUtil = ContrastColorUtil.getInstance(context); @@ -95,6 +112,7 @@ public class NotificationIconAreaController implements DarkReceiver, mWakeUpCoordinator = wakeUpCoordinator; wakeUpCoordinator.addListener(this); mBypassController = keyguardBypassController; + notificationListener.addNotificationSettingsListener(mSettingsListener); initializeNotificationAreaViews(context); reloadAodColor(); @@ -230,7 +248,7 @@ public class NotificationIconAreaController implements DarkReceiver, } protected boolean shouldShowNotificationIcon(NotificationEntry entry, - boolean showAmbient, boolean hideDismissed, + boolean showAmbient, boolean showLowPriority, boolean hideDismissed, boolean hideRepliedMessages, boolean hideCurrentMedia, boolean hideCenteredIcon, boolean hidePulsing, boolean onlyShowCenteredIcon) { @@ -249,6 +267,9 @@ public class NotificationIconAreaController implements DarkReceiver, if (hideCurrentMedia && entry.getKey().equals(mMediaManager.getMediaNotificationKey())) { return false; } + if (!showLowPriority && entry.getImportance() < NotificationManager.IMPORTANCE_DEFAULT) { + return false; + } if (!entry.isTopLevelChild()) { return false; } @@ -288,6 +309,7 @@ public class NotificationIconAreaController implements DarkReceiver, private void updateShelfIcons() { updateIconsForLayout(entry -> entry.expandedIcon, mShelfIcons, true /* showAmbient */, + true /* showLowPriority */, false /* hideDismissed */, false /* hideRepliedMessages */, false /* hideCurrentMedia */, @@ -299,6 +321,7 @@ public class NotificationIconAreaController implements DarkReceiver, public void updateStatusBarIcons() { updateIconsForLayout(entry -> entry.icon, mNotificationIcons, false /* showAmbient */, + mShowLowPriority, true /* hideDismissed */, true /* hideRepliedMessages */, false /* hideCurrentMedia */, @@ -310,6 +333,7 @@ public class NotificationIconAreaController implements DarkReceiver, private void updateCenterIcon() { updateIconsForLayout(entry -> entry.centeredIcon, mCenteredIcon, false /* showAmbient */, + true /* showLowPriority */, false /* hideDismissed */, false /* hideRepliedMessages */, false /* hideCurrentMedia */, @@ -321,6 +345,7 @@ public class NotificationIconAreaController implements DarkReceiver, public void updateAodNotificationIcons() { updateIconsForLayout(entry -> entry.aodIcon, mAodIcons, false /* showAmbient */, + true /* showLowPriority */, true /* hideDismissed */, true /* hideRepliedMessages */, true /* hideCurrentMedia */, @@ -329,18 +354,24 @@ public class NotificationIconAreaController implements DarkReceiver, false /* onlyShowCenteredIcon */); } + @VisibleForTesting + boolean shouldShouldLowPriorityIcons() { + return mShowLowPriority; + } + /** * Updates the notification icons for a host layout. This will ensure that the notification * host layout will have the same icons like the ones in here. * @param function A function to look up an icon view based on an entry * @param hostLayout which layout should be updated * @param showAmbient should ambient notification icons be shown + * @param showLowPriority should icons from silent notifications be shown * @param hideDismissed should dismissed icons be hidden * @param hideRepliedMessages should messages that have been replied to be hidden * @param hidePulsing should pulsing notifications be hidden */ private void updateIconsForLayout(Function<NotificationEntry, StatusBarIconView> function, - NotificationIconContainer hostLayout, boolean showAmbient, + NotificationIconContainer hostLayout, boolean showAmbient, boolean showLowPriority, boolean hideDismissed, boolean hideRepliedMessages, boolean hideCurrentMedia, boolean hideCenteredIcon, boolean hidePulsing, boolean onlyShowCenteredIcon) { ArrayList<StatusBarIconView> toShow = new ArrayList<>( @@ -351,7 +382,7 @@ public class NotificationIconAreaController implements DarkReceiver, View view = mNotificationScrollLayout.getChildAt(i); if (view instanceof ExpandableNotificationRow) { NotificationEntry ent = ((ExpandableNotificationRow) view).getEntry(); - if (shouldShowNotificationIcon(ent, showAmbient, hideDismissed, + if (shouldShowNotificationIcon(ent, showAmbient, showLowPriority, hideDismissed, hideRepliedMessages, hideCurrentMedia, hideCenteredIcon, hidePulsing, onlyShowCenteredIcon)) { StatusBarIconView iconView = function.apply(ent); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 6112ae88f634..d2186f959aba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -25,10 +25,8 @@ import android.animation.ValueAnimator; import android.app.ActivityManager; import android.app.Fragment; import android.app.StatusBarManager; -import android.content.Context; import android.content.pm.ResolveInfo; import android.content.res.Configuration; -import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; @@ -40,8 +38,6 @@ import android.graphics.drawable.Drawable; import android.hardware.biometrics.BiometricSourceType; import android.os.PowerManager; import android.os.SystemClock; -import android.provider.DeviceConfig; -import android.provider.Settings; import android.util.Log; import android.util.MathUtils; import android.view.LayoutInflater; @@ -57,7 +53,6 @@ import android.widget.FrameLayout; import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.LatencyTracker; @@ -73,13 +68,10 @@ import com.android.systemui.doze.DozeLog; import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.fragments.FragmentHostManager.FragmentListener; import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.HomeControlsPlugin; -import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.qs.QSFragment; -import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.statusbar.GestureRecorder; @@ -113,7 +105,6 @@ import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.util.InjectionInflationController; -import com.android.systemui.util.Utils; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -173,8 +164,6 @@ public class NotificationPanelViewController extends PanelViewController { private final ConfigurationController mConfigurationController; private final FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder; - private double mQqsSplitFraction; - // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is // changed. private static final int CAP_HEIGHT = 1456; @@ -258,7 +247,6 @@ public class NotificationPanelViewController extends PanelViewController { private View mQsNavbarScrim; private NotificationsQuickSettingsContainer mNotificationContainerParent; private NotificationStackScrollLayout mNotificationStackScroller; - private FrameLayout mHomeControlsLayout; private boolean mAnimateNextPositionUpdate; private int mTrackingPointer; @@ -446,9 +434,6 @@ public class NotificationPanelViewController extends PanelViewController { */ private boolean mDelayShowingKeyguardStatusBar; - private PluginManager mPluginManager; - private FrameLayout mPluginFrame; - private NPVPluginManager mNPVPluginManager; private int mOldLayoutDirection; @Inject @@ -457,7 +442,7 @@ public class NotificationPanelViewController extends PanelViewController { NotificationWakeUpCoordinator coordinator, PulseExpansionHandler pulseExpansionHandler, DynamicPrivacyController dynamicPrivacyController, KeyguardBypassController bypassController, FalsingManager falsingManager, - PluginManager pluginManager, ShadeController shadeController, + ShadeController shadeController, NotificationLockscreenUserManager notificationLockscreenUserManager, NotificationEntryManager notificationEntryManager, KeyguardStateController keyguardStateController, @@ -523,7 +508,6 @@ public class NotificationPanelViewController extends PanelViewController { }); mBottomAreaShadeAlphaAnimator.setDuration(160); mBottomAreaShadeAlphaAnimator.setInterpolator(Interpolators.ALPHA_OUT); - mPluginManager = pluginManager; mShadeController = shadeController; mLockscreenUserManager = notificationLockscreenUserManager; mEntryManager = notificationEntryManager; @@ -553,7 +537,6 @@ public class NotificationPanelViewController extends PanelViewController { mBigClockContainer = mView.findViewById(R.id.big_clock_container); keyguardClockSwitch.setBigClockContainer(mBigClockContainer); - mHomeControlsLayout = mView.findViewById(R.id.home_controls_layout); mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent); mNotificationStackScroller = mView.findViewById(R.id.notification_stack_scroller); mNotificationStackScroller.setOnHeightChangedListener(mOnHeightChangedListener); @@ -563,12 +546,6 @@ public class NotificationPanelViewController extends PanelViewController { mKeyguardBottomArea = mView.findViewById(R.id.keyguard_bottom_area); mQsNavbarScrim = mView.findViewById(R.id.qs_navbar_scrim); mLastOrientation = mResources.getConfiguration().orientation; - mPluginFrame = mView.findViewById(R.id.plugin_frame); - if (Settings.System.getInt(mView.getContext().getContentResolver(), "npv_plugin_flag", 0) - == 1) { - mNPVPluginManager = new NPVPluginManager(mPluginFrame, mPluginManager); - } - initBottomArea(); @@ -592,19 +569,6 @@ public class NotificationPanelViewController extends PanelViewController { } }); - mPluginManager.addPluginListener(new PluginListener<HomeControlsPlugin>() { - - @Override - public void onPluginConnected(HomeControlsPlugin plugin, Context pluginContext) { - plugin.sendParentGroup(mHomeControlsLayout); - } - - @Override - public void onPluginDisconnected(HomeControlsPlugin plugin) { - - } - }, HomeControlsPlugin.class, false); - mView.setRtlChangeListener(layoutDirection -> { if (layoutDirection != mOldLayoutDirection) { mAffordanceHelper.onRtlPropertiesChanged(); @@ -637,9 +601,6 @@ public class NotificationPanelViewController extends PanelViewController { com.android.internal.R.dimen.status_bar_height); mHeadsUpInset = statusbarHeight + mResources.getDimensionPixelSize( R.dimen.heads_up_status_bar_padding); - mQqsSplitFraction = ((float) mResources.getInteger(R.integer.qqs_split_fraction)) / ( - mResources.getInteger(R.integer.qqs_split_fraction) + mResources.getInteger( - R.integer.qs_split_fraction)); } /** @@ -679,18 +640,6 @@ public class NotificationPanelViewController extends PanelViewController { lp.gravity = panelGravity; mNotificationStackScroller.setLayoutParams(lp); } - int sideMargin = mResources.getDimensionPixelOffset(R.dimen.notification_side_paddings); - int topMargin = sideMargin; - lp = (FrameLayout.LayoutParams) mPluginFrame.getLayoutParams(); - if (lp.width != qsWidth || lp.gravity != panelGravity || lp.leftMargin != sideMargin - || lp.rightMargin != sideMargin || lp.topMargin != topMargin) { - lp.width = qsWidth; - lp.gravity = panelGravity; - lp.leftMargin = sideMargin; - lp.rightMargin = sideMargin; - lp.topMargin = topMargin; - mPluginFrame.setLayoutParams(lp); - } } private void reInflateViews() { @@ -732,41 +681,6 @@ public class NotificationPanelViewController extends PanelViewController { if (mOnReinflationListener != null) { mOnReinflationListener.run(); } - reinflatePluginContainer(); - } - - private void reinflatePluginContainer() { - int index = mView.indexOfChild(mPluginFrame); - mView.removeView(mPluginFrame); - mPluginFrame = (FrameLayout) mInjectionInflationController.injectable( - LayoutInflater.from(mView.getContext())).inflate( - R.layout.status_bar_expanded_plugin_frame, mView, false); - mView.addView(mPluginFrame, index); - - Resources res = mView.getResources(); - int qsWidth = res.getDimensionPixelSize(R.dimen.qs_panel_width); - int panelGravity = mView.getResources().getInteger( - R.integer.notification_panel_layout_gravity); - FrameLayout.LayoutParams lp; - int sideMargin = res.getDimensionPixelOffset(R.dimen.notification_side_paddings); - int topMargin = res.getDimensionPixelOffset( - com.android.internal.R.dimen.quick_qs_total_height); - if (Utils.useQsMediaPlayer(mView.getContext())) { - topMargin = res.getDimensionPixelOffset( - com.android.internal.R.dimen.quick_qs_total_height_with_media); - } - lp = (FrameLayout.LayoutParams) mPluginFrame.getLayoutParams(); - if (lp.width != qsWidth || lp.gravity != panelGravity || lp.leftMargin != sideMargin - || lp.rightMargin != sideMargin || lp.topMargin != topMargin) { - lp.width = qsWidth; - lp.gravity = panelGravity; - lp.leftMargin = sideMargin; - lp.rightMargin = sideMargin; - lp.topMargin = topMargin; - mPluginFrame.setLayoutParams(lp); - } - - if (mNPVPluginManager != null) mNPVPluginManager.replaceFrameLayout(mPluginFrame); } private void initBottomArea() { @@ -1266,17 +1180,6 @@ public class NotificationPanelViewController extends PanelViewController { // earlier so the state is already up to date when dragging down. setListening(true); } - if (isQsSplitEnabled() && !mKeyguardShowing) { - if (mQsExpandImmediate) { - mNotificationStackScroller.setVisibility(View.GONE); - mQsFrame.setVisibility(View.VISIBLE); - mHomeControlsLayout.setVisibility(View.VISIBLE); - } else { - mNotificationStackScroller.setVisibility(View.VISIBLE); - mQsFrame.setVisibility(View.GONE); - mHomeControlsLayout.setVisibility(View.GONE); - } - } return false; } @@ -1286,17 +1189,6 @@ public class NotificationPanelViewController extends PanelViewController { || y <= mQs.getView().getY() + mQs.getView().getHeight()); } - private boolean isOnQsEndArea(float x) { - if (!isQsSplitEnabled()) return false; - if (mView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR) { - return x >= mQsFrame.getX() + mQqsSplitFraction * mQsFrame.getWidth() - && x <= mQsFrame.getX() + mQsFrame.getWidth(); - } else { - return x >= mQsFrame.getX() - && x <= mQsFrame.getX() + (1 - mQqsSplitFraction) * mQsFrame.getWidth(); - } - } - private boolean isOpenQsEvent(MotionEvent event) { final int pointerCount = event.getPointerCount(); final int action = event.getActionMasked(); @@ -1317,9 +1209,7 @@ public class NotificationPanelViewController extends PanelViewController { MotionEvent.BUTTON_SECONDARY) || event.isButtonPressed( MotionEvent.BUTTON_TERTIARY)); - final boolean onHeaderRight = isOnQsEndArea(event.getX()); - - return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag || onHeaderRight; + return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag; } private void handleQsDown(MotionEvent event) { @@ -1659,10 +1549,7 @@ public class NotificationPanelViewController extends PanelViewController { mBarState != StatusBarState.KEYGUARD && (!mQsExpanded || mQsExpansionFromOverscroll)); updateEmptyShadeView(); - if (mNPVPluginManager != null) { - mNPVPluginManager.changeVisibility( - (mBarState != StatusBarState.KEYGUARD) ? View.VISIBLE : View.INVISIBLE); - } + mQsNavbarScrim.setVisibility( mBarState == StatusBarState.SHADE && mQsExpanded && !mStackScrollerOverscrolling && mQsScrimEnabled ? View.VISIBLE : View.INVISIBLE); @@ -1718,9 +1605,6 @@ public class NotificationPanelViewController extends PanelViewController { float qsExpansionFraction = getQsExpansionFraction(); mQs.setQsExpansion(qsExpansionFraction, getHeaderTranslation()); int heightDiff = mQs.getDesiredHeight() - mQs.getQsMinExpansionHeight(); - if (mNPVPluginManager != null) { - mNPVPluginManager.setExpansion(qsExpansionFraction, getHeaderTranslation(), heightDiff); - } mNotificationStackScroller.setQsExpansionFraction(qsExpansionFraction); } @@ -2015,7 +1899,6 @@ public class NotificationPanelViewController extends PanelViewController { targetHeight = mQsMinExpansionHeight + t * (mQsMaxExpansionHeight - mQsMinExpansionHeight); setQsExpansion(targetHeight); - mHomeControlsLayout.setTranslationY(targetHeight); } updateExpandedHeight(expandedHeight); updateHeader(); @@ -2150,7 +2033,6 @@ public class NotificationPanelViewController extends PanelViewController { appearAmount = mNotificationStackScroller.calculateAppearFractionBypass(); } startHeight = -mQs.getQsMinExpansionHeight(); - if (mNPVPluginManager != null) startHeight -= mNPVPluginManager.getHeight(); } float translation = MathUtils.lerp(startHeight, 0, Math.min(1.0f, appearAmount)) + mExpandOffset; @@ -2294,7 +2176,6 @@ public class NotificationPanelViewController extends PanelViewController { mKeyguardStatusBar.setListening(listening); if (mQs == null) return; mQs.setListening(listening); - if (mNPVPluginManager != null) mNPVPluginManager.setListening(listening); } @Override @@ -3029,11 +2910,6 @@ public class NotificationPanelViewController extends PanelViewController { mOnReinflationListener = onReinflationListener; } - public static boolean isQsSplitEnabled() { - return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.QS_SPLIT_ENABLED, false); - } - public void setAlpha(float alpha) { mView.setAlpha(alpha); } @@ -3500,9 +3376,7 @@ public class NotificationPanelViewController extends PanelViewController { } @Override - public void onUiModeChanged() { - reinflatePluginContainer(); - } + public void onUiModeChanged() {} } private class StatusBarStateListener implements StateListener { @@ -3517,11 +3391,6 @@ public class NotificationPanelViewController extends PanelViewController { mBarState = statusBarState; mKeyguardShowing = keyguardShowing; - if (mKeyguardShowing && isQsSplitEnabled()) { - mNotificationStackScroller.setVisibility(View.VISIBLE); - mQsFrame.setVisibility(View.VISIBLE); - mHomeControlsLayout.setVisibility(View.GONE); - } if (oldState == StatusBarState.KEYGUARD && (goingToFullShade || statusBarState == StatusBarState.SHADE_LOCKED)) { @@ -3544,7 +3413,6 @@ public class NotificationPanelViewController extends PanelViewController { } else { mKeyguardStatusBar.setAlpha(1f); mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE); - ((PhoneStatusBarView) mBar).maybeShowDivider(keyguardShowing); if (keyguardShowing && oldState != mBarState) { if (mQs != null) { mQs.hideImmediately(); @@ -3622,10 +3490,6 @@ public class NotificationPanelViewController extends PanelViewController { int oldMaxHeight = mQsMaxExpansionHeight; if (mQs != null) { mQsMinExpansionHeight = mKeyguardShowing ? 0 : mQs.getQsMinExpansionHeight(); - if (mNPVPluginManager != null) { - mNPVPluginManager.setYOffset(mQsMinExpansionHeight); - mQsMinExpansionHeight += mNPVPluginManager.getHeight(); - } mQsMaxExpansionHeight = mQs.getDesiredHeight(); mNotificationStackScroller.setMaxTopPadding( mQsMaxExpansionHeight + mQsNotificationTopPadding); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java index fe4879b0b071..10b68b9a518d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java @@ -16,8 +16,10 @@ package com.android.systemui.statusbar.phone; +import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED; import static com.android.systemui.DejankUtils.whitelistIpcs; import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; @@ -175,11 +177,18 @@ public class NotificationShadeWindowController implements Callback, Dumpable, PixelFormat.TRANSLUCENT); mLp.token = new Binder(); mLp.gravity = Gravity.TOP; - mLp.setFitWindowInsetsTypes(0 /* types */); + mLp.setFitInsetsTypes(0 /* types */); mLp.softInputMode = LayoutParams.SOFT_INPUT_ADJUST_RESIZE; mLp.setTitle("NotificationShade"); mLp.packageName = mContext.getPackageName(); mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + + // We use BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE here, however, there is special logic in + // window manager which disables the transient show behavior. + // TODO: Clean this up once that behavior moves into the Shell. + mLp.privateFlags |= PRIVATE_FLAG_BEHAVIOR_CONTROLLED; + mLp.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; + mWindowManager.addView(mNotificationShadeView, mLp); mLpChanged.copyFrom(mLp); onThemeChanged(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java index 6979554303b3..7650a3ab3a4e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.phone; +import static android.view.WindowInsets.Type.systemBars; + import android.annotation.ColorInt; import android.annotation.DrawableRes; import android.annotation.LayoutRes; @@ -81,7 +83,7 @@ public class NotificationShadeWindowView extends FrameLayout { @Override public WindowInsets onApplyWindowInsets(WindowInsets windowInsets) { - final Insets insets = windowInsets.getMaxInsets(WindowInsets.Type.systemBars()); + final Insets insets = windowInsets.getInsetsIgnoringVisibility(systemBars()); if (getFitsSystemWindows()) { boolean paddingChanged = insets.top != getPaddingTop() || insets.bottom != getPaddingBottom(); 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 9522d3574e17..98188bd4e0ba 100755 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -38,7 +38,6 @@ import android.telephony.TelephonyManager; import android.text.format.DateFormat; import android.util.Log; -import com.android.internal.telephony.TelephonyIntents; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -176,7 +175,7 @@ public class PhoneStatusBarPolicy filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION); filter.addAction(AudioManager.ACTION_HEADSET_PLUG); - filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED); + filter.addAction(Intent.ACTION_SIM_STATE_CHANGED); filter.addAction(TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED); filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); @@ -613,7 +612,7 @@ public class PhoneStatusBarPolicy case AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION: updateVolumeZen(); break; - case TelephonyIntents.ACTION_SIM_STATE_CHANGED: + case Intent.ACTION_SIM_STATE_CHANGED: // Avoid rebroadcast because SysUI is direct boot aware. if (intent.getBooleanExtra(Intent.EXTRA_REBROADCAST_ON_UNLOCK, false)) { break; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java index 96b4b22d0580..e8bc2f58adb4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java @@ -32,7 +32,7 @@ public final class PhoneStatusBarTransitions extends BarTransitions { private final PhoneStatusBarView mView; private final float mIconAlphaWhenOpaque; - private View mLeftSide, mStatusIcons, mBattery, mClock, mDivider; + private View mLeftSide, mStatusIcons, mBattery, mClock; private Animator mCurrentAnimation; public PhoneStatusBarTransitions(PhoneStatusBarView view) { @@ -46,7 +46,6 @@ public final class PhoneStatusBarTransitions extends BarTransitions { mLeftSide = mView.findViewById(R.id.status_bar_left_side); mStatusIcons = mView.findViewById(R.id.statusIcons); mBattery = mView.findViewById(R.id.battery); - mDivider = mView.findViewById(R.id.divider); applyModeBackground(-1, getMode(), false /*animate*/); applyMode(getMode(), false /*animate*/); } @@ -89,7 +88,6 @@ public final class PhoneStatusBarTransitions extends BarTransitions { anims.playTogether( animateTransitionTo(mLeftSide, newAlpha), animateTransitionTo(mStatusIcons, newAlpha), - animateTransitionTo(mDivider, newAlpha), animateTransitionTo(mBattery, newAlphaBC) ); if (isLightsOut(mode)) { @@ -100,8 +98,7 @@ public final class PhoneStatusBarTransitions extends BarTransitions { } else { mLeftSide.setAlpha(newAlpha); mStatusIcons.setAlpha(newAlpha); - mDivider.setAlpha(newAlpha); mBattery.setAlpha(newAlphaBC); } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index ffbbffc0d8d9..f3b0a79f9518 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -25,7 +25,6 @@ import android.content.Context; import android.content.res.Configuration; import android.graphics.Point; import android.graphics.Rect; -import android.provider.DeviceConfig; import android.util.AttributeSet; import android.util.EventLog; import android.util.Pair; @@ -40,8 +39,6 @@ import android.view.accessibility.AccessibilityEvent; import android.widget.FrameLayout; import android.widget.LinearLayout; -import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; -import com.android.systemui.DarkReceiverImpl; import com.android.systemui.Dependency; import com.android.systemui.EventLogTags; import com.android.systemui.R; @@ -81,9 +78,6 @@ public class PhoneStatusBarView extends PanelBar { @Nullable private DisplayCutout mDisplayCutout; - private DarkReceiverImpl mSplitDivider; - private View mDividerContainer; - private QsSplitPropertyListener mPropertyListener; /** * Draw this many pixels into the left/right side of the cutout to optimally use the space */ @@ -115,10 +109,6 @@ public class PhoneStatusBarView extends PanelBar { mBattery = findViewById(R.id.battery); mCutoutSpace = findViewById(R.id.cutout_space_view); mCenterIconSpace = findViewById(R.id.centered_icon_area); - mSplitDivider = findViewById(R.id.divider); - mDividerContainer = findViewById(R.id.divider_container); - maybeShowDivider(true); - mPropertyListener = new QsSplitPropertyListener(mDividerContainer); updateResources(); } @@ -128,26 +118,16 @@ public class PhoneStatusBarView extends PanelBar { super.onAttachedToWindow(); // Always have Battery meters in the status bar observe the dark/light modes. Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mBattery); - Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mSplitDivider); - maybeShowDivider(true); if (updateOrientationAndCutout(getResources().getConfiguration().orientation)) { updateLayoutForCutout(); } - if (mPropertyListener != null) { - DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, - mContext.getMainExecutor(), mPropertyListener); - } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(mBattery); - Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(mSplitDivider); mDisplayCutout = null; - if (mPropertyListener != null) { - DeviceConfig.removeOnPropertiesChangedListener(mPropertyListener); - } } @Override @@ -216,7 +196,6 @@ public class PhoneStatusBarView extends PanelBar { public void onPanelPeeked() { super.onPanelPeeked(); mBar.makeExpandedVisible(false); - maybeShowDivider(!mBar.mPanelExpanded); } @Override @@ -225,7 +204,6 @@ public class PhoneStatusBarView extends PanelBar { // Close the status bar in the next frame so we can show the end of the animation. post(mHideExpandedRunnable); mIsFullyOpenedPanel = false; - maybeShowDivider(!mBar.mPanelExpanded); } public void removePendingHideExpandedRunnables() { @@ -239,7 +217,6 @@ public class PhoneStatusBarView extends PanelBar { mPanel.getView().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); } mIsFullyOpenedPanel = true; - maybeShowDivider(!mBar.mPanelExpanded); } @Override @@ -263,28 +240,24 @@ public class PhoneStatusBarView extends PanelBar { mBar.onTrackingStarted(); mScrimController.onTrackingStarted(); removePendingHideExpandedRunnables(); - maybeShowDivider(!mBar.mPanelExpanded); } @Override public void onClosingFinished() { super.onClosingFinished(); mBar.onClosingFinished(); - maybeShowDivider(!mBar.mPanelExpanded); } @Override public void onTrackingStopped(boolean expand) { super.onTrackingStopped(expand); mBar.onTrackingStopped(expand); - maybeShowDivider(!mBar.mPanelExpanded); } @Override public void onExpandingFinished() { super.onExpandingFinished(); mScrimController.onExpandingFinished(); - maybeShowDivider(!mBar.mPanelExpanded); } @Override @@ -416,31 +389,4 @@ public class PhoneStatusBarView extends PanelBar { protected boolean shouldPanelBeVisible() { return mHeadsUpVisible || super.shouldPanelBeVisible(); } - - void maybeShowDivider(boolean showDivider) { - int state = - showDivider && NotificationPanelViewController.isQsSplitEnabled() - ? View.VISIBLE : View.GONE; - mDividerContainer.setVisibility(state); - } - - private static class QsSplitPropertyListener implements - DeviceConfig.OnPropertiesChangedListener { - private final View mDivider; - - QsSplitPropertyListener(View divider) { - mDivider = divider; - } - - @Override - public void onPropertiesChanged(DeviceConfig.Properties properties) { - if (properties.getNamespace().equals(DeviceConfig.NAMESPACE_SYSTEMUI) - && properties.getKeyset().contains( - SystemUiDeviceConfigFlags.QS_SPLIT_ENABLED)) { - boolean splitEnabled = properties.getBoolean( - SystemUiDeviceConfigFlags.QS_SPLIT_ENABLED, false); - mDivider.setVisibility(splitEnabled ? VISIBLE : GONE); - } - } - } } 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 832005210353..dc502d86ee45 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -144,7 +144,6 @@ import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.fragments.ExtensionFragmentListener; import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.keyguard.DismissCallbackRegistry; -import com.android.systemui.keyguard.KeyguardSliceProvider; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; @@ -169,12 +168,10 @@ import com.android.systemui.statusbar.BackDropView; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.EmptyShadeView; -import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.GestureRecorder; import com.android.systemui.statusbar.KeyboardShortcuts; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.NavigationBarController; -import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationPresenter; @@ -192,15 +189,11 @@ import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationAlertingManager; -import com.android.systemui.statusbar.notification.NotificationClicker; -import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; -import com.android.systemui.statusbar.notification.NotificationListController; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; -import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer; +import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -219,7 +212,6 @@ import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; -import com.android.systemui.statusbar.policy.RemoteInputUriController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.volume.VolumeComponent; @@ -357,7 +349,6 @@ public class StatusBar extends SystemUI implements DemoMode, private final Object mQueueLock = new Object(); - private final FeatureFlags mFeatureFlags; private final StatusBarIconController mIconController; private final PulseExpansionHandler mPulseExpansionHandler; private final NotificationWakeUpCoordinator mWakeUpCoordinator; @@ -366,7 +357,6 @@ public class StatusBar extends SystemUI implements DemoMode, private final HeadsUpManagerPhone mHeadsUpManager; private final DynamicPrivacyController mDynamicPrivacyController; private final BypassHeadsUpNotifier mBypassHeadsUpNotifier; - private final Lazy<NotifPipelineInitializer> mNewNotifPipeline; private final FalsingManager mFalsingManager; private final BroadcastDispatcher mBroadcastDispatcher; private final ConfigurationController mConfigurationController; @@ -375,7 +365,6 @@ public class StatusBar extends SystemUI implements DemoMode, private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy; private final Provider<StatusBarComponent.Builder> mStatusBarComponentBuilder; private final PluginManager mPluginManager; - private final RemoteInputUriController mRemoteInputUriController; private final Optional<Divider> mDividerOptional; private final StatusBarNotificationActivityStarter.Builder mStatusBarNotificationActivityStarterBuilder; @@ -388,8 +377,8 @@ public class StatusBar extends SystemUI implements DemoMode, private final KeyguardDismissUtil mKeyguardDismissUtil; private final ExtensionController mExtensionController; private final UserInfoControllerImpl mUserInfoControllerImpl; - private final NotificationRowBinderImpl mNotificationRowBinder; private final DismissCallbackRegistry mDismissCallbackRegistry; + private NotificationsController mNotificationsController; // expanded notifications // the sliding/resizing panel within the notification window @@ -413,8 +402,6 @@ public class StatusBar extends SystemUI implements DemoMode, private final NotificationGutsManager mGutsManager; private final NotificationLogger mNotificationLogger; - private final NotificationEntryManager mEntryManager; - private NotificationListController mNotificationListController; private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; private final NotificationViewHierarchyManager mViewHierarchyManager; private final KeyguardViewMediator mKeyguardViewMediator; @@ -590,7 +577,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onStrongAuthStateChanged(int userId) { super.onStrongAuthStateChanged(userId); - mEntryManager.updateNotifications("onStrongAuthStateChanged"); + mNotificationsController.requestNotificationUpdate("onStrongAuthStateChanged"); } }; private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); @@ -615,7 +602,7 @@ public class StatusBar extends SystemUI implements DemoMode, @SuppressWarnings("OptionalUsedAsFieldOrParameterType") public StatusBar( Context context, - FeatureFlags featureFlags, + NotificationsController notificationsController, LightBarController lightBarController, AutoHideController autoHideController, KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -627,13 +614,11 @@ public class StatusBar extends SystemUI implements DemoMode, HeadsUpManagerPhone headsUpManagerPhone, DynamicPrivacyController dynamicPrivacyController, BypassHeadsUpNotifier bypassHeadsUpNotifier, - Lazy<NotifPipelineInitializer> newNotifPipeline, FalsingManager falsingManager, BroadcastDispatcher broadcastDispatcher, RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler, NotificationGutsManager notificationGutsManager, NotificationLogger notificationLogger, - NotificationEntryManager notificationEntryManager, NotificationInterruptionStateProvider notificationInterruptionStateProvider, NotificationViewHierarchyManager notificationViewHierarchyManager, KeyguardViewMediator keyguardViewMediator, @@ -654,12 +639,10 @@ public class StatusBar extends SystemUI implements DemoMode, VibratorHelper vibratorHelper, BubbleController bubbleController, NotificationGroupManager groupManager, - NotificationGroupAlertTransferHelper groupAlertTransferHelper, VisualStabilityManager visualStabilityManager, DeviceProvisionedController deviceProvisionedController, NavigationBarController navigationBarController, Lazy<AssistManager> assistManagerLazy, - NotificationListener notificationListener, ConfigurationController configurationController, NotificationShadeWindowController notificationShadeWindowController, LockscreenLockIconController lockscreenLockIconController, @@ -677,7 +660,6 @@ public class StatusBar extends SystemUI implements DemoMode, Optional<Recents> recentsOptional, Provider<StatusBarComponent.Builder> statusBarComponentBuilder, PluginManager pluginManager, - RemoteInputUriController remoteInputUriController, Optional<Divider> dividerOptional, LightsOutNotifController lightsOutNotifController, StatusBarNotificationActivityStarter.Builder @@ -693,10 +675,9 @@ public class StatusBar extends SystemUI implements DemoMode, KeyguardDismissUtil keyguardDismissUtil, ExtensionController extensionController, UserInfoControllerImpl userInfoControllerImpl, - NotificationRowBinderImpl notificationRowBinder, DismissCallbackRegistry dismissCallbackRegistry) { super(context); - mFeatureFlags = featureFlags; + mNotificationsController = notificationsController; mLightBarController = lightBarController; mAutoHideController = autoHideController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; @@ -708,13 +689,11 @@ public class StatusBar extends SystemUI implements DemoMode, mHeadsUpManager = headsUpManagerPhone; mDynamicPrivacyController = dynamicPrivacyController; mBypassHeadsUpNotifier = bypassHeadsUpNotifier; - mNewNotifPipeline = newNotifPipeline; mFalsingManager = falsingManager; mBroadcastDispatcher = broadcastDispatcher; mRemoteInputQuickSettingsDisabler = remoteInputQuickSettingsDisabler; mGutsManager = notificationGutsManager; mNotificationLogger = notificationLogger; - mEntryManager = notificationEntryManager; mNotificationInterruptionStateProvider = notificationInterruptionStateProvider; mViewHierarchyManager = notificationViewHierarchyManager; mKeyguardViewMediator = keyguardViewMediator; @@ -735,12 +714,10 @@ public class StatusBar extends SystemUI implements DemoMode, mVibratorHelper = vibratorHelper; mBubbleController = bubbleController; mGroupManager = groupManager; - mGroupAlertTransferHelper = groupAlertTransferHelper; mVisualStabilityManager = visualStabilityManager; mDeviceProvisionedController = deviceProvisionedController; mNavigationBarController = navigationBarController; mAssistManagerLazy = assistManagerLazy; - mNotificationListener = notificationListener; mConfigurationController = configurationController; mNotificationShadeWindowController = notificationShadeWindowController; mLockscreenLockIconController = lockscreenLockIconController; @@ -758,7 +735,6 @@ public class StatusBar extends SystemUI implements DemoMode, mRecentsOptional = recentsOptional; mStatusBarComponentBuilder = statusBarComponentBuilder; mPluginManager = pluginManager; - mRemoteInputUriController = remoteInputUriController; mDividerOptional = dividerOptional; mStatusBarNotificationActivityStarterBuilder = statusBarNotificationActivityStarterBuilder; mShadeController = shadeController; @@ -772,12 +748,11 @@ public class StatusBar extends SystemUI implements DemoMode, mKeyguardDismissUtil = keyguardDismissUtil; mExtensionController = extensionController; mUserInfoControllerImpl = userInfoControllerImpl; - mNotificationRowBinder = notificationRowBinder; mDismissCallbackRegistry = dismissCallbackRegistry; mBubbleExpandListener = (isExpanding, key) -> { - mEntryManager.updateNotifications("onBubbleExpandChanged"); + mNotificationsController.requestNotificationUpdate("onBubbleExpandChanged"); updateScrimController(); }; @@ -787,20 +762,12 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void start() { - mNotificationListener.registerAsSystemService(); mScreenLifecycle.addObserver(mScreenObserver); mWakefulnessLifecycle.addObserver(mWakefulnessObserver); mUiModeManager = mContext.getSystemService(UiModeManager.class); - mBypassHeadsUpNotifier.setUp(mEntryManager); + mBypassHeadsUpNotifier.setUp(); mBubbleController.setExpandListener(mBubbleExpandListener); mActivityIntentHelper = new ActivityIntentHelper(mContext); - KeyguardSliceProvider sliceProvider = KeyguardSliceProvider.getAttachedInstance(); - if (sliceProvider != null) { - sliceProvider.initDependencies(mMediaManager, mStatusBarStateController, - mKeyguardBypassController, mDozeParameters); - } else { - Log.w(TAG, "Cannot init KeyguardSliceProvider dependencies"); - } mColorExtractor.addOnColorsChangedListener(this); mStatusBarStateController.addCallback(this, @@ -1068,12 +1035,8 @@ public class StatusBar extends SystemUI implements DemoMode, mConfigurationController.addCallback(mHeadsUpManager); mHeadsUpManager.addListener(this); mHeadsUpManager.addListener(mNotificationPanelViewController.getOnHeadsUpChangedListener()); - mHeadsUpManager.addListener(mGroupManager); - mHeadsUpManager.addListener(mGroupAlertTransferHelper); mHeadsUpManager.addListener(mVisualStabilityManager); mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager); - mGroupManager.setHeadsUpManager(mHeadsUpManager); - mGroupAlertTransferHelper.setHeadsUpManager(mHeadsUpManager); mNotificationLogger.setHeadsUpManager(mHeadsUpManager); createNavigationBar(result); @@ -1251,16 +1214,10 @@ public class StatusBar extends SystemUI implements DemoMode, mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanelViewController, mHeadsUpManager, mNotificationShadeWindowView, mStackScroller, mDozeScrimController, mScrimController, mActivityLaunchAnimator, mDynamicPrivacyController, - mNotificationAlertingManager, mNotificationRowBinder, mKeyguardStateController, + mNotificationAlertingManager, mKeyguardStateController, mKeyguardIndicationController, this /* statusBar */, mShadeController, mCommandQueue, mInitController); - mNotificationListController = - new NotificationListController( - mEntryManager, - (NotificationListContainer) mStackScroller, - mDeviceProvisionedController); - mNotificationShelf.setOnActivatedListener(mPresenter); mRemoteInputManager.getController().addCallback(mNotificationShadeWindowController); @@ -1274,22 +1231,12 @@ public class StatusBar extends SystemUI implements DemoMode, mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter); - if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { - mNotificationRowBinder.setInflationCallback(mEntryManager); - } - - mRemoteInputUriController.attach(mEntryManager); - - mNotificationRowBinder.setNotificationClicker(new NotificationClicker( - Optional.of(this), mBubbleController, mNotificationActivityStarter)); - - mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager); - mNotificationListController.bind(); - - if (mFeatureFlags.isNewNotifPipelineEnabled()) { - mNewNotifPipeline.get().initialize(mNotificationListener, mNotificationRowBinder); - } - mEntryManager.attach(mNotificationListener); + mNotificationsController.initialize( + this, + mPresenter, + (NotificationListContainer) mStackScroller, + mNotificationActivityStarter, + mPresenter); } /** @@ -1526,7 +1473,7 @@ public class StatusBar extends SystemUI implements DemoMode, * @param reason why we're requesting a notification update */ public void requestNotificationUpdate(String reason) { - mEntryManager.updateNotifications(reason); + mNotificationsController.requestNotificationUpdate(reason); } /** @@ -1734,7 +1681,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) { - mEntryManager.updateNotifications("onHeadsUpStateChanged"); + mNotificationsController.requestNotificationUpdate("onHeadsUpStateChanged"); if (mStatusBarStateController.isDozing() && isHeadsUp) { entry.setPulseSuppressed(false); mDozeServiceHost.fireNotificationPulse(entry); @@ -2493,11 +2440,9 @@ public class StatusBar extends SystemUI implements DemoMode, mStatusBarKeyguardViewManager.dump(pw); } - if (DUMPTRUCK) { - synchronized (mEntryManager) { - mEntryManager.dump(pw, " "); - } + mNotificationsController.dump(fd, pw, args, DUMPTRUCK); + if (DUMPTRUCK) { if (false) { pw.println("see the logcat for a dump of the views we have created."); // must happen on ui thread @@ -2520,11 +2465,6 @@ public class StatusBar extends SystemUI implements DemoMode, } else { pw.println(" mHeadsUpManager: null"); } - if (mGroupManager != null) { - mGroupManager.dump(fd, pw, args); - } else { - pw.println(" mGroupManager: null"); - } if (mBubbleController != null) { mBubbleController.dump(fd, pw, args); @@ -2755,9 +2695,7 @@ public class StatusBar extends SystemUI implements DemoMode, }; public void resetUserExpandedStates() { - for (NotificationEntry entry : mEntryManager.getVisibleNotifications()) { - entry.resetUserExpansion(); - } + mNotificationsController.resetUserExpandedStates(); } private void executeWhenUnlocked(OnDismissAction action, boolean requiresShadeOpen) { @@ -2863,7 +2801,7 @@ public class StatusBar extends SystemUI implements DemoMode, try { // consider the transition from peek to expanded to be a panel open, // but not one that clears notification effects. - int notificationLoad = mEntryManager.getActiveNotificationsCount(); + int notificationLoad = mNotificationsController.getActiveNotificationsCount(); mBarService.onPanelRevealed(false, notificationLoad); } catch (RemoteException ex) { // Won't fail unless the world has ended. @@ -2881,7 +2819,7 @@ public class StatusBar extends SystemUI implements DemoMode, !mPresenter.isPresenterFullyCollapsed() && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED); - int notificationLoad = mEntryManager.getActiveNotificationsCount(); + int notificationLoad = mNotificationsController.getActiveNotificationsCount(); if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) { notificationLoad = 1; } @@ -3522,7 +3460,7 @@ public class StatusBar extends SystemUI implements DemoMode, updateQsExpansionEnabled(); mKeyguardViewMediator.setDozing(mDozing); - mEntryManager.updateNotifications("onDozingChanged"); + mNotificationsController.requestNotificationUpdate("onDozingChanged"); updateDozingState(); mDozeServiceHost.updateDozing(); updateScrimController(); @@ -3974,7 +3912,6 @@ public class StatusBar extends SystemUI implements DemoMode, protected ViewGroup mStackScroller; private final NotificationGroupManager mGroupManager; - private final NotificationGroupAlertTransferHelper mGroupAlertTransferHelper; // handling reordering private final VisualStabilityManager mVisualStabilityManager; @@ -4041,21 +3978,12 @@ public class StatusBar extends SystemUI implements DemoMode, } }; - private final NotificationListener mNotificationListener; - public void setNotificationSnoozed(StatusBarNotification sbn, SnoozeOption snoozeOption) { - if (snoozeOption.getSnoozeCriterion() != null) { - mNotificationListener.snoozeNotification(sbn.getKey(), - snoozeOption.getSnoozeCriterion().getId()); - } else { - mNotificationListener.snoozeNotification(sbn.getKey(), - snoozeOption.getMinutesToSnoozeFor() * 60 * 1000); - } + mNotificationsController.setNotificationSnoozed(sbn, snoozeOption); } public void setNotificationSnoozed(StatusBarNotification sbn, int hoursToSnooze) { - mNotificationListener.snoozeNotification(sbn.getKey(), - hoursToSnooze * 60 * 60 * 1000); + mNotificationsController.setNotificationSnoozed(sbn, hoursToSnooze); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 86a81ce3d1f1..6a046884e835 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -16,6 +16,9 @@ package com.android.systemui.statusbar.phone; +import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; +import static android.view.ViewRootImpl.sNewInsetsMode; +import static android.view.WindowInsets.Type.navigationBars; import static com.android.systemui.plugins.ActivityStarter.OnDismissAction; import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_UNLOCK_FADING; import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK; @@ -789,7 +792,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private Runnable mMakeNavigationBarVisibleRunnable = new Runnable() { @Override public void run() { - mStatusBar.getNavigationBarView().getRootView().setVisibility(View.VISIBLE); + if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_FULL) { + mStatusBar.getNotificationShadeWindowView().getWindowInsetsController() + .show(navigationBars()); + } else { + mStatusBar.getNavigationBarView().getRootView().setVisibility(View.VISIBLE); + } } }; @@ -856,7 +864,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } } else { mContainer.removeCallbacks(mMakeNavigationBarVisibleRunnable); - mStatusBar.getNavigationBarView().getRootView().setVisibility(View.GONE); + if (sNewInsetsMode == NEW_INSETS_MODE_FULL) { + mStatusBar.getNotificationShadeWindowView().getWindowInsetsController() + .hide(navigationBars()); + } else { + mStatusBar.getNavigationBarView().getRootView().setVisibility(View.GONE); + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java index 7615bf826287..15a0e08e285f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java @@ -46,9 +46,7 @@ import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NavigationBarController; -import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -61,12 +59,10 @@ import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationAlertingManager; -import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; -import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer; +import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; @@ -77,7 +73,6 @@ import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; -import com.android.systemui.statusbar.policy.RemoteInputUriController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.volume.VolumeComponent; @@ -105,7 +100,7 @@ public class StatusBarModule { @Singleton static StatusBar provideStatusBar( Context context, - FeatureFlags featureFlags, + NotificationsController notificationsController, LightBarController lightBarController, AutoHideController autoHideController, KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -117,13 +112,11 @@ public class StatusBarModule { HeadsUpManagerPhone headsUpManagerPhone, DynamicPrivacyController dynamicPrivacyController, BypassHeadsUpNotifier bypassHeadsUpNotifier, - Lazy<NotifPipelineInitializer> newNotifPipeline, FalsingManager falsingManager, BroadcastDispatcher broadcastDispatcher, RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler, NotificationGutsManager notificationGutsManager, NotificationLogger notificationLogger, - NotificationEntryManager notificationEntryManager, NotificationInterruptionStateProvider notificationInterruptionStateProvider, NotificationViewHierarchyManager notificationViewHierarchyManager, KeyguardViewMediator keyguardViewMediator, @@ -144,12 +137,10 @@ public class StatusBarModule { VibratorHelper vibratorHelper, BubbleController bubbleController, NotificationGroupManager groupManager, - NotificationGroupAlertTransferHelper groupAlertTransferHelper, VisualStabilityManager visualStabilityManager, DeviceProvisionedController deviceProvisionedController, NavigationBarController navigationBarController, Lazy<AssistManager> assistManagerLazy, - NotificationListener notificationListener, ConfigurationController configurationController, NotificationShadeWindowController notificationShadeWindowController, LockscreenLockIconController lockscreenLockIconController, @@ -167,7 +158,6 @@ public class StatusBarModule { Optional<Recents> recentsOptional, Provider<StatusBarComponent.Builder> statusBarComponentBuilder, PluginManager pluginManager, - RemoteInputUriController remoteInputUriController, Optional<Divider> dividerOptional, LightsOutNotifController lightsOutNotifController, StatusBarNotificationActivityStarter.Builder @@ -183,11 +173,10 @@ public class StatusBarModule { KeyguardDismissUtil keyguardDismissUtil, ExtensionController extensionController, UserInfoControllerImpl userInfoControllerImpl, - NotificationRowBinderImpl notificationRowBinder, DismissCallbackRegistry dismissCallbackRegistry) { return new StatusBar( context, - featureFlags, + notificationsController, lightBarController, autoHideController, keyguardUpdateMonitor, @@ -199,13 +188,11 @@ public class StatusBarModule { headsUpManagerPhone, dynamicPrivacyController, bypassHeadsUpNotifier, - newNotifPipeline, falsingManager, broadcastDispatcher, remoteInputQuickSettingsDisabler, notificationGutsManager, notificationLogger, - notificationEntryManager, notificationInterruptionStateProvider, notificationViewHierarchyManager, keyguardViewMediator, @@ -226,12 +213,10 @@ public class StatusBarModule { vibratorHelper, bubbleController, groupManager, - groupAlertTransferHelper, visualStabilityManager, deviceProvisionedController, navigationBarController, assistManagerLazy, - notificationListener, configurationController, notificationShadeWindowController, lockscreenLockIconController, @@ -249,7 +234,6 @@ public class StatusBarModule { recentsOptional, statusBarComponentBuilder, pluginManager, - remoteInputUriController, dividerOptional, lightsOutNotifController, statusBarNotificationActivityStarterBuilder, @@ -264,7 +248,6 @@ public class StatusBarModule { keyguardDismissUtil, extensionController, userInfoControllerImpl, - notificationRowBinder, dismissCallbackRegistry); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index 1336b2de82d3..2485513abba5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -141,7 +141,6 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, ActivityLaunchAnimator activityLaunchAnimator, DynamicPrivacyController dynamicPrivacyController, NotificationAlertingManager notificationAlertingManager, - NotificationRowBinderImpl notificationRowBinder, KeyguardStateController keyguardStateController, KeyguardIndicationController keyguardIndicationController, StatusBar statusBar, @@ -216,8 +215,6 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, mEntryManager.addNotificationLifetimeExtender(mGutsManager); mEntryManager.addNotificationLifetimeExtenders( remoteInputManager.getLifetimeExtenders()); - notificationRowBinder.setUpWithPresenter(this, notifListContainer, mHeadsUpManager, - this); mNotificationInterruptionStateProvider.setUpWithPresenter( this, mHeadsUpManager, this::canHeadsUp); mLockscreenUserManager.setUpWithPresenter(this); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java index fb30bdec68b5..e448d0ac8649 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java @@ -107,6 +107,7 @@ public class StatusBarWindowController { PixelFormat.TRANSLUCENT); mLp.token = new Binder(); mLp.gravity = Gravity.TOP; + mLp.setFitInsetsTypes(0 /* types */); mLp.setTitle("StatusBar"); mLp.packageName = mContext.getPackageName(); mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 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 d06164963f7b..5a7c5c9b5ebc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java @@ -56,6 +56,7 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout { private static final int MAX_DOTS = 1; private int mDotPadding; + private int mIconSpacing; private int mStaticDotDiameter; private int mUnderflowWidth; private int mUnderflowStart = 0; @@ -99,6 +100,7 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout { mIconDotFrameWidth = getResources().getDimensionPixelSize( com.android.internal.R.dimen.status_bar_icon_size); mDotPadding = getResources().getDimensionPixelSize(R.dimen.overflow_icon_dot_padding); + mIconSpacing = getResources().getDimensionPixelSize(R.dimen.status_bar_system_icon_spacing); int radius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius); mStaticDotDiameter = 2 * radius; mUnderflowWidth = mIconDotFrameWidth + (MAX_DOTS - 1) * (mStaticDotDiameter + mDotPadding); @@ -163,20 +165,21 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout { // Measure all children so that they report the correct width int childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED); mNeedsUnderflow = mShouldRestrictIcons && visibleCount > MAX_ICONS; - for (int i = 0; i < mMeasureViews.size(); i++) { + for (int i = 0; i < visibleCount; i++) { // Walking backwards View child = mMeasureViews.get(visibleCount - i - 1); measureChild(child, childWidthSpec, heightMeasureSpec); + int spacing = i == visibleCount - 1 ? 0 : mIconSpacing; if (mShouldRestrictIcons) { if (i < maxVisible && trackWidth) { - totalWidth += getViewTotalMeasuredWidth(child); + totalWidth += getViewTotalMeasuredWidth(child) + spacing; } else if (trackWidth) { // We've hit the icon limit; add space for dots totalWidth += mUnderflowWidth; trackWidth = false; } } else { - totalWidth += getViewTotalMeasuredWidth(child); + totalWidth += getViewTotalMeasuredWidth(child) + spacing; } } @@ -284,11 +287,15 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout { continue; } + // Move translationX to the spot within StatusIconContainer's layout to add the view + // without cutting off the child view. + translationX -= getViewTotalWidth(child); childState.visibleState = STATE_ICON; - childState.xTranslation = translationX - getViewTotalWidth(child); + childState.xTranslation = translationX; mLayoutStates.add(0, childState); - translationX -= getViewTotalWidth(child); + // Shift translationX over by mIconSpacing for the next view. + translationX -= mIconSpacing; } // Show either 1-MAX_ICONS icons, or (MAX_ICONS - 1) icons + overflow @@ -306,7 +313,8 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout { firstUnderflowIndex = i; break; } - mUnderflowStart = (int) Math.max(contentStart, state.xTranslation - mUnderflowWidth); + mUnderflowStart = (int) Math.max( + contentStart, state.xTranslation - mUnderflowWidth - mIconSpacing); visible++; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java index 28b6c389d4df..06105f532eb6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java @@ -106,8 +106,8 @@ public class SystemUIDialog extends AlertDialog { if (Dependency.get(KeyguardStateController.class).isShowing()) { final Window window = dialog.getWindow(); window.setType(LayoutParams.TYPE_STATUS_BAR_PANEL); - window.setFitWindowInsetsTypes( - window.getFitWindowInsetsTypes() & ~Type.statusBars()); + window.getAttributes().setFitInsetsTypes( + window.getAttributes().getFitInsetsTypes() & ~Type.statusBars()); } else { dialog.getWindow().setType(LayoutParams.TYPE_STATUS_BAR_SUB_PANEL); } @@ -118,8 +118,8 @@ public class SystemUIDialog extends AlertDialog { window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL); window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); - window.setFitWindowInsetsTypes( - window.getFitWindowInsetsTypes() & ~Type.statusBars()); + window.getAttributes().setFitInsetsTypes( + window.getAttributes().getFitInsetsTypes() & ~Type.statusBars()); return dialog; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java index e675a7f373b6..5dc91047770d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java @@ -29,7 +29,6 @@ import android.text.TextUtils; import android.util.AttributeSet; import android.widget.TextView; -import com.android.internal.telephony.TelephonyIntents; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.Dependency; @@ -137,9 +136,9 @@ public class EmergencyCryptkeeperText extends TextView { displayText = getContext().getText( com.android.internal.R.string.emergency_calls_only); Intent i = getContext().registerReceiver(null, - new IntentFilter(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)); + new IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED)); if (i != null) { - displayText = i.getStringExtra(TelephonyIntents.EXTRA_PLMN); + displayText = i.getStringExtra(TelephonyManager.EXTRA_PLMN); } } } 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 39cbdc83a295..0d5b3756b68a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java @@ -29,11 +29,13 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.provider.Settings.Global; +import android.telephony.AccessNetworkConstants; import android.telephony.Annotation; import android.telephony.CdmaEriInformation; import android.telephony.CellSignalStrength; import android.telephony.CellSignalStrengthCdma; import android.telephony.CellSignalStrengthNr; +import android.telephony.DataSpecificRegistrationInfo; import android.telephony.ims.ImsMmTelManager; import android.telephony.ims.ImsReasonInfo; import android.telephony.ims.feature.MmTelFeature; @@ -51,9 +53,9 @@ import com.android.ims.ImsException; import com.android.ims.ImsManager; import com.android.ims.FeatureConnector; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.telephony.TelephonyIntents; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.PhoneConstants.DataState; +import com.android.internal.telephony.TelephonyIntents; import com.android.settingslib.Utils; import com.android.settingslib.graph.SignalDrawable; import com.android.settingslib.net.SignalStrengthUtil; @@ -390,9 +392,9 @@ public class MobileSignalController extends SignalController< private int getNumLevels() { if (mInflateSignalStrengths) { - return SignalStrength.NUM_SIGNAL_STRENGTH_BINS + 1; + return CellSignalStrength.getNumSignalStrengthLevels() + 1; } - return SignalStrength.NUM_SIGNAL_STRENGTH_BINS; + return CellSignalStrength.getNumSignalStrengthLevels(); } @Override @@ -586,12 +588,12 @@ public class MobileSignalController extends SignalController< public void handleBroadcast(Intent intent) { String action = intent.getAction(); - if (action.equals(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)) { - updateNetworkName(intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false), - intent.getStringExtra(TelephonyIntents.EXTRA_SPN), - intent.getStringExtra(TelephonyIntents.EXTRA_DATA_SPN), - intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false), - intent.getStringExtra(TelephonyIntents.EXTRA_PLMN)); + if (action.equals(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED)) { + updateNetworkName(intent.getBooleanExtra(TelephonyManager.EXTRA_SHOW_SPN, false), + intent.getStringExtra(TelephonyManager.EXTRA_SPN), + intent.getStringExtra(TelephonyManager.EXTRA_DATA_SPN), + intent.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false), + intent.getStringExtra(TelephonyManager.EXTRA_PLMN)); notifyListenersIfNecessary(); } else if (action.equals(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) { updateDataSim(); @@ -852,10 +854,19 @@ public class MobileSignalController extends SignalController< notifyListenersIfNecessary(); } + private int getNrState(ServiceState serviceState) { + NetworkRegistrationInfo nri = serviceState.getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + if (nri != null) { + return nri.getNrState(); + } + return NetworkRegistrationInfo.NR_STATE_NONE; + } + private MobileIconGroup getNr5GIconGroup() { if (mServiceState == null) return null; - int nrState = mServiceState.getNrState(); + int nrState = getNrState(mServiceState); if (nrState == NetworkRegistrationInfo.NR_STATE_CONNECTED) { // Check if the NR 5G is using millimeter wave and the icon is config. if (mServiceState.getNrFrequencyRange() == ServiceState.FREQUENCY_RANGE_MMWAVE) { @@ -1124,12 +1135,24 @@ public class MobileSignalController extends SignalController< if (mDataNetType == TelephonyManager.NETWORK_TYPE_LTE) { if (isCarrierSpecificDataIcon()) { mCAPlus = true; - } else if (mServiceState != null && mServiceState.isUsingCarrierAggregation()) { + } else if (mServiceState != null && isUsingCarrierAggregation(mServiceState)) { mCA = true; } } } + private boolean isUsingCarrierAggregation(ServiceState serviceState) { + NetworkRegistrationInfo nri = serviceState.getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + if (nri != null) { + DataSpecificRegistrationInfo dsri = nri.getDataSpecificInfo(); + if (dsri != null) { + return dsri.isUsingCarrierAggregation(); + } + } + return false; + } + @Override public void onDataActivity(int direction) { if (DEBUG) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index bb3342d2d567..bb6676e38df6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -41,9 +41,9 @@ import android.os.Looper; import android.os.PersistableBundle; import android.provider.Settings; import android.telephony.CarrierConfigManager; +import android.telephony.CellSignalStrength; import android.telephony.PhoneStateListener; import android.telephony.ServiceState; -import android.telephony.SignalStrength; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; @@ -319,11 +319,11 @@ public class NetworkControllerImpl extends BroadcastReceiver filter.addAction(WifiManager.RSSI_CHANGED_ACTION); filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); - filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED); + filter.addAction(Intent.ACTION_SIM_STATE_CHANGED); filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED); filter.addAction(TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED); filter.addAction(Intent.ACTION_SERVICE_STATE); - filter.addAction(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION); + filter.addAction(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); filter.addAction(ConnectivityManager.INET_CONDITION_ACTION); filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); @@ -530,7 +530,7 @@ public class NetworkControllerImpl extends BroadcastReceiver mConfig = Config.readConfig(mContext); mReceiverHandler.post(this::handleConfigurationChanged); break; - case TelephonyIntents.ACTION_SIM_STATE_CHANGED: + case Intent.ACTION_SIM_STATE_CHANGED: // Avoid rebroadcast because SysUI is direct boot aware. if (intent.getBooleanExtra(Intent.EXTRA_REBROADCAST_ON_UNLOCK, false)) { break; @@ -611,7 +611,7 @@ public class NetworkControllerImpl extends BroadcastReceiver @VisibleForTesting void doUpdateMobileControllers() { List<SubscriptionInfo> subscriptions = mSubscriptionManager - .getActiveSubscriptionInfoList(false); + .getActiveAndHiddenSubscriptionInfoList(); if (subscriptions == null) { subscriptions = Collections.emptyList(); } @@ -1043,7 +1043,7 @@ public class NetworkControllerImpl extends BroadcastReceiver if (level != null) { controller.getState().level = level.equals("null") ? -1 : Math.min(Integer.parseInt(level), - SignalStrength.NUM_SIGNAL_STRENGTH_BINS); + CellSignalStrength.getNumSignalStrengthLevels()); controller.getState().connected = controller.getState().level >= 0; } if (args.containsKey("inflate")) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index 307e3bcbaba5..408b3a619ff1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -161,6 +161,11 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND); RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent, results); + + mEntry.remoteInputText = mEditText.getText(); + mEntry.remoteInputUri = null; + mEntry.remoteInputMimeType = null; + if (mEntry.editedSuggestionInfo == null) { RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_FREE_FORM_INPUT); } else { @@ -177,6 +182,10 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND); RemoteInput.addDataResultToIntent(mRemoteInput, fillInIntent, results); + mEntry.remoteInputText = mContext.getString(R.string.remote_input_image_insertion_text); + mEntry.remoteInputMimeType = contentType; + mEntry.remoteInputUri = data; + return fillInIntent; } @@ -184,7 +193,6 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene mEditText.setEnabled(false); mSendButton.setVisibility(INVISIBLE); mProgressBar.setVisibility(VISIBLE); - mEntry.remoteInputText = mEditText.getText(); mEntry.lastRemoteInputSent = SystemClock.elapsedRealtime(); mController.addSpinning(mEntry.getKey(), mToken); mController.removeRemoteInput(mEntry, mToken); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java index d60afa3c5133..9ffa5900eac3 100755 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java @@ -58,7 +58,8 @@ public class WifiSignalController extends mWifiTracker.setListening(true); mHasMobileData = hasMobileData; if (wifiManager != null) { - wifiManager.registerTrafficStateCallback(new WifiTrafficStateCallback()); + wifiManager.registerTrafficStateCallback(context.getMainExecutor(), + new WifiTrafficStateCallback()); } mDefaultWifiIconGroup = new IconGroup( diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayManager.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayManager.java index 41e026af7c72..665cb6307b6a 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayManager.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayManager.java @@ -178,7 +178,7 @@ class ThemeOverlayManager { } else { mOverlayManager.setEnabled(pkg, false, userHandle); } - } catch (IllegalStateException e) { + } catch (SecurityException | IllegalStateException e) { Log.e(TAG, String.format("setEnabled failed: %s %s %b", pkg, userHandle, enabled), e); } diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java new file mode 100644 index 000000000000..dea8c5d49dfc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.toast; + +import android.annotation.MainThread; +import android.annotation.Nullable; +import android.app.INotificationManager; +import android.app.ITransientNotificationCallback; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.PixelFormat; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup.LayoutParams; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; +import android.widget.TextView; +import android.widget.Toast; + +import com.android.internal.R; +import com.android.systemui.SystemUI; +import com.android.systemui.statusbar.CommandQueue; + +import java.util.Objects; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Controls display of text toasts. + */ +@Singleton +public class ToastUI extends SystemUI implements CommandQueue.Callbacks { + private static final String TAG = "ToastUI"; + + /** + * Values taken from {@link Toast}. + */ + private static final long DURATION_SHORT = 4000; + private static final long DURATION_LONG = 7000; + + private final CommandQueue mCommandQueue; + private final WindowManager mWindowManager; + private final INotificationManager mNotificationManager; + private final AccessibilityManager mAccessibilityManager; + private ToastEntry mCurrentToast; + + @Inject + public ToastUI(Context context, CommandQueue commandQueue) { + super(context); + mCommandQueue = commandQueue; + mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + mNotificationManager = INotificationManager.Stub.asInterface( + ServiceManager.getService(Context.NOTIFICATION_SERVICE)); + mAccessibilityManager = AccessibilityManager.getInstance(context); + } + + @Override + public void start() { + mCommandQueue.addCallback(this); + } + + @Override + @MainThread + public void showToast(String packageName, IBinder token, CharSequence text, + IBinder windowToken, int duration, @Nullable ITransientNotificationCallback callback) { + if (mCurrentToast != null) { + hideCurrentToast(); + } + View view = getView(text); + LayoutParams params = getLayoutParams(windowToken, duration); + mCurrentToast = new ToastEntry(packageName, token, view, windowToken, callback); + try { + mWindowManager.addView(view, params); + } catch (WindowManager.BadTokenException e) { + Log.w(TAG, "Error while attempting to show toast from " + packageName, e); + return; + } + trySendAccessibilityEvent(view, packageName); + if (callback != null) { + try { + callback.onToastShown(); + } catch (RemoteException e) { + Log.w(TAG, "Error calling back " + packageName + " to notify onToastShow()", e); + } + } + } + + @Override + @MainThread + public void hideToast(String packageName, IBinder token) { + if (mCurrentToast == null || !Objects.equals(mCurrentToast.packageName, packageName) + || !Objects.equals(mCurrentToast.token, token)) { + Log.w(TAG, "Attempt to hide non-current toast from package " + packageName); + return; + } + hideCurrentToast(); + } + + @MainThread + private void hideCurrentToast() { + if (mCurrentToast.view.getParent() != null) { + mWindowManager.removeViewImmediate(mCurrentToast.view); + } + String packageName = mCurrentToast.packageName; + try { + mNotificationManager.finishToken(packageName, mCurrentToast.windowToken); + } catch (RemoteException e) { + Log.w(TAG, "Error finishing toast window token from package " + packageName, e); + } + if (mCurrentToast.callback != null) { + try { + mCurrentToast.callback.onToastHidden(); + } catch (RemoteException e) { + Log.w(TAG, "Error calling back " + packageName + " to notify onToastHide()", e); + } + } + mCurrentToast = null; + } + + private void trySendAccessibilityEvent(View view, String packageName) { + if (!mAccessibilityManager.isEnabled()) { + return; + } + AccessibilityEvent event = AccessibilityEvent.obtain( + AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); + event.setClassName(Toast.class.getName()); + event.setPackageName(packageName); + view.dispatchPopulateAccessibilityEvent(event); + mAccessibilityManager.sendAccessibilityEvent(event); + } + + private View getView(CharSequence text) { + View view = LayoutInflater.from(mContext).inflate( + R.layout.transient_notification, null); + TextView textView = view.findViewById(com.android.internal.R.id.message); + textView.setText(text); + return view; + } + + private LayoutParams getLayoutParams(IBinder windowToken, int duration) { + WindowManager.LayoutParams params = new WindowManager.LayoutParams(); + params.height = WindowManager.LayoutParams.WRAP_CONTENT; + params.width = WindowManager.LayoutParams.WRAP_CONTENT; + params.format = PixelFormat.TRANSLUCENT; + params.windowAnimations = com.android.internal.R.style.Animation_Toast; + params.type = WindowManager.LayoutParams.TYPE_TOAST; + params.setTitle("Toast"); + params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + Configuration config = mContext.getResources().getConfiguration(); + int specificGravity = mContext.getResources().getInteger( + com.android.internal.R.integer.config_toastDefaultGravity); + int gravity = Gravity.getAbsoluteGravity(specificGravity, config.getLayoutDirection()); + params.gravity = gravity; + if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) { + params.horizontalWeight = 1.0f; + } + if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) { + params.verticalWeight = 1.0f; + } + params.x = 0; + params.y = mContext.getResources().getDimensionPixelSize(R.dimen.toast_y_offset); + params.verticalMargin = 0; + params.horizontalMargin = 0; + params.packageName = mContext.getPackageName(); + params.hideTimeoutMilliseconds = + (duration == Toast.LENGTH_LONG) ? DURATION_LONG : DURATION_SHORT; + params.token = windowToken; + return params; + } + + private static class ToastEntry { + public final String packageName; + public final IBinder token; + public final View view; + public final IBinder windowToken; + + @Nullable + public final ITransientNotificationCallback callback; + + private ToastEntry(String packageName, IBinder token, View view, IBinder windowToken, + @Nullable ITransientNotificationCallback callback) { + this.packageName = packageName; + this.token = token; + this.view = view; + this.windowToken = windowToken; + this.callback = callback; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/tracing/ProtoTracer.java b/packages/SystemUI/src/com/android/systemui/tracing/ProtoTracer.java new file mode 100644 index 000000000000..3bef044a2526 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/tracing/ProtoTracer.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.tracing; + +import static com.android.systemui.tracing.nano.SystemUiTraceFileProto.MAGIC_NUMBER_H; +import static com.android.systemui.tracing.nano.SystemUiTraceFileProto.MAGIC_NUMBER_L; + +import android.content.Context; +import android.os.SystemClock; + +import androidx.annotation.NonNull; + +import com.android.systemui.DumpController; +import com.android.systemui.Dumpable; +import com.android.systemui.shared.tracing.FrameProtoTracer; +import com.android.systemui.shared.tracing.FrameProtoTracer.ProtoTraceParams; +import com.android.systemui.shared.tracing.ProtoTraceable; +import com.android.systemui.tracing.nano.SystemUiTraceProto; +import com.android.systemui.tracing.nano.SystemUiTraceEntryProto; +import com.android.systemui.tracing.nano.SystemUiTraceFileProto; + +import com.google.protobuf.nano.MessageNano; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Queue; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Controller for coordinating winscope proto tracing. + */ +@Singleton +public class ProtoTracer implements Dumpable, ProtoTraceParams<MessageNano, SystemUiTraceFileProto, + SystemUiTraceEntryProto, SystemUiTraceProto> { + + private static final String TAG = "ProtoTracer"; + private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L; + + private final Context mContext; + private final FrameProtoTracer<MessageNano, SystemUiTraceFileProto, SystemUiTraceEntryProto, + SystemUiTraceProto> mProtoTracer; + + @Inject + public ProtoTracer(Context context, DumpController dumpController) { + mContext = context; + mProtoTracer = new FrameProtoTracer<>(this); + dumpController.registerDumpable(this); + } + + @Override + public File getTraceFile() { + return new File(mContext.getFilesDir(), "sysui_trace.pb"); + } + + @Override + public SystemUiTraceFileProto getEncapsulatingTraceProto() { + return new SystemUiTraceFileProto(); + } + + @Override + public SystemUiTraceEntryProto updateBufferProto(SystemUiTraceEntryProto reuseObj, + ArrayList<ProtoTraceable<SystemUiTraceProto>> traceables) { + SystemUiTraceEntryProto proto = reuseObj != null + ? reuseObj + : new SystemUiTraceEntryProto(); + proto.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos(); + proto.systemUi = proto.systemUi != null ? proto.systemUi : new SystemUiTraceProto(); + for (ProtoTraceable t : traceables) { + t.writeToProto(proto.systemUi); + } + return proto; + } + + @Override + public byte[] serializeEncapsulatingProto(SystemUiTraceFileProto encapsulatingProto, + Queue<SystemUiTraceEntryProto> buffer) { + encapsulatingProto.magicNumber = MAGIC_NUMBER_VALUE; + encapsulatingProto.entry = buffer.toArray(new SystemUiTraceEntryProto[0]); + return MessageNano.toByteArray(encapsulatingProto); + } + + @Override + public byte[] getProtoBytes(MessageNano proto) { + return MessageNano.toByteArray(proto); + } + + @Override + public int getProtoSize(MessageNano proto) { + return proto.getCachedSize(); + } + + public void start() { + mProtoTracer.start(); + } + + public void stop() { + mProtoTracer.stop(); + } + + public boolean isEnabled() { + return mProtoTracer.isEnabled(); + } + + public void add(ProtoTraceable<SystemUiTraceProto> traceable) { + mProtoTracer.add(traceable); + } + + public void remove(ProtoTraceable<SystemUiTraceProto> traceable) { + mProtoTracer.remove(traceable); + } + + public void scheduleFrameUpdate() { + mProtoTracer.scheduleFrameUpdate(); + } + + public void update() { + mProtoTracer.update(); + } + + @Override + public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { + pw.println("ProtoTracer:"); + pw.print(" "); pw.println("enabled: " + mProtoTracer.isEnabled()); + pw.print(" "); pw.println("usagePct: " + mProtoTracer.getBufferUsagePct()); + pw.print(" "); pw.println("file: " + getTraceFile()); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto b/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto new file mode 100644 index 000000000000..d940a6b5c460 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +package com.android.systemui.tracing; + +option java_multiple_files = true; + +message SystemUiTraceProto { + + optional EdgeBackGestureHandlerProto edge_back_gesture_handler = 1; +} + +message EdgeBackGestureHandlerProto { + + optional bool allow_gesture = 1; +} + +/* represents a file full of system ui trace entries. + Encoded, it should start with 0x9 0x53 0x59 0x53 0x55 0x49 0x54 0x52 0x43 (.SYSUITRC), such + that they can be easily identified. */ +message SystemUiTraceFileProto { + + /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L + (this is needed because enums have to be 32 bits and there's no nice way to put 64bit + constants into .proto files. */ + enum MagicNumber { + INVALID = 0; + MAGIC_NUMBER_L = 0x55535953; /* SYSU (little-endian ASCII) */ + MAGIC_NUMBER_H = 0x43525449; /* ITRC (little-endian ASCII) */ + } + + optional fixed64 magic_number = 1; /* Must be the first field, set to value in MagicNumber */ + repeated SystemUiTraceEntryProto entry = 2; +} + +/* one system ui trace entry. */ +message SystemUiTraceEntryProto { + /* required: elapsed realtime in nanos since boot of when this entry was logged */ + optional fixed64 elapsed_realtime_nanos = 1; + + optional SystemUiTraceProto system_ui = 3; +} diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java index 47454cb5aca1..cfa2947eb862 100644 --- a/packages/SystemUI/src/com/android/systemui/util/Utils.java +++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java @@ -129,8 +129,6 @@ public class Utils { */ public static boolean useQsMediaPlayer(Context context) { int flag = Settings.System.getInt(context.getContentResolver(), "qs_media_player", 0); - flag |= Settings.System.getInt(context.getContentResolver(), "npv_plugin_flag", 0); - return flag > 0; } } diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayChangeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayChangeController.java new file mode 100644 index 000000000000..66bc306d4a59 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayChangeController.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.wm; + +import android.os.Handler; +import android.os.RemoteException; +import android.view.IDisplayWindowRotationCallback; +import android.view.IDisplayWindowRotationController; +import android.view.IWindowManager; +import android.view.WindowContainerTransaction; + +import java.util.ArrayList; + +/** + * This module deals with display rotations coming from WM. When WM starts a rotation: after it has + * frozen the screen, it will call into this class. This will then call all registered local + * controllers and give them a chance to queue up task changes to be applied synchronously with that + * rotation. + */ +public class DisplayChangeController { + + private final Handler mHandler; + private final IWindowManager mWmService; + + private final ArrayList<OnDisplayChangingListener> mRotationListener = + new ArrayList<>(); + private final ArrayList<OnDisplayChangingListener> mTmpListeners = new ArrayList<>(); + + private final IDisplayWindowRotationController mDisplayRotationController = + new IDisplayWindowRotationController.Stub() { + @Override + public void onRotateDisplay(int displayId, final int fromRotation, + final int toRotation, IDisplayWindowRotationCallback callback) { + mHandler.post(() -> { + WindowContainerTransaction t = new WindowContainerTransaction(); + synchronized (mRotationListener) { + mTmpListeners.clear(); + // Make a local copy in case the handlers add/remove themselves. + mTmpListeners.addAll(mRotationListener); + } + for (OnDisplayChangingListener c : mTmpListeners) { + c.onRotateDisplay(displayId, fromRotation, toRotation, t); + } + try { + callback.continueRotateDisplay(toRotation, t); + } catch (RemoteException e) { + } + }); + } + }; + + public DisplayChangeController(Handler mainHandler, IWindowManager wmService) { + mHandler = mainHandler; + mWmService = wmService; + try { + mWmService.setDisplayWindowRotationController(mDisplayRotationController); + } catch (RemoteException e) { + throw new RuntimeException("Unable to register rotation controller"); + } + } + + /** + * Adds a display rotation controller. + */ + public void addRotationListener(OnDisplayChangingListener listener) { + synchronized (mRotationListener) { + mRotationListener.add(listener); + } + } + + /** + * Removes a display rotation controller. + */ + public void removeRotationListener(OnDisplayChangingListener listener) { + synchronized (mRotationListener) { + mRotationListener.remove(listener); + } + } + + /** + * Give a listener a chance to queue up configuration changes to execute as part of a + * display rotation. The contents of {@link #onRotateDisplay} must run synchronously. + */ + public interface OnDisplayChangingListener { + /** + * Called before the display is rotated. Contents of this method must run synchronously. + * @param displayId Id of display that is rotating. + * @param fromRotation starting rotation of the display. + * @param toRotation target rotation of the display (after rotating). + * @param t A task transaction to populate. + */ + void onRotateDisplay(int displayId, int fromRotation, int toRotation, + WindowContainerTransaction t); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayWindowController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayController.java index 951d6dd4c3a3..bc24ad0118e7 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/DisplayWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayController.java @@ -26,12 +26,10 @@ import android.util.Slog; import android.util.SparseArray; import android.view.Display; import android.view.IDisplayWindowListener; -import android.view.IDisplayWindowRotationCallback; -import android.view.IDisplayWindowRotationController; import android.view.IWindowManager; -import android.view.WindowContainerTransaction; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.wm.DisplayChangeController.OnDisplayChangingListener; import java.util.ArrayList; @@ -45,42 +43,16 @@ import javax.inject.Singleton; * rotation. */ @Singleton -public class DisplayWindowController { - private static final String TAG = "DisplayWindowController"; +public class DisplayController { + private static final String TAG = "DisplayController"; private final Handler mHandler; private final Context mContext; private final IWindowManager mWmService; - - private final ArrayList<OnDisplayWindowRotationController> mRotationControllers = - new ArrayList<>(); - private final ArrayList<OnDisplayWindowRotationController> mTmpControllers = new ArrayList<>(); + private final DisplayChangeController mChangeController; private final SparseArray<DisplayRecord> mDisplays = new SparseArray<>(); - private final ArrayList<DisplayWindowListener> mDisplayChangedListeners = new ArrayList<>(); - - private final IDisplayWindowRotationController mDisplayRotationController = - new IDisplayWindowRotationController.Stub() { - @Override - public void onRotateDisplay(int displayId, final int fromRotation, - final int toRotation, IDisplayWindowRotationCallback callback) { - mHandler.post(() -> { - WindowContainerTransaction t = new WindowContainerTransaction(); - synchronized (mRotationControllers) { - mTmpControllers.clear(); - // Make a local copy in case the handlers add/remove themselves. - mTmpControllers.addAll(mRotationControllers); - } - for (OnDisplayWindowRotationController c : mTmpControllers) { - c.onRotateDisplay(displayId, fromRotation, toRotation, t); - } - try { - callback.continueRotateDisplay(toRotation, t); - } catch (RemoteException e) { - } - }); - } - }; + private final ArrayList<OnDisplaysChangedListener> mDisplayChangedListeners = new ArrayList<>(); /** * Get's a display by id from DisplayManager. @@ -160,14 +132,14 @@ public class DisplayWindowController { }; @Inject - public DisplayWindowController(Context context, @Main Handler mainHandler, + public DisplayController(Context context, @Main Handler mainHandler, IWindowManager wmService) { mHandler = mainHandler; mContext = context; mWmService = wmService; + mChangeController = new DisplayChangeController(mHandler, mWmService); try { mWmService.registerDisplayWindowListener(mDisplayContainerListener); - mWmService.setDisplayWindowRotationController(mDisplayRotationController); } catch (RemoteException e) { throw new RuntimeException("Unable to register hierarchy listener"); } @@ -193,7 +165,7 @@ public class DisplayWindowController { * Add a display window-container listener. It will get notified whenever a display's * configuration changes or when displays are added/removed from the WM hierarchy. */ - public void addDisplayWindowListener(DisplayWindowListener listener) { + public void addDisplayWindowListener(OnDisplaysChangedListener listener) { synchronized (mDisplays) { if (mDisplayChangedListeners.contains(listener)) { return; @@ -208,7 +180,7 @@ public class DisplayWindowController { /** * Remove a display window-container listener. */ - public void removeDisplayWindowListener(DisplayWindowListener listener) { + public void removeDisplayWindowListener(OnDisplaysChangedListener listener) { synchronized (mDisplays) { mDisplayChangedListeners.remove(listener); } @@ -217,19 +189,15 @@ public class DisplayWindowController { /** * Adds a display rotation controller. */ - public void addRotationController(OnDisplayWindowRotationController controller) { - synchronized (mRotationControllers) { - mRotationControllers.add(controller); - } + public void addDisplayChangingController(OnDisplayChangingListener controller) { + mChangeController.addRotationListener(controller); } /** * Removes a display rotation controller. */ - public void removeRotationController(OnDisplayWindowRotationController controller) { - synchronized (mRotationControllers) { - mRotationControllers.remove(controller); - } + public void removeDisplayChangingController(OnDisplayChangingListener controller) { + mChangeController.removeRotationListener(controller); } private static class DisplayRecord { @@ -244,36 +212,20 @@ public class DisplayWindowController { * * @see IDisplayWindowListener */ - public interface DisplayWindowListener { + public interface OnDisplaysChangedListener { /** * Called when a display has been added to the WM hierarchy. */ - void onDisplayAdded(int displayId); + default void onDisplayAdded(int displayId) {} /** * Called when a display's window-container configuration changes. */ - void onDisplayConfigurationChanged(int displayId, Configuration newConfig); + default void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {} /** * Called when a display is removed. */ - void onDisplayRemoved(int displayId); - } - - /** - * Give a controller a chance to queue up configuration changes to execute as part of a - * display rotation. The contents of {@link #onRotateDisplay} must run synchronously. - */ - public interface OnDisplayWindowRotationController { - /** - * Called before the display is rotated. Contents of this method must run synchronously. - * @param displayId Id of display that is rotating. - * @param fromRotation starting rotation of the display. - * @param toRotation target rotation of the display (after rotating). - * @param t A task transaction to populate. - */ - void onRotateDisplay(int displayId, int fromRotation, int toRotation, - WindowContainerTransaction t); + default void onDisplayRemoved(int displayId) {} } } diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java index d413308d4573..7dad05df8f2c 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java +++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java @@ -45,7 +45,7 @@ import javax.inject.Singleton; * Manages IME control at the display-level. This occurs when IME comes up in multi-window mode. */ @Singleton -public class DisplayImeController implements DisplayWindowController.DisplayWindowListener { +public class DisplayImeController implements DisplayController.OnDisplaysChangedListener { private static final String TAG = "DisplayImeController"; static final int ANIMATION_DURATION_SHOW_MS = 275; @@ -63,7 +63,7 @@ public class DisplayImeController implements DisplayWindowController.DisplayWind final ArrayList<ImePositionProcessor> mPositionProcessors = new ArrayList<>(); @Inject - DisplayImeController(SystemWindows syswin, DisplayWindowController displayController, + public DisplayImeController(SystemWindows syswin, DisplayController displayController, @Main Handler mainHandler) { mHandler = mainHandler; mSystemWindows = syswin; @@ -315,19 +315,20 @@ public class DisplayImeController implements DisplayWindowController.DisplayWind /** * Called when the IME position is starting to animate. */ - void onImeStartPositioning(int displayId, int imeTop, int finalImeTop, boolean showing, - SurfaceControl.Transaction t); + default void onImeStartPositioning(int displayId, int imeTop, int finalImeTop, + boolean showing, SurfaceControl.Transaction t) {} /** * Called when the ime position changed. This is expected to be a synchronous call on the * animation thread. Operations can be added to the transaction to be applied in sync. */ - void onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t); + default void onImePositionChanged(int displayId, int imeTop, + SurfaceControl.Transaction t) {} /** * Called when the IME position is done animating. */ - void onImeEndPositioning(int displayId, int imeTop, boolean showing, - SurfaceControl.Transaction t); + default void onImeEndPositioning(int displayId, int imeTop, boolean showing, + SurfaceControl.Transaction t) {} } } diff --git a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java index 5aba013a7fb8..044a2a6cc4b6 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java +++ b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java @@ -64,11 +64,11 @@ public class SystemWindows { final HashMap<View, SurfaceControlViewHost> mViewRoots = new HashMap<>(); Context mContext; IWindowSession mSession; - DisplayWindowController mDisplayController; + DisplayController mDisplayController; IWindowManager mWmService; - private final DisplayWindowController.DisplayWindowListener mDisplayListener = - new DisplayWindowController.DisplayWindowListener() { + private final DisplayController.OnDisplaysChangedListener mDisplayListener = + new DisplayController.OnDisplaysChangedListener() { @Override public void onDisplayAdded(int displayId) { } @@ -86,7 +86,7 @@ public class SystemWindows { }; @Inject - public SystemWindows(Context context, DisplayWindowController displayController, + public SystemWindows(Context context, DisplayController displayController, IWindowManager wmService) { mContext = context; mWmService = wmService; @@ -239,12 +239,12 @@ public class SystemWindows { Rect outVisibleInsets, Rect outStableInsets, DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl, InsetsState outInsetsState, - Point outSurfaceSize) { + Point outSurfaceSize, SurfaceControl outBLASTSurfaceControl) { int res = super.relayout(window, seq, attrs, requestedWidth, requestedHeight, viewVisibility, flags, frameNumber, outFrame, outOverscanInsets, outContentInsets, outVisibleInsets, outStableInsets, cutout, mergedConfiguration, outSurfaceControl, outInsetsState, - outSurfaceSize); + outSurfaceSize, outBLASTSurfaceControl); if (res != 0) { return res; } |