| |
| |
| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.systemui.statusbar.phone; |
| |
| import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; |
| import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; |
| import static android.app.StatusBarManager.windowStateToString; |
| |
| import static com.android.systemui.statusbar.notification.NotificationInflater.InflationExceptionHandler; |
| import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT; |
| import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT; |
| import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE; |
| import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT; |
| import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT; |
| import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT; |
| import static com.android.systemui.statusbar.phone.BarTransitions.MODE_WARNING; |
| |
| import android.R.style; |
| import android.animation.Animator; |
| import android.animation.AnimatorListenerAdapter; |
| import android.annotation.NonNull; |
| import android.app.ActivityManager; |
| import android.app.ActivityOptions; |
| import android.app.Notification; |
| import android.app.NotificationManager; |
| import android.app.PendingIntent; |
| import android.app.StatusBarManager; |
| import android.app.admin.DevicePolicyManager; |
| import android.content.BroadcastReceiver; |
| import android.content.ComponentCallbacks2; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.IntentSender; |
| import android.content.pm.IPackageManager; |
| import android.content.pm.PackageManager; |
| import android.content.res.Configuration; |
| import android.content.res.Resources; |
| import android.database.ContentObserver; |
| import android.graphics.Bitmap; |
| import android.graphics.Canvas; |
| import android.graphics.ColorFilter; |
| import android.graphics.PixelFormat; |
| import android.graphics.Point; |
| import android.graphics.PointF; |
| import android.graphics.PorterDuff; |
| import android.graphics.PorterDuffXfermode; |
| import android.graphics.Rect; |
| import android.graphics.drawable.BitmapDrawable; |
| import android.graphics.drawable.ColorDrawable; |
| import android.graphics.drawable.Drawable; |
| import android.media.AudioAttributes; |
| import android.media.MediaMetadata; |
| import android.media.session.MediaController; |
| import android.media.session.MediaSession; |
| import android.media.session.MediaSessionManager; |
| import android.media.session.PlaybackState; |
| import android.metrics.LogMaker; |
| import android.net.Uri; |
| import android.os.AsyncTask; |
| import android.os.Bundle; |
| import android.os.IBinder; |
| import android.os.Message; |
| import android.os.PowerManager; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.os.SystemClock; |
| import android.os.SystemProperties; |
| import android.os.Trace; |
| import android.os.UserHandle; |
| import android.os.UserManager; |
| import android.os.Vibrator; |
| import android.provider.Settings; |
| import android.service.notification.NotificationListenerService.RankingMap; |
| import android.service.notification.StatusBarNotification; |
| import android.util.ArraySet; |
| import android.util.DisplayMetrics; |
| import android.util.EventLog; |
| import android.util.Log; |
| import android.view.ContextThemeWrapper; |
| import android.view.Display; |
| import android.view.KeyEvent; |
| import android.view.LayoutInflater; |
| import android.view.MotionEvent; |
| import android.view.ThreadedRenderer; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.ViewParent; |
| import android.view.ViewStub; |
| import android.view.ViewTreeObserver; |
| import android.view.WindowManager; |
| import android.view.WindowManagerGlobal; |
| import android.view.animation.AccelerateInterpolator; |
| import android.view.animation.Interpolator; |
| import android.widget.DateTimeView; |
| import android.widget.ImageView; |
| import android.widget.TextView; |
| |
| import com.android.internal.logging.MetricsLogger; |
| import com.android.internal.logging.nano.MetricsProto.MetricsEvent; |
| import com.android.internal.statusbar.NotificationVisibility; |
| import com.android.internal.statusbar.StatusBarIcon; |
| import com.android.internal.util.NotificationMessagingUtil; |
| import com.android.keyguard.KeyguardHostView.OnDismissAction; |
| import com.android.keyguard.KeyguardStatusView; |
| import com.android.keyguard.KeyguardUpdateMonitor; |
| import com.android.keyguard.KeyguardUpdateMonitorCallback; |
| import com.android.keyguard.ViewMediatorCallback; |
| import com.android.systemui.ActivityStarterDelegate; |
| import com.android.systemui.DemoMode; |
| import com.android.systemui.Dependency; |
| import com.android.systemui.EventLogTags; |
| import com.android.systemui.Interpolators; |
| import com.android.systemui.Prefs; |
| import com.android.systemui.R; |
| import com.android.systemui.SystemUIFactory; |
| import com.android.systemui.assist.AssistManager; |
| import com.android.systemui.classifier.FalsingLog; |
| import com.android.systemui.classifier.FalsingManager; |
| import com.android.systemui.doze.DozeHost; |
| import com.android.systemui.doze.DozeLog; |
| import com.android.systemui.fragments.FragmentHostManager; |
| import com.android.systemui.fragments.PluginFragmentListener; |
| import com.android.systemui.keyguard.KeyguardViewMediator; |
| import com.android.systemui.plugins.qs.QS; |
| import com.android.systemui.plugins.ActivityStarter; |
| import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption; |
| import com.android.systemui.qs.QSFragment; |
| import com.android.systemui.qs.QSPanel; |
| import com.android.systemui.qs.QSTileHost; |
| import com.android.systemui.recents.ScreenPinningRequest; |
| import com.android.systemui.recents.events.EventBus; |
| import com.android.systemui.recents.events.activity.AppTransitionFinishedEvent; |
| import com.android.systemui.recents.events.activity.UndockingTaskEvent; |
| import com.android.systemui.stackdivider.Divider; |
| import com.android.systemui.stackdivider.WindowManagerProxy; |
| import com.android.systemui.statusbar.ActivatableNotificationView; |
| import com.android.systemui.statusbar.BackDropView; |
| import com.android.systemui.statusbar.CommandQueue; |
| import com.android.systemui.statusbar.DismissView; |
| import com.android.systemui.statusbar.DragDownHelper; |
| import com.android.systemui.statusbar.EmptyShadeView; |
| import com.android.systemui.statusbar.ExpandableNotificationRow; |
| import com.android.systemui.statusbar.GestureRecorder; |
| import com.android.systemui.statusbar.KeyboardShortcuts; |
| import com.android.systemui.statusbar.KeyguardIndicationController; |
| import com.android.systemui.statusbar.NotificationData; |
| import com.android.systemui.statusbar.NotificationData.Entry; |
| import com.android.systemui.statusbar.NotificationGuts; |
| import com.android.systemui.statusbar.NotificationInfo; |
| import com.android.systemui.statusbar.NotificationShelf; |
| import com.android.systemui.statusbar.NotificationSnooze; |
| import com.android.systemui.statusbar.RemoteInputController; |
| import com.android.systemui.statusbar.ScrimView; |
| import com.android.systemui.statusbar.SignalClusterView; |
| import com.android.systemui.statusbar.StatusBarState; |
| import com.android.systemui.statusbar.notification.InflationException; |
| import com.android.systemui.statusbar.notification.VisualStabilityManager; |
| import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager; |
| import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener; |
| import com.android.systemui.statusbar.policy.BatteryController; |
| import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; |
| import com.android.systemui.statusbar.policy.BrightnessMirrorController; |
| import com.android.systemui.statusbar.policy.ConfigurationController; |
| import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; |
| import com.android.systemui.statusbar.policy.DarkIconDispatcher; |
| import com.android.systemui.statusbar.policy.DeviceProvisionedController; |
| import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; |
| import com.android.systemui.statusbar.policy.HeadsUpManager; |
| import com.android.systemui.statusbar.policy.KeyguardMonitor; |
| import com.android.systemui.statusbar.policy.KeyguardMonitorImpl; |
| 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.PreviewInflater; |
| import com.android.systemui.statusbar.policy.UserInfoController; |
| import com.android.systemui.statusbar.policy.UserInfoControllerImpl; |
| import com.android.systemui.statusbar.policy.UserSwitcherController; |
| import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; |
| import com.android.systemui.statusbar.stack.NotificationStackScrollLayout.OnChildLocationsChangedListener; |
| import com.android.systemui.util.leak.LeakDetector; |
| import com.android.systemui.volume.VolumeComponent; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import android.app.ActivityManager.StackId; |
| import android.app.INotificationManager; |
| import android.app.KeyguardManager; |
| import android.app.NotificationChannel; |
| import android.app.RemoteInput; |
| import android.app.TaskStackBuilder; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageManager.NameNotFoundException; |
| import android.content.pm.UserInfo; |
| import android.os.Build; |
| import android.os.Handler; |
| import android.service.dreams.DreamService; |
| import android.service.dreams.IDreamManager; |
| import android.service.notification.NotificationListenerService; |
| import android.service.vr.IVrManager; |
| import android.service.vr.IVrStateCallbacks; |
| import android.text.TextUtils; |
| import android.util.Slog; |
| import android.util.SparseArray; |
| import android.util.SparseBooleanArray; |
| import android.view.IWindowManager; |
| import android.view.ViewAnimationUtils; |
| import android.view.accessibility.AccessibilityManager; |
| import android.widget.RemoteViews; |
| import android.widget.Toast; |
| |
| import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; |
| import com.android.internal.statusbar.IStatusBarService; |
| import com.android.internal.widget.LockPatternUtils; |
| import com.android.systemui.DejankUtils; |
| import com.android.systemui.RecentsComponent; |
| import com.android.systemui.SwipeHelper; |
| import com.android.systemui.SystemUI; |
| import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; |
| import com.android.systemui.recents.Recents; |
| import com.android.systemui.statusbar.policy.RemoteInputView; |
| import com.android.systemui.statusbar.stack.StackStateAnimator; |
| import com.android.systemui.util.NotificationChannels; |
| |
| import java.util.HashSet; |
| import java.util.Locale; |
| import java.util.Set; |
| import java.util.Stack; |
| |
| public class StatusBar extends SystemUI implements DemoMode, |
| DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener, |
| OnHeadsUpChangedListener, VisualStabilityManager.Callback, CommandQueue.Callbacks, |
| ActivatableNotificationView.OnActivatedListener, |
| ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment, |
| ExpandableNotificationRow.OnExpandClickListener { |
| public static final boolean MULTIUSER_DEBUG = false; |
| |
| public static final boolean ENABLE_REMOTE_INPUT = |
| SystemProperties.getBoolean("debug.enable_remote_input", true); |
| public static final boolean ENABLE_CHILD_NOTIFICATIONS |
| = SystemProperties.getBoolean("debug.child_notifs", true); |
| public static final boolean FORCE_REMOTE_INPUT_HISTORY = |
| SystemProperties.getBoolean("debug.force_remoteinput_history", false); |
| private static boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false; |
| |
| protected static final int MSG_SHOW_RECENT_APPS = 1019; |
| protected static final int MSG_HIDE_RECENT_APPS = 1020; |
| protected static final int MSG_TOGGLE_RECENTS_APPS = 1021; |
| protected static final int MSG_PRELOAD_RECENT_APPS = 1022; |
| protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023; |
| protected static final int MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU = 1026; |
| protected static final int MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU = 1027; |
| |
| protected static final boolean ENABLE_HEADS_UP = true; |
| protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up"; |
| |
| // Must match constant in Settings. Used to highlight preferences when linking to Settings. |
| private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key"; |
| |
| private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF"; |
| |
| // Should match the values in PhoneWindowManager |
| public static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey"; |
| public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps"; |
| |
| private static final String BANNER_ACTION_CANCEL = |
| "com.android.systemui.statusbar.banner_action_cancel"; |
| private static final String BANNER_ACTION_SETUP = |
| "com.android.systemui.statusbar.banner_action_setup"; |
| private static final String NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION |
| = "com.android.systemui.statusbar.work_challenge_unlocked_notification_action"; |
| public static final String TAG = "StatusBar"; |
| public static final boolean DEBUG = false; |
| public static final boolean SPEW = false; |
| public static final boolean DUMPTRUCK = true; // extra dumpsys info |
| public static final boolean DEBUG_GESTURES = false; |
| public static final boolean DEBUG_MEDIA = false; |
| public static final boolean DEBUG_MEDIA_FAKE_ARTWORK = false; |
| |
| public static final boolean DEBUG_WINDOW_STATE = false; |
| |
| // additional instrumentation for testing purposes; intended to be left on during development |
| public static final boolean CHATTY = DEBUG; |
| |
| public static final boolean SHOW_LOCKSCREEN_MEDIA_ARTWORK = true; |
| |
| public static final String ACTION_FAKE_ARTWORK = "fake_artwork"; |
| |
| private static final int MSG_OPEN_NOTIFICATION_PANEL = 1000; |
| private static final int MSG_CLOSE_PANELS = 1001; |
| private static final int MSG_OPEN_SETTINGS_PANEL = 1002; |
| private static final int MSG_LAUNCH_TRANSITION_TIMEOUT = 1003; |
| // 1020-1040 reserved for BaseStatusBar |
| |
| // Time after we abort the launch transition. |
| private static final long LAUNCH_TRANSITION_TIMEOUT_MS = 5000; |
| |
| private static final boolean CLOSE_PANEL_WHEN_EMPTIED = true; |
| |
| private static final int STATUS_OR_NAV_TRANSIENT = |
| View.STATUS_BAR_TRANSIENT | View.NAVIGATION_BAR_TRANSIENT; |
| private static final long AUTOHIDE_TIMEOUT_MS = 3000; |
| |
| /** The minimum delay in ms between reports of notification visibility. */ |
| private static final int VISIBILITY_REPORT_MIN_DELAY_MS = 500; |
| |
| /** |
| * The delay to reset the hint text when the hint animation is finished running. |
| */ |
| private static final int HINT_RESET_DELAY_MS = 1200; |
| |
| private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() |
| .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) |
| .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) |
| .build(); |
| |
| public static final int FADE_KEYGUARD_START_DELAY = 100; |
| public static final int FADE_KEYGUARD_DURATION = 300; |
| public static final int FADE_KEYGUARD_DURATION_PULSING = 96; |
| |
| /** If true, the system is in the half-boot-to-decryption-screen state. |
| * Prudently disable QS and notifications. */ |
| private static final boolean ONLY_CORE_APPS; |
| |
| /** If true, the lockscreen will show a distinct wallpaper */ |
| private static final boolean ENABLE_LOCKSCREEN_WALLPAPER = true; |
| |
| /* If true, the device supports freeform window management. |
| * This affects the status bar UI. */ |
| private static final boolean FREEFORM_WINDOW_MANAGEMENT; |
| |
| /** |
| * How long to wait before auto-dismissing a notification that was kept for remote input, and |
| * has now sent a remote input. We auto-dismiss, because the app may not see a reason to cancel |
| * these given that they technically don't exist anymore. We wait a bit in case the app issues |
| * an update. |
| */ |
| private static final int REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY = 200; |
| |
| /** |
| * Never let the alpha become zero for surfaces that draw with SRC - otherwise the RenderNode |
| * won't draw anything and uninitialized memory will show through |
| * if mScrimSrcModeEnabled. Note that 0.001 is rounded down to 0 in |
| * libhwui. |
| */ |
| private static final float SRC_MIN_ALPHA = 0.002f; |
| |
| static { |
| boolean onlyCoreApps; |
| boolean freeformWindowManagement; |
| try { |
| IPackageManager packageManager = |
| IPackageManager.Stub.asInterface(ServiceManager.getService("package")); |
| onlyCoreApps = packageManager.isOnlyCoreApps(); |
| freeformWindowManagement = packageManager.hasSystemFeature( |
| PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT, 0); |
| } catch (RemoteException e) { |
| onlyCoreApps = false; |
| freeformWindowManagement = false; |
| } |
| ONLY_CORE_APPS = onlyCoreApps; |
| FREEFORM_WINDOW_MANAGEMENT = freeformWindowManagement; |
| } |
| |
| /** |
| * The {@link StatusBarState} of the status bar. |
| */ |
| protected int mState; |
| protected boolean mBouncerShowing; |
| protected boolean mShowLockscreenNotifications; |
| protected boolean mAllowLockscreenRemoteInput; |
| |
| PhoneStatusBarPolicy mIconPolicy; |
| |
| VolumeComponent mVolumeComponent; |
| BrightnessMirrorController mBrightnessMirrorController; |
| protected FingerprintUnlockController mFingerprintUnlockController; |
| LightBarController mLightBarController; |
| protected LockscreenWallpaper mLockscreenWallpaper; |
| |
| int mNaturalBarHeight = -1; |
| |
| Point mCurrentDisplaySize = new Point(); |
| |
| protected StatusBarWindowView mStatusBarWindow; |
| protected PhoneStatusBarView mStatusBarView; |
| private int mStatusBarWindowState = WINDOW_STATE_SHOWING; |
| protected StatusBarWindowManager mStatusBarWindowManager; |
| protected UnlockMethodCache mUnlockMethodCache; |
| private DozeServiceHost mDozeServiceHost; |
| private boolean mWakeUpComingFromTouch; |
| private PointF mWakeUpTouchLocation; |
| private boolean mScreenTurningOn; |
| |
| int mPixelFormat; |
| Object mQueueLock = new Object(); |
| |
| protected StatusBarIconController mIconController; |
| |
| // expanded notifications |
| protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window |
| View mExpandedContents; |
| TextView mNotificationPanelDebugText; |
| |
| // settings |
| private QSPanel mQSPanel; |
| |
| // top bar |
| protected KeyguardStatusBarView mKeyguardStatusBar; |
| KeyguardStatusView mKeyguardStatusView; |
| KeyguardBottomAreaView mKeyguardBottomArea; |
| boolean mLeaveOpenOnKeyguardHide; |
| KeyguardIndicationController mKeyguardIndicationController; |
| |
| // Keyguard is going away soon. |
| private boolean mKeyguardGoingAway; |
| // Keyguard is actually fading away now. |
| protected boolean mKeyguardFadingAway; |
| protected long mKeyguardFadingAwayDelay; |
| protected long mKeyguardFadingAwayDuration; |
| |
| // RemoteInputView to be activated after unlock |
| private View mPendingRemoteInputView; |
| private View mPendingWorkRemoteInputView; |
| |
| private View mReportRejectedTouch; |
| |
| int mMaxAllowedKeyguardNotifications; |
| |
| boolean mExpandedVisible; |
| |
| // the tracker view |
| int mTrackingPosition; // the position of the top of the tracking view. |
| |
| // Tracking finger for opening/closing. |
| boolean mTracking; |
| |
| int[] mAbsPos = new int[2]; |
| ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>(); |
| |
| // for disabling the status bar |
| int mDisabled1 = 0; |
| int mDisabled2 = 0; |
| |
| // tracking calls to View.setSystemUiVisibility() |
| int mSystemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE; |
| private final Rect mLastFullscreenStackBounds = new Rect(); |
| private final Rect mLastDockedStackBounds = new Rect(); |
| |
| // last value sent to window manager |
| private int mLastDispatchedSystemUiVisibility = ~View.SYSTEM_UI_FLAG_VISIBLE; |
| |
| DisplayMetrics mDisplayMetrics = new DisplayMetrics(); |
| |
| // XXX: gesture research |
| private final GestureRecorder mGestureRec = DEBUG_GESTURES |
| ? new GestureRecorder("/sdcard/statusbar_gestures.dat") |
| : null; |
| |
| private ScreenPinningRequest mScreenPinningRequest; |
| |
| private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); |
| |
| // ensure quick settings is disabled until the current user makes it through the setup wizard |
| private boolean mUserSetup = false; |
| private DeviceProvisionedListener mUserSetupObserver = new DeviceProvisionedListener() { |
| @Override |
| public void onUserSetupChanged() { |
| final boolean userSetup = mDeviceProvisionedController.isUserSetup( |
| mDeviceProvisionedController.getCurrentUser()); |
| if (MULTIUSER_DEBUG) Log.d(TAG, String.format("User setup changed: " + |
| "userSetup=%s mUserSetup=%s", userSetup, mUserSetup)); |
| |
| if (userSetup != mUserSetup) { |
| mUserSetup = userSetup; |
| if (!mUserSetup && mStatusBarView != null) |
| animateCollapseQuickSettings(); |
| if (mKeyguardBottomArea != null) { |
| mKeyguardBottomArea.setUserSetupComplete(mUserSetup); |
| } |
| updateQsExpansionEnabled(); |
| } |
| } |
| }; |
| |
| protected H mHandler = createHandler(); |
| final private ContentObserver mHeadsUpObserver = new ContentObserver(mHandler) { |
| @Override |
| public void onChange(boolean selfChange) { |
| boolean wasUsing = mUseHeadsUp; |
| mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts |
| && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt( |
| mContext.getContentResolver(), Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED, |
| Settings.Global.HEADS_UP_OFF); |
| mHeadsUpTicker = mUseHeadsUp && 0 != Settings.Global.getInt( |
| mContext.getContentResolver(), SETTING_HEADS_UP_TICKER, 0); |
| Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled")); |
| if (wasUsing != mUseHeadsUp) { |
| if (!mUseHeadsUp) { |
| Log.d(TAG, "dismissing any existing heads up notification on disable event"); |
| mHeadsUpManager.releaseAllImmediately(); |
| } |
| } |
| } |
| }; |
| |
| private int mInteractingWindows; |
| private boolean mAutohideSuspended; |
| private int mStatusBarMode; |
| private int mMaxKeyguardNotifications; |
| |
| private ViewMediatorCallback mKeyguardViewMediatorCallback; |
| protected ScrimController mScrimController; |
| protected DozeScrimController mDozeScrimController; |
| |
| private final Runnable mAutohide = new Runnable() { |
| @Override |
| public void run() { |
| int requested = mSystemUiVisibility & ~STATUS_OR_NAV_TRANSIENT; |
| if (mSystemUiVisibility != requested) { |
| notifyUiVisibilityChanged(requested); |
| } |
| }}; |
| |
| private boolean mWaitingForKeyguardExit; |
| private boolean mDozing; |
| private boolean mDozingRequested; |
| protected boolean mScrimSrcModeEnabled; |
| |
| public static final Interpolator ALPHA_IN = Interpolators.ALPHA_IN; |
| public static final Interpolator ALPHA_OUT = Interpolators.ALPHA_OUT; |
| |
| protected BackDropView mBackdrop; |
| protected ImageView mBackdropFront, mBackdropBack; |
| protected PorterDuffXfermode mSrcXferMode = new PorterDuffXfermode(PorterDuff.Mode.SRC); |
| protected PorterDuffXfermode mSrcOverXferMode = |
| new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER); |
| |
| private MediaSessionManager mMediaSessionManager; |
| private MediaController mMediaController; |
| private String mMediaNotificationKey; |
| private MediaMetadata mMediaMetadata; |
| private MediaController.Callback mMediaListener |
| = new MediaController.Callback() { |
| @Override |
| public void onPlaybackStateChanged(PlaybackState state) { |
| super.onPlaybackStateChanged(state); |
| if (DEBUG_MEDIA) Log.v(TAG, "DEBUG_MEDIA: onPlaybackStateChanged: " + state); |
| if (state != null) { |
| if (!isPlaybackActive(state.getState())) { |
| clearCurrentMediaNotification(); |
| updateMediaMetaData(true, true); |
| } |
| } |
| } |
| |
| @Override |
| public void onMetadataChanged(MediaMetadata metadata) { |
| super.onMetadataChanged(metadata); |
| if (DEBUG_MEDIA) Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata); |
| mMediaMetadata = metadata; |
| updateMediaMetaData(true, true); |
| } |
| }; |
| |
| private final OnChildLocationsChangedListener mOnChildLocationsChangedListener = |
| new OnChildLocationsChangedListener() { |
| @Override |
| public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout) { |
| userActivity(); |
| } |
| }; |
| |
| private int mDisabledUnmodified1; |
| private int mDisabledUnmodified2; |
| |
| /** Keys of notifications currently visible to the user. */ |
| private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications = |
| new ArraySet<>(); |
| private long mLastVisibilityReportUptimeMs; |
| |
| private Runnable mLaunchTransitionEndRunnable; |
| protected boolean mLaunchTransitionFadingAway; |
| private ExpandableNotificationRow mDraggedDownRow; |
| private boolean mLaunchCameraOnScreenTurningOn; |
| private boolean mLaunchCameraOnFinishedGoingToSleep; |
| private int mLastCameraLaunchSource; |
| private PowerManager.WakeLock mGestureWakeLock; |
| private Vibrator mVibrator; |
| private long[] mCameraLaunchGestureVibePattern; |
| |
| private final int[] mTmpInt2 = new int[2]; |
| |
| // Fingerprint (as computed by getLoggingFingerprint() of the last logged state. |
| private int mLastLoggedStateFingerprint; |
| |
| /** |
| * If set, the device has started going to sleep but isn't fully non-interactive yet. |
| */ |
| protected boolean mStartedGoingToSleep; |
| |
| private final OnChildLocationsChangedListener mNotificationLocationsChangedListener = |
| new OnChildLocationsChangedListener() { |
| @Override |
| public void onChildLocationsChanged( |
| NotificationStackScrollLayout stackScrollLayout) { |
| if (mHandler.hasCallbacks(mVisibilityReporter)) { |
| // Visibilities will be reported when the existing |
| // callback is executed. |
| return; |
| } |
| // Calculate when we're allowed to run the visibility |
| // reporter. Note that this timestamp might already have |
| // passed. That's OK, the callback will just be executed |
| // ASAP. |
| long nextReportUptimeMs = |
| mLastVisibilityReportUptimeMs + VISIBILITY_REPORT_MIN_DELAY_MS; |
| mHandler.postAtTime(mVisibilityReporter, nextReportUptimeMs); |
| } |
| }; |
| |
| // Tracks notifications currently visible in mNotificationStackScroller and |
| // emits visibility events via NoMan on changes. |
| private final Runnable mVisibilityReporter = new Runnable() { |
| private final ArraySet<NotificationVisibility> mTmpNewlyVisibleNotifications = |
| new ArraySet<>(); |
| private final ArraySet<NotificationVisibility> mTmpCurrentlyVisibleNotifications = |
| new ArraySet<>(); |
| private final ArraySet<NotificationVisibility> mTmpNoLongerVisibleNotifications = |
| new ArraySet<>(); |
| |
| @Override |
| public void run() { |
| mLastVisibilityReportUptimeMs = SystemClock.uptimeMillis(); |
| final String mediaKey = getCurrentMediaNotificationKey(); |
| |
| // 1. Loop over mNotificationData entries: |
| // A. Keep list of visible notifications. |
| // B. Keep list of previously hidden, now visible notifications. |
| // 2. Compute no-longer visible notifications by removing currently |
| // visible notifications from the set of previously visible |
| // notifications. |
| // 3. Report newly visible and no-longer visible notifications. |
| // 4. Keep currently visible notifications for next report. |
| ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications(); |
| int N = activeNotifications.size(); |
| for (int i = 0; i < N; i++) { |
| Entry entry = activeNotifications.get(i); |
| String key = entry.notification.getKey(); |
| boolean isVisible = mStackScroller.isInVisibleLocation(entry.row); |
| NotificationVisibility visObj = NotificationVisibility.obtain(key, i, isVisible); |
| boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(visObj); |
| if (isVisible) { |
| // Build new set of visible notifications. |
| mTmpCurrentlyVisibleNotifications.add(visObj); |
| if (!previouslyVisible) { |
| mTmpNewlyVisibleNotifications.add(visObj); |
| } |
| } else { |
| // release object |
| visObj.recycle(); |
| } |
| } |
| mTmpNoLongerVisibleNotifications.addAll(mCurrentlyVisibleNotifications); |
| mTmpNoLongerVisibleNotifications.removeAll(mTmpCurrentlyVisibleNotifications); |
| |
| logNotificationVisibilityChanges( |
| mTmpNewlyVisibleNotifications, mTmpNoLongerVisibleNotifications); |
| |
| recycleAllVisibilityObjects(mCurrentlyVisibleNotifications); |
| mCurrentlyVisibleNotifications.addAll(mTmpCurrentlyVisibleNotifications); |
| |
| recycleAllVisibilityObjects(mTmpNoLongerVisibleNotifications); |
| mTmpCurrentlyVisibleNotifications.clear(); |
| mTmpNewlyVisibleNotifications.clear(); |
| mTmpNoLongerVisibleNotifications.clear(); |
| } |
| }; |
| |
| private NotificationMessagingUtil mMessagingUtil; |
| private KeyguardUserSwitcher mKeyguardUserSwitcher; |
| private UserSwitcherController mUserSwitcherController; |
| private NetworkController mNetworkController; |
| private KeyguardMonitorImpl mKeyguardMonitor; |
| private BatteryController mBatteryController; |
| private boolean mPanelExpanded; |
| private LogMaker mStatusBarStateLog; |
| private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger(); |
| private NotificationIconAreaController mNotificationIconAreaController; |
| private ConfigurationListener mConfigurationListener; |
| private InflationExceptionHandler mInflationExceptionHandler = this::handleInflationException; |
| private boolean mReinflateNotificationsOnUserSwitched; |
| |
| private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) { |
| final int N = array.size(); |
| for (int i = 0 ; i < N; i++) { |
| array.valueAt(i).recycle(); |
| } |
| array.clear(); |
| } |
| |
| private final View.OnClickListener mGoToLockedShadeListener = v -> { |
| if (mState == StatusBarState.KEYGUARD) { |
| wakeUpIfDozing(SystemClock.uptimeMillis(), v); |
| goToLockedShade(null); |
| } |
| }; |
| private HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> mTmpChildOrderMap |
| = new HashMap<>(); |
| private RankingMap mLatestRankingMap; |
| private boolean mNoAnimationOnNextBarModeChange; |
| private FalsingManager mFalsingManager; |
| |
| private KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() { |
| @Override |
| public void onDreamingStateChanged(boolean dreaming) { |
| if (dreaming) { |
| maybeEscalateHeadsUp(); |
| } |
| } |
| }; |
| |
| private NavigationBarFragment mNavigationBar; |
| private View mNavigationBarView; |
| |
| @Override |
| public void start() { |
| mNetworkController = Dependency.get(NetworkController.class); |
| mUserSwitcherController = Dependency.get(UserSwitcherController.class); |
| mKeyguardMonitor = (KeyguardMonitorImpl) Dependency.get(KeyguardMonitor.class); |
| mBatteryController = Dependency.get(BatteryController.class); |
| mAssistManager = Dependency.get(AssistManager.class); |
| mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); |
| |
| mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); |
| mDisplay = mWindowManager.getDefaultDisplay(); |
| updateDisplaySize(); |
| mScrimSrcModeEnabled = mContext.getResources().getBoolean( |
| R.bool.config_status_bar_scrim_behind_use_src); |
| |
| DateTimeView.setReceiverHandler(Dependency.get(Dependency.TIME_TICK_HANDLER)); |
| putComponent(StatusBar.class, this); |
| |
| // start old BaseStatusBar.start(). |
| mWindowManagerService = WindowManagerGlobal.getWindowManagerService(); |
| mDevicePolicyManager = (DevicePolicyManager)mContext.getSystemService( |
| Context.DEVICE_POLICY_SERVICE); |
| |
| mNotificationData = new NotificationData(this); |
| mMessagingUtil = new NotificationMessagingUtil(mContext); |
| |
| mAccessibilityManager = (AccessibilityManager) |
| mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); |
| |
| mDreamManager = IDreamManager.Stub.asInterface( |
| ServiceManager.checkService(DreamService.DREAM_SERVICE)); |
| mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); |
| |
| mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); |
| mDeviceProvisionedController.addCallback(mDeviceProvisionedListener); |
| mContext.getContentResolver().registerContentObserver( |
| Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false, |
| mSettingsObserver); |
| mContext.getContentResolver().registerContentObserver( |
| Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), false, |
| mLockscreenSettingsObserver, |
| UserHandle.USER_ALL); |
| if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) { |
| mContext.getContentResolver().registerContentObserver( |
| Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT), |
| false, |
| mSettingsObserver, |
| UserHandle.USER_ALL); |
| } |
| |
| mContext.getContentResolver().registerContentObserver( |
| Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS), |
| true, |
| mLockscreenSettingsObserver, |
| UserHandle.USER_ALL); |
| |
| mBarService = IStatusBarService.Stub.asInterface( |
| ServiceManager.getService(Context.STATUS_BAR_SERVICE)); |
| |
| mRecents = getComponent(Recents.class); |
| |
| final Configuration currentConfig = mContext.getResources().getConfiguration(); |
| mLocale = currentConfig.locale; |
| mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale); |
| |
| mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); |
| mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); |
| mLockPatternUtils = new LockPatternUtils(mContext); |
| |
| // Connect in to the status bar manager service |
| mCommandQueue = getComponent(CommandQueue.class); |
| mCommandQueue.addCallbacks(this); |
| |
| int[] switches = new int[9]; |
| ArrayList<IBinder> binders = new ArrayList<IBinder>(); |
| ArrayList<String> iconSlots = new ArrayList<>(); |
| ArrayList<StatusBarIcon> icons = new ArrayList<>(); |
| Rect fullscreenStackBounds = new Rect(); |
| Rect dockedStackBounds = new Rect(); |
| try { |
| mBarService.registerStatusBar(mCommandQueue, iconSlots, icons, switches, binders, |
| fullscreenStackBounds, dockedStackBounds); |
| } catch (RemoteException ex) { |
| // If the system process isn't there we're doomed anyway. |
| } |
| |
| createAndAddWindows(); |
| |
| mSettingsObserver.onChange(false); // set up |
| mCommandQueue.disable(switches[0], switches[6], false /* animate */); |
| setSystemUiVisibility(switches[1], switches[7], switches[8], 0xffffffff, |
| fullscreenStackBounds, dockedStackBounds); |
| topAppWindowChanged(switches[2] != 0); |
| // StatusBarManagerService has a back up of IME token and it's restored here. |
| setImeWindowStatus(binders.get(0), switches[3], switches[4], switches[5] != 0); |
| |
| // Set up the initial icon state |
| int N = iconSlots.size(); |
| int viewIndex = 0; |
| for (int i=0; i < N; i++) { |
| mCommandQueue.setIcon(iconSlots.get(i), icons.get(i)); |
| } |
| |
| // Set up the initial notification state. |
| try { |
| mNotificationListener.registerAsSystemService(mContext, |
| new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()), |
| UserHandle.USER_ALL); |
| } catch (RemoteException e) { |
| Log.e(TAG, "Unable to register notification listener", e); |
| } |
| |
| |
| if (DEBUG) { |
| Log.d(TAG, String.format( |
| "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x", |
| icons.size(), |
| switches[0], |
| switches[1], |
| switches[2], |
| switches[3] |
| )); |
| } |
| |
| mCurrentUserId = ActivityManager.getCurrentUser(); |
| setHeadsUpUser(mCurrentUserId); |
| |
| IntentFilter filter = new IntentFilter(); |
| filter.addAction(Intent.ACTION_USER_SWITCHED); |
| filter.addAction(Intent.ACTION_USER_ADDED); |
| filter.addAction(Intent.ACTION_USER_PRESENT); |
| mContext.registerReceiver(mBaseBroadcastReceiver, filter); |
| |
| IntentFilter internalFilter = new IntentFilter(); |
| internalFilter.addAction(NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION); |
| internalFilter.addAction(BANNER_ACTION_CANCEL); |
| internalFilter.addAction(BANNER_ACTION_SETUP); |
| mContext.registerReceiver(mBaseBroadcastReceiver, internalFilter, PERMISSION_SELF, null); |
| |
| IntentFilter allUsersFilter = new IntentFilter(); |
| allUsersFilter.addAction( |
| DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); |
| allUsersFilter.addAction(Intent.ACTION_DEVICE_LOCKED_CHANGED); |
| mContext.registerReceiverAsUser(mAllUsersReceiver, UserHandle.ALL, allUsersFilter, |
| null, null); |
| updateCurrentProfilesCache(); |
| |
| IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService( |
| Context.VR_SERVICE)); |
| try { |
| vrManager.registerListener(mVrStateCallbacks); |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Failed to register VR mode state listener: " + e); |
| } |
| |
| mNonBlockablePkgs = new HashSet<String>(); |
| Collections.addAll(mNonBlockablePkgs, mContext.getResources().getStringArray( |
| com.android.internal.R.array.config_nonBlockableNotificationPackages)); |
| // end old BaseStatusBar.start(). |
| |
| mMediaSessionManager |
| = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE); |
| // TODO: use MediaSessionManager.SessionListener to hook us up to future updates |
| // in session state |
| |
| // Lastly, call to the icon policy to install/update all the icons. |
| mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController); |
| mSettingsObserver.onChange(false); // set up |
| |
| mHeadsUpObserver.onChange(true); // set up |
| if (ENABLE_HEADS_UP) { |
| mContext.getContentResolver().registerContentObserver( |
| Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED), true, |
| mHeadsUpObserver); |
| mContext.getContentResolver().registerContentObserver( |
| Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true, |
| mHeadsUpObserver); |
| } |
| mUnlockMethodCache = UnlockMethodCache.getInstance(mContext); |
| mUnlockMethodCache.addListener(this); |
| startKeyguard(); |
| |
| KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateCallback); |
| mDozeServiceHost = new DozeServiceHost(); |
| putComponent(DozeHost.class, mDozeServiceHost); |
| |
| notifyUserAboutHiddenNotifications(); |
| |
| mScreenPinningRequest = new ScreenPinningRequest(mContext); |
| mFalsingManager = FalsingManager.getInstance(mContext); |
| |
| Dependency.get(ActivityStarterDelegate.class).setActivityStarterImpl(this); |
| |
| mConfigurationListener = new ConfigurationListener() { |
| @Override |
| public void onConfigChanged(Configuration newConfig) { |
| StatusBar.this.onConfigurationChanged(newConfig); |
| } |
| |
| @Override |
| public void onDensityOrFontScaleChanged() { |
| StatusBar.this.onDensityOrFontScaleChanged(); |
| } |
| }; |
| Dependency.get(ConfigurationController.class).addCallback(mConfigurationListener); |
| } |
| |
| protected void createIconController() { |
| } |
| |
| // ================================================================================ |
| // Constructing the view |
| // ================================================================================ |
| protected void makeStatusBarView() { |
| final Context context = mContext; |
| updateDisplaySize(); // populates mDisplayMetrics |
| updateResources(); |
| |
| inflateStatusBarWindow(context); |
| mStatusBarWindow.setService(this); |
| mStatusBarWindow.setOnTouchListener(getStatusBarWindowTouchListener()); |
| |
| // TODO: Deal with the ugliness that comes from having some of the statusbar broken out |
| // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot. |
| mNotificationPanel = (NotificationPanelView) mStatusBarWindow.findViewById( |
| R.id.notification_panel); |
| mStackScroller = (NotificationStackScrollLayout) mStatusBarWindow.findViewById( |
| R.id.notification_stack_scroller); |
| mNotificationPanel.setStatusBar(this); |
| mNotificationPanel.setGroupManager(mGroupManager); |
| mKeyguardStatusBar = (KeyguardStatusBarView) mStatusBarWindow.findViewById(R.id.keyguard_header); |
| |
| mNotificationIconAreaController = SystemUIFactory.getInstance() |
| .createNotificationIconAreaController(context, this); |
| inflateShelf(); |
| mNotificationIconAreaController.setupShelf(mNotificationShelf); |
| Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mNotificationIconAreaController); |
| FragmentHostManager.get(mStatusBarWindow) |
| .addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> { |
| CollapsedStatusBarFragment statusBarFragment = |
| (CollapsedStatusBarFragment) fragment; |
| statusBarFragment.initNotificationIconArea(mNotificationIconAreaController); |
| mStatusBarView = (PhoneStatusBarView) fragment.getView(); |
| mStatusBarView.setBar(this); |
| mStatusBarView.setPanel(mNotificationPanel); |
| mStatusBarView.setScrimController(mScrimController); |
| setAreThereNotifications(); |
| checkBarModes(); |
| }).getFragmentManager() |
| .beginTransaction() |
| .replace(R.id.status_bar_container, new CollapsedStatusBarFragment(), |
| CollapsedStatusBarFragment.TAG) |
| .commit(); |
| Dependency.get(StatusBarIconController.class).addIconGroup( |
| new IconManager((ViewGroup) mKeyguardStatusBar.findViewById(R.id.statusIcons))); |
| mIconController = Dependency.get(StatusBarIconController.class); |
| |
| if (!ActivityManager.isHighEndGfx()) { |
| mStatusBarWindow.setBackground(null); |
| mNotificationPanel.setBackground(new FastColorDrawable(context.getColor( |
| R.color.notification_panel_solid_background))); |
| } |
| |
| mHeadsUpManager = new HeadsUpManager(context, mStatusBarWindow, mGroupManager); |
| mHeadsUpManager.setBar(this); |
| mHeadsUpManager.addListener(this); |
| mHeadsUpManager.addListener(mNotificationPanel); |
| mHeadsUpManager.addListener(mGroupManager); |
| mHeadsUpManager.addListener(mVisualStabilityManager); |
| mNotificationPanel.setHeadsUpManager(mHeadsUpManager); |
| mNotificationData.setHeadsUpManager(mHeadsUpManager); |
| mGroupManager.setHeadsUpManager(mHeadsUpManager); |
| mHeadsUpManager.setVisualStabilityManager(mVisualStabilityManager); |
| |
| if (MULTIUSER_DEBUG) { |
| mNotificationPanelDebugText = (TextView) mNotificationPanel.findViewById( |
| R.id.header_debug_info); |
| mNotificationPanelDebugText.setVisibility(View.VISIBLE); |
| } |
| |
| try { |
| boolean showNav = mWindowManagerService.hasNavigationBar(); |
| if (DEBUG) Log.v(TAG, "hasNavigationBar=" + showNav); |
| if (showNav) { |
| createNavigationBar(); |
| } |
| } catch (RemoteException ex) { |
| // no window manager? good luck with that |
| } |
| |
| // figure out which pixel-format to use for the status bar. |
| mPixelFormat = PixelFormat.OPAQUE; |
| |
| mStackScroller.setLongPressListener(getNotificationLongClicker()); |
| mStackScroller.setStatusBar(this); |
| mStackScroller.setGroupManager(mGroupManager); |
| mStackScroller.setHeadsUpManager(mHeadsUpManager); |
| mGroupManager.setOnGroupChangeListener(mStackScroller); |
| mVisualStabilityManager.setVisibilityLocationProvider(mStackScroller); |
| |
| inflateEmptyShadeView(); |
| inflateDismissView(); |
| mExpandedContents = mStackScroller; |
| |
| mBackdrop = (BackDropView) mStatusBarWindow.findViewById(R.id.backdrop); |
| mBackdropFront = (ImageView) mBackdrop.findViewById(R.id.backdrop_front); |
| mBackdropBack = (ImageView) mBackdrop.findViewById(R.id.backdrop_back); |
| |
| if (ENABLE_LOCKSCREEN_WALLPAPER) { |
| mLockscreenWallpaper = new LockscreenWallpaper(mContext, this, mHandler); |
| } |
| |
| mKeyguardStatusView = |
| (KeyguardStatusView) mStatusBarWindow.findViewById(R.id.keyguard_status_view); |
| mKeyguardBottomArea = |
| (KeyguardBottomAreaView) mStatusBarWindow.findViewById(R.id.keyguard_bottom_area); |
| mKeyguardIndicationController = |
| SystemUIFactory.getInstance().createKeyguardIndicationController(mContext, |
| (ViewGroup) mStatusBarWindow.findViewById(R.id.keyguard_indication_area), |
| mKeyguardBottomArea.getLockIcon()); |
| mKeyguardBottomArea.setKeyguardIndicationController(mKeyguardIndicationController); |
| |
| // set the initial view visibility |
| setAreThereNotifications(); |
| |
| // TODO: Find better place for this callback. |
| mBatteryController.addCallback(new BatteryStateChangeCallback() { |
| @Override |
| public void onPowerSaveChanged(boolean isPowerSave) { |
| mHandler.post(mCheckBarModes); |
| if (mDozeServiceHost != null) { |
| mDozeServiceHost.firePowerSaveChanged(isPowerSave); |
| } |
| } |
| |
| @Override |
| public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { |
| // noop |
| } |
| }); |
| |
| mLightBarController = new LightBarController(); |
| if (mNavigationBar != null) { |
| mNavigationBar.setLightBarController(mLightBarController); |
| } |
| |
| ScrimView scrimBehind = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_behind); |
| ScrimView scrimInFront = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_in_front); |
| View headsUpScrim = mStatusBarWindow.findViewById(R.id.heads_up_scrim); |
| mScrimController = SystemUIFactory.getInstance().createScrimController(mLightBarController, |
| scrimBehind, scrimInFront, headsUpScrim, mLockscreenWallpaper); |
| if (mScrimSrcModeEnabled) { |
| Runnable runnable = new Runnable() { |
| @Override |
| public void run() { |
| boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE; |
| mScrimController.setDrawBehindAsSrc(asSrc); |
| mStackScroller.setDrawBackgroundAsSrc(asSrc); |
| } |
| }; |
| mBackdrop.setOnVisibilityChangedRunnable(runnable); |
| runnable.run(); |
| } |
| mHeadsUpManager.addListener(mScrimController); |
| mStackScroller.setScrimController(mScrimController); |
| mDozeScrimController = new DozeScrimController(mScrimController, context); |
| |
| // Other icons |
| mVolumeComponent = getComponent(VolumeComponent.class); |
| |
| mKeyguardBottomArea.setStatusBar(this); |
| mKeyguardBottomArea.setUserSetupComplete(mUserSetup); |
| if (UserManager.get(mContext).isUserSwitcherEnabled()) { |
| createUserSwitcher(); |
| } |
| |
| // Set up the quick settings tile panel |
| View container = mStatusBarWindow.findViewById(R.id.qs_frame); |
| if (container != null) { |
| FragmentHostManager fragmentHostManager = FragmentHostManager.get(container); |
| fragmentHostManager.getFragmentManager().beginTransaction() |
| .replace(R.id.qs_frame, new QSFragment(), QS.TAG) |
| .commit(); |
| new PluginFragmentListener(container, QS.TAG, QSFragment.class, QS.class) |
| .startListening(); |
| final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this, |
| mIconController); |
| mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow); |
| fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> { |
| QS qs = (QS) f; |
| if (qs instanceof QSFragment) { |
| ((QSFragment) qs).setHost(qsh); |
| mQSPanel = ((QSFragment) qs).getQsPanel(); |
| mQSPanel.setBrightnessMirror(mBrightnessMirrorController); |
| mKeyguardStatusBar.setQSPanel(mQSPanel); |
| } |
| }); |
| } |
| |
| mReportRejectedTouch = mStatusBarWindow.findViewById(R.id.report_rejected_touch); |
| if (mReportRejectedTouch != null) { |
| updateReportRejectedTouchVisibility(); |
| mReportRejectedTouch.setOnClickListener(v -> { |
| Uri session = mFalsingManager.reportRejectedTouch(); |
| if (session == null) { return; } |
| |
| StringWriter message = new StringWriter(); |
| message.write("Build info: "); |
| message.write(SystemProperties.get("ro.build.description")); |
| message.write("\nSerial number: "); |
| message.write(SystemProperties.get("ro.serialno")); |
| message.write("\n"); |
| |
| PrintWriter falsingPw = new PrintWriter(message); |
| FalsingLog.dump(falsingPw); |
| falsingPw.flush(); |
| |
| startActivityDismissingKeyguard(Intent.createChooser(new Intent(Intent.ACTION_SEND) |
| .setType("*/*") |
| .putExtra(Intent.EXTRA_SUBJECT, "Rejected touch report") |
| .putExtra(Intent.EXTRA_STREAM, session) |
| .putExtra(Intent.EXTRA_TEXT, message.toString()), |
| "Share rejected touch report") |
| .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), |
| true /* onlyProvisioned */, true /* dismissShade */); |
| }); |
| } |
| |
| |
| PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); |
| if (!pm.isScreenOn()) { |
| mBroadcastReceiver.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF)); |
| } |
| mGestureWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, |
| "GestureWakeLock"); |
| mVibrator = mContext.getSystemService(Vibrator.class); |
| int[] pattern = mContext.getResources().getIntArray( |
| R.array.config_cameraLaunchGestureVibePattern); |
| mCameraLaunchGestureVibePattern = new long[pattern.length]; |
| for (int i = 0; i < pattern.length; i++) { |
| mCameraLaunchGestureVibePattern[i] = pattern[i]; |
| } |
| |
| // receive broadcasts |
| IntentFilter filter = new IntentFilter(); |
| filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); |
| filter.addAction(Intent.ACTION_SCREEN_OFF); |
| filter.addAction(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG); |
| context.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null); |
| |
| IntentFilter demoFilter = new IntentFilter(); |
| if (DEBUG_MEDIA_FAKE_ARTWORK) { |
| demoFilter.addAction(ACTION_FAKE_ARTWORK); |
| } |
| demoFilter.addAction(ACTION_DEMO); |
| context.registerReceiverAsUser(mDemoReceiver, UserHandle.ALL, demoFilter, |
| android.Manifest.permission.DUMP, null); |
| |
| // listen for USER_SETUP_COMPLETE setting (per-user) |
| mDeviceProvisionedController.addCallback(mUserSetupObserver); |
| mUserSetupObserver.onUserSetupChanged(); |
| |
| // disable profiling bars, since they overlap and clutter the output on app windows |
| ThreadedRenderer.overrideProperty("disableProfileBars", "true"); |
| |
| // Private API call to make the shadows look better for Recents |
| ThreadedRenderer.overrideProperty("ambientRatio", String.valueOf(1.5f)); |
| } |
| |
| protected void createNavigationBar() { |
| mNavigationBarView = NavigationBarFragment.create(mContext, (tag, fragment) -> { |
| mNavigationBar = (NavigationBarFragment) fragment; |
| if (mLightBarController != null) { |
| mNavigationBar.setLightBarController(mLightBarController); |
| } |
| mNavigationBar.setCurrentSysuiVisibility(mSystemUiVisibility); |
| }); |
| } |
| |
| /** |
| * Returns the {@link android.view.View.OnTouchListener} that will be invoked when the |
| * background window of the status bar is clicked. |
| */ |
| protected View.OnTouchListener getStatusBarWindowTouchListener() { |
| return (v, event) -> { |
| checkUserAutohide(v, event); |
| checkRemoteInputOutside(event); |
| if (event.getAction() == MotionEvent.ACTION_DOWN) { |
| if (mExpandedVisible) { |
| animateCollapsePanels(); |
| } |
| } |
| return mStatusBarWindow.onTouchEvent(event); |
| }; |
| } |
| |
| private void inflateShelf() { |
| mNotificationShelf = |
| (NotificationShelf) LayoutInflater.from(mContext).inflate( |
| R.layout.status_bar_notification_shelf, mStackScroller, false); |
| mNotificationShelf.setOnActivatedListener(this); |
| mStackScroller.setShelf(mNotificationShelf); |
| mNotificationShelf.setOnClickListener(mGoToLockedShadeListener); |
| mNotificationShelf.setStatusBarState(mState); |
| } |
| |
| protected void onDensityOrFontScaleChanged() { |
| // start old BaseStatusBar.onDensityOrFontScaleChanged(). |
| if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) { |
| updateNotificationsOnDensityOrFontScaleChanged(); |
| } else { |
| mReinflateNotificationsOnUserSwitched = true; |
| } |
| // end old BaseStatusBar.onDensityOrFontScaleChanged(). |
| mScrimController.onDensityOrFontScaleChanged(); |
| // TODO: Remove this. |
| if (mStatusBarView != null) mStatusBarView.onDensityOrFontScaleChanged(); |
| if (mBrightnessMirrorController != null) { |
| mBrightnessMirrorController.onDensityOrFontScaleChanged(); |
| } |
| inflateSignalClusters(); |
| mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext); |
| inflateDismissView(); |
| updateClearAll(); |
| inflateEmptyShadeView(); |
| updateEmptyShadeView(); |
| mStatusBarKeyguardViewManager.onDensityOrFontScaleChanged(); |
| // TODO: Bring these out of StatusBar. |
| ((UserInfoControllerImpl) Dependency.get(UserInfoController.class)) |
| .onDensityOrFontScaleChanged(); |
| Dependency.get(UserSwitcherController.class).onDensityOrFontScaleChanged(); |
| if (mKeyguardUserSwitcher != null) { |
| mKeyguardUserSwitcher.onDensityOrFontScaleChanged(); |
| } |
| } |
| |
| private void updateNotificationsOnDensityOrFontScaleChanged() { |
| ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications(); |
| for (int i = 0; i < activeNotifications.size(); i++) { |
| Entry entry = activeNotifications.get(i); |
| boolean exposedGuts = mNotificationGutsExposed != null |
| && entry.row.getGuts() == mNotificationGutsExposed; |
| entry.row.onDensityOrFontScaleChanged(); |
| if (exposedGuts) { |
| mNotificationGutsExposed = entry.row.getGuts(); |
| bindGuts(entry.row, mGutsMenuItem); |
| } |
| } |
| } |
| |
| private void inflateSignalClusters() { |
| reinflateSignalCluster(mKeyguardStatusBar); |
| } |
| |
| public static SignalClusterView reinflateSignalCluster(View view) { |
| Context context = view.getContext(); |
| SignalClusterView signalCluster = |
| (SignalClusterView) view.findViewById(R.id.signal_cluster); |
| if (signalCluster != null) { |
| ViewParent parent = signalCluster.getParent(); |
| if (parent instanceof ViewGroup) { |
| ViewGroup viewParent = (ViewGroup) parent; |
| int index = viewParent.indexOfChild(signalCluster); |
| viewParent.removeView(signalCluster); |
| SignalClusterView newCluster = (SignalClusterView) LayoutInflater.from(context) |
| .inflate(R.layout.signal_cluster_view, viewParent, false); |
| ViewGroup.MarginLayoutParams layoutParams = |
| (ViewGroup.MarginLayoutParams) viewParent.getLayoutParams(); |
| layoutParams.setMarginsRelative( |
| context.getResources().getDimensionPixelSize( |
| R.dimen.signal_cluster_margin_start), |
| 0, 0, 0); |
| newCluster.setLayoutParams(layoutParams); |
| viewParent.addView(newCluster, index); |
| return newCluster; |
| } |
| return signalCluster; |
| } |
| return null; |
| } |
| |
| private void inflateEmptyShadeView() { |
| mEmptyShadeView = (EmptyShadeView) LayoutInflater.from(mContext).inflate( |
| R.layout.status_bar_no_notifications, mStackScroller, false); |
| mStackScroller.setEmptyShadeView(mEmptyShadeView); |
| } |
| |
| private void inflateDismissView() { |
| // Always inflate with a dark theme, since this sits on the scrim. |
| ContextThemeWrapper themedContext = new ContextThemeWrapper(mContext, |
| style.Theme_DeviceDefault); |
| mDismissView = (DismissView) LayoutInflater.from(themedContext).inflate( |
| R.layout.status_bar_notification_dismiss_all, mStackScroller, false); |
| mDismissView.setOnButtonClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES); |
| clearAllNotifications(); |
| } |
| }); |
| mStackScroller.setDismissView(mDismissView); |
| } |
| |
| protected void createUserSwitcher() { |
| mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext, |
| (ViewStub) mStatusBarWindow.findViewById(R.id.keyguard_user_switcher), |
| mKeyguardStatusBar, mNotificationPanel); |
| } |
| |
| protected void inflateStatusBarWindow(Context context) { |
| mStatusBarWindow = (StatusBarWindowView) View.inflate(context, |
| R.layout.super_status_bar, null); |
| } |
| |
| public void clearAllNotifications() { |
| |
| // animate-swipe all dismissable notifications, then animate the shade closed |
| int numChildren = mStackScroller.getChildCount(); |
| |
| final ArrayList<View> viewsToHide = new ArrayList<View>(numChildren); |
| for (int i = 0; i < numChildren; i++) { |
| final View child = mStackScroller.getChildAt(i); |
| if (child instanceof ExpandableNotificationRow) { |
| if (mStackScroller.canChildBeDismissed(child)) { |
| if (child.getVisibility() == View.VISIBLE) { |
| viewsToHide.add(child); |
| } |
| } |
| ExpandableNotificationRow row = (ExpandableNotificationRow) child; |
| List<ExpandableNotificationRow> children = row.getNotificationChildren(); |
| if (row.areChildrenExpanded() && children != null) { |
| for (ExpandableNotificationRow childRow : children) { |
| if (mStackScroller.canChildBeDismissed(childRow)) { |
| if (childRow.getVisibility() == View.VISIBLE) { |
| viewsToHide.add(childRow); |
| } |
| } |
| } |
| } |
| } |
| } |
| if (viewsToHide.isEmpty()) { |
| animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); |
| return; |
| } |
| |
| addPostCollapseAction(new Runnable() { |
| @Override |
| public void run() { |
| mStackScroller.setDismissAllInProgress(false); |
| try { |
| mBarService.onClearAllNotifications(mCurrentUserId); |
| } catch (Exception ex) { } |
| } |
| }); |
| |
| performDismissAllAnimations(viewsToHide); |
| |
| } |
| |
| private void performDismissAllAnimations(ArrayList<View> hideAnimatedList) { |
| Runnable animationFinishAction = new Runnable() { |
| @Override |
| public void run() { |
| animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); |
| } |
| }; |
| |
| // let's disable our normal animations |
| mStackScroller.setDismissAllInProgress(true); |
| |
| // Decrease the delay for every row we animate to give the sense of |
| // accelerating the swipes |
| int rowDelayDecrement = 10; |
| int currentDelay = 140; |
| int totalDelay = 180; |
| int numItems = hideAnimatedList.size(); |
| for (int i = numItems - 1; i >= 0; i--) { |
| View view = hideAnimatedList.get(i); |
| Runnable endRunnable = null; |
| if (i == 0) { |
| endRunnable = animationFinishAction; |
| } |
| mStackScroller.dismissViewAnimated(view, endRunnable, totalDelay, 260); |
| currentDelay = Math.max(50, currentDelay - rowDelayDecrement); |
| totalDelay += currentDelay; |
| } |
| } |
| |
| protected void setZenMode(int mode) { |
| // start old BaseStatusBar.setZenMode(). |
| if (isDeviceProvisioned()) { |
| mZenMode = mode; |
| updateNotifications(); |
| } |
| // end old BaseStatusBar.setZenMode(). |
| } |
| |
| protected void startKeyguard() { |
| Trace.beginSection("StatusBar#startKeyguard"); |
| KeyguardViewMediator keyguardViewMediator = getComponent(KeyguardViewMediator.class); |
| mFingerprintUnlockController = new FingerprintUnlockController(mContext, |
| mDozeScrimController, keyguardViewMediator, |
| mScrimController, this, UnlockMethodCache.getInstance(mContext)); |
| mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this, |
| getBouncerContainer(), mScrimController, |
| mFingerprintUnlockController); |
| mKeyguardIndicationController.setStatusBarKeyguardViewManager( |
| mStatusBarKeyguardViewManager); |
| mKeyguardIndicationController.setUserInfoController( |
| Dependency.get(UserInfoController.class)); |
| mFingerprintUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager); |
| mRemoteInputController.addCallback(mStatusBarKeyguardViewManager); |
| |
| mRemoteInputController.addCallback(new RemoteInputController.Callback() { |
| @Override |
| public void onRemoteInputSent(Entry entry) { |
| if (FORCE_REMOTE_INPUT_HISTORY && mKeysKeptForRemoteInput.contains(entry.key)) { |
| removeNotification(entry.key, null); |
| } else if (mRemoteInputEntriesToRemoveOnCollapse.contains(entry)) { |
| // We're currently holding onto this notification, but from the apps point of |
| // view it is already canceled, so we'll need to cancel it on the apps behalf |
| // after sending - unless the app posts an update in the mean time, so wait a |
| // bit. |
| mHandler.postDelayed(() -> { |
| if (mRemoteInputEntriesToRemoveOnCollapse.remove(entry)) { |
| removeNotification(entry.key, null); |
| } |
| }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY); |
| } |
| } |
| }); |
| |
| mKeyguardViewMediatorCallback = keyguardViewMediator.getViewMediatorCallback(); |
| mLightBarController.setFingerprintUnlockController(mFingerprintUnlockController); |
| Trace.endSection(); |
| } |
| |
| protected View getStatusBarView() { |
| return mStatusBarView; |
| } |
| |
| public StatusBarWindowView getStatusBarWindow() { |
| return mStatusBarWindow; |
| } |
| |
| protected ViewGroup getBouncerContainer() { |
| return mStatusBarWindow; |
| } |
| |
| public int getStatusBarHeight() { |
| if (mNaturalBarHeight < 0) { |
| final Resources res = mContext.getResources(); |
| mNaturalBarHeight = |
| res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); |
| } |
| return mNaturalBarHeight; |
| } |
| |
| protected boolean toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction) { |
| if (mRecents == null) { |
| return false; |
| } |
| int dockSide = WindowManagerProxy.getInstance().getDockSide(); |
| if (dockSide == WindowManager.DOCKED_INVALID) { |
| return mRecents.dockTopTask(NavigationBarGestureHelper.DRAG_MODE_NONE, |
| ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, null, metricsDockAction); |
| } else { |
| Divider divider = getComponent(Divider.class); |
| if (divider != null && divider.isMinimized() && !divider.isHomeStackResizable()) { |
| // Undocking from the minimized state is not supported |
| return false; |
| } else { |
| EventBus.getDefault().send(new UndockingTaskEvent()); |
| if (metricsUndockAction != -1) { |
| mMetricsLogger.action(metricsUndockAction); |
| } |
| } |
| } |
| return true; |
| } |
| |
| void awakenDreams() { |
| if (mDreamManager != null) { |
| try { |
| mDreamManager.awaken(); |
| } catch (RemoteException e) { |
| // fine, stay asleep then |
| } |
| } |
| } |
| |
| public UserHandle getCurrentUserHandle() { |
| return new UserHandle(mCurrentUserId); |
| } |
| |
| public void addNotification(StatusBarNotification notification, RankingMap ranking, |
| Entry oldEntry) throws InflationException { |
| if (DEBUG) Log.d(TAG, "addNotification key=" + notification.getKey()); |
| |
| mNotificationData.updateRanking(ranking); |
| Entry shadeEntry = createNotificationViews(notification); |
| boolean isHeadsUped = shouldPeek(shadeEntry); |
| if (isHeadsUped) { |
| mHeadsUpManager.showNotification(shadeEntry); |
| // Mark as seen immediately |
| setNotificationShown(notification); |
| } |
| |
| if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) { |
| if (shouldSuppressFullScreenIntent(notification.getKey())) { |
| if (DEBUG) { |
| Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + notification.getKey()); |
| } |
| } else if (mNotificationData.getImportance(notification.getKey()) |
| < NotificationManager.IMPORTANCE_HIGH) { |
| if (DEBUG) { |
| Log.d(TAG, "No Fullscreen intent: not important enough: " |
| + notification.getKey()); |
| } |
| } else { |
| // Stop screensaver if the notification has a full-screen intent. |
| // (like an incoming phone call) |
| awakenDreams(); |
| |
| // not immersive & a full-screen alert should be shown |
| if (DEBUG) |
| Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent"); |
| try { |
| EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION, |
| notification.getKey()); |
| notification.getNotification().fullScreenIntent.send(); |
| shadeEntry.notifyFullScreenIntentLaunched(); |
| mMetricsLogger.count("note_fullscreen", 1); |
| } catch (PendingIntent.CanceledException e) { |
| } |
| } |
| } |
| addNotificationViews(shadeEntry, ranking); |
| // Recalculate the position of the sliding windows and the titles. |
| setAreThereNotifications(); |
| } |
| |
| public void handleInflationException(StatusBarNotification notification, InflationException e) { |
| handleNotificationError(notification, e.getMessage()); |
| } |
| |
| private boolean shouldSuppressFullScreenIntent(String key) { |
| if (isDeviceInVrMode()) { |
| return true; |
| } |
| |
| if (mPowerManager.isInteractive()) { |
| return mNotificationData.shouldSuppressScreenOn(key); |
| } else { |
| return mNotificationData.shouldSuppressScreenOff(key); |
| } |
| } |
| |
| protected void updateNotificationRanking(RankingMap ranking) { |
| mNotificationData.updateRanking(ranking); |
| updateNotifications(); |
| } |
| |
| public void removeNotification(String key, RankingMap ranking) { |
| boolean deferRemoval = false; |
| if (mHeadsUpManager.isHeadsUp(key)) { |
| // A cancel() in repsonse to a remote input shouldn't be delayed, as it makes the |
| // sending look longer than it takes. |
| // Also we should not defer the removal if reordering isn't allowed since otherwise |
| // some notifications can't disappear before the panel is closed. |
| boolean ignoreEarliestRemovalTime = mRemoteInputController.isSpinning(key) |
| && !FORCE_REMOTE_INPUT_HISTORY |
| || !mVisualStabilityManager.isReorderingAllowed(); |
| deferRemoval = !mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime); |
| } |
| if (key.equals(mMediaNotificationKey)) { |
| clearCurrentMediaNotification(); |
| updateMediaMetaData(true, true); |
| } |
| if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputController.isSpinning(key)) { |
| Entry entry = mNotificationData.get(key); |
| StatusBarNotification sbn = entry.notification; |
| |
| Notification.Builder b = Notification.Builder |
| .recoverBuilder(mContext, sbn.getNotification().clone()); |
| CharSequence[] oldHistory = sbn.getNotification().extras |
| .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY); |
| CharSequence[] newHistory; |
| if (oldHistory == null) { |
| newHistory = new CharSequence[1]; |
| } else { |
| newHistory = new CharSequence[oldHistory.length + 1]; |
| for (int i = 0; i < oldHistory.length; i++) { |
| newHistory[i + 1] = oldHistory[i]; |
| } |
| } |
| newHistory[0] = String.valueOf(entry.remoteInputText); |
| b.setRemoteInputHistory(newHistory); |
| |
| Notification newNotification = b.build(); |
| |
| // Undo any compatibility view inflation |
| newNotification.contentView = sbn.getNotification().contentView; |
| newNotification.bigContentView = sbn.getNotification().bigContentView; |
| newNotification.headsUpContentView = sbn.getNotification().headsUpContentView; |
| |
| StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(), |
| sbn.getOpPkg(), |
| sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(), |
| newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime()); |
| boolean updated = false; |
| try { |
| updateNotification(newSbn, null); |
| updated = true; |
| } catch (InflationException e) { |
| deferRemoval = false; |
| } |
| if (updated) { |
| mKeysKeptForRemoteInput.add(entry.key); |
| return; |
| } |
| } |
| if (deferRemoval) { |
| mLatestRankingMap = ranking; |
| mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key)); |
| return; |
| } |
| Entry entry = mNotificationData.get(key); |
| |
| if (entry != null && mRemoteInputController.isRemoteInputActive(entry) |
| && (entry.row != null && !entry.row.isDismissed())) { |
| mLatestRankingMap = ranking; |
| mRemoteInputEntriesToRemoveOnCollapse.add(entry); |
| return; |
| } |
| |
| if (entry != null && entry.row != null) { |
| entry.row.setRemoved(); |
| mStackScroller.cleanUpViewState(entry.row); |
| } |
| // Let's remove the children if this was a summary |
| handleGroupSummaryRemoved(key, ranking); |
| StatusBarNotification old = removeNotificationViews(key, ranking); |
| if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old); |
| |
| if (old != null) { |
| if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications() |
| && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) { |
| if (mState == StatusBarState.SHADE) { |
| animateCollapsePanels(); |
| } else if (mState == StatusBarState.SHADE_LOCKED && !isCollapsing()) { |
| goToKeyguard(); |
| } |
| } |
| } |
| setAreThereNotifications(); |
| } |
| |
| /** |
| * Ensures that the group children are cancelled immediately when the group summary is cancelled |
| * instead of waiting for the notification manager to send all cancels. Otherwise this could |
| * lead to flickers. |
| * |
| * This also ensures that the animation looks nice and only consists of a single disappear |
| * animation instead of multiple. |
| * |
| * @param key the key of the notification was removed |
| * @param ranking the current ranking |
| */ |
| private void handleGroupSummaryRemoved(String key, |
| RankingMap ranking) { |
| Entry entry = mNotificationData.get(key); |
| if (entry != null && entry.row != null |
| && entry.row.isSummaryWithChildren()) { |
| if (entry.notification.getOverrideGroupKey() != null && !entry.row.isDismissed()) { |
| // We don't want to remove children for autobundled notifications as they are not |
| // always cancelled. We only remove them if they were dismissed by the user. |
| return; |
| } |
| List<ExpandableNotificationRow> notificationChildren = |
| entry.row.getNotificationChildren(); |
| ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>(); |
| for (int i = 0; i < notificationChildren.size(); i++) { |
| ExpandableNotificationRow row = notificationChildren.get(i); |
| if ((row.getStatusBarNotification().getNotification().flags |
| & Notification.FLAG_FOREGROUND_SERVICE) != 0) { |
| // the child is a forground service notification which we can't remove! |
| continue; |
| } |
| toRemove.add(row); |
| toRemove.get(i).setKeepInParent(true); |
| // we need to set this state earlier as otherwise we might generate some weird |
| // animations |
| toRemove.get(i).setRemoved(); |
| } |
| } |
| } |
| |
| protected void performRemoveNotification(StatusBarNotification n) { |
| Entry entry = mNotificationData.get(n.getKey()); |
| if (mRemoteInputController.isRemoteInputActive(entry)) { |
| mRemoteInputController.removeRemoteInput(entry, null); |
| } |
| // start old BaseStatusBar.performRemoveNotification. |
| final String pkg = n.getPackageName(); |
| final String tag = n.getTag(); |
| final int id = n.getId(); |
| final int userId = n.getUserId(); |
| try { |
| mBarService.onNotificationClear(pkg, tag, id, userId); |
| if (FORCE_REMOTE_INPUT_HISTORY |
| && mKeysKeptForRemoteInput.contains(n.getKey())) { |
| mKeysKeptForRemoteInput.remove(n.getKey()); |
| } |
| removeNotification(n.getKey(), null); |
| |
| } catch (RemoteException ex) { |
| // system process is dead if we're here. |
| } |
| // end old BaseStatusBar.performRemoveNotification. |
| } |
| |
| private void updateNotificationShade() { |
| if (mStackScroller == null) return; |
| |
| // Do not modify the notifications during collapse. |
| if (isCollapsing()) { |
| addPostCollapseAction(new Runnable() { |
| @Override |
| public void run() { |
| updateNotificationShade(); |
| } |
| }); |
| return; |
| } |
| |
| ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications(); |
| ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size()); |
| final int N = activeNotifications.size(); |
| for (int i=0; i<N; i++) { |
| Entry ent = activeNotifications.get(i); |
| if (ent.row.isDismissed() || ent.row.isRemoved()) { |
| // we don't want to update removed notifications because they could |
| // temporarily become children if they were isolated before. |
| continue; |
| } |
| int vis = ent.notification.getNotification().visibility; |
| int userId = ent.notification.getUserId(); |
| |
| // Display public version of the notification if we need to redact. |
| boolean deviceSensitive = (isLockscreenPublicMode(mCurrentUserId) |
| && !userAllowsPrivateNotificationsInPublic(mCurrentUserId)); |
| boolean userSensitive = deviceSensitive || (isLockscreenPublicMode(userId) |
| && !userAllowsPrivateNotificationsInPublic(userId)); |
| boolean sensitiveNote = vis == Notification.VISIBILITY_PRIVATE; |
| boolean sensitivePackage = packageHasVisibilityOverride(ent.notification.getKey()); |
| boolean sensitive = (sensitiveNote && userSensitive) || sensitivePackage; |
| boolean showingPublic = sensitive && isLockscreenPublicMode(userId); |
| if (showingPublic) { |
| updatePublicContentView(ent, ent.notification); |
| } |
| ent.row.setSensitive(sensitive, deviceSensitive); |
| ent.row.setNeedsRedaction(needsRedaction(ent)); |
| if (mGroupManager.isChildInGroupWithSummary(ent.row.getStatusBarNotification())) { |
| ExpandableNotificationRow summary = mGroupManager.getGroupSummary( |
| ent.row.getStatusBarNotification()); |
| List<ExpandableNotificationRow> orderedChildren = |
| mTmpChildOrderMap.get(summary); |
| if (orderedChildren == null) { |
| orderedChildren = new ArrayList<>(); |
| mTmpChildOrderMap.put(summary, orderedChildren); |
| } |
| orderedChildren.add(ent.row); |
| } else { |
| toShow.add(ent.row); |
| } |
| |
| } |
| |
| ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>(); |
| for (int i=0; i< mStackScroller.getChildCount(); i++) { |
| View child = mStackScroller.getChildAt(i); |
| if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) { |
| toRemove.add((ExpandableNotificationRow) child); |
| } |
| } |
| |
| for (ExpandableNotificationRow remove : toRemove) { |
| if (mGroupManager.isChildInGroupWithSummary(remove.getStatusBarNotification())) { |
| // we are only transfering this notification to its parent, don't generate an animation |
| mStackScroller.setChildTransferInProgress(true); |
| } |
| if (remove.isSummaryWithChildren()) { |
| remove.removeAllChildren(); |
| } |
| mStackScroller.removeView(remove); |
| mStackScroller.setChildTransferInProgress(false); |
| } |
| |
| removeNotificationChildren(); |
| |
| for (int i=0; i<toShow.size(); i++) { |
| View v = toShow.get(i); |
| if (v.getParent() == null) { |
| mVisualStabilityManager.notifyViewAddition(v); |
| mStackScroller.addView(v); |
| } |
| } |
| |
| addNotificationChildrenAndSort(); |
| |
| // So after all this work notifications still aren't sorted correctly. |
| // Let's do that now by advancing through toShow and mStackScroller in |
| // lock-step, making sure mStackScroller matches what we see in toShow. |
| int j = 0; |
| for (int i = 0; i < mStackScroller.getChildCount(); i++) { |
| View child = mStackScroller.getChildAt(i); |
| if (!(child instanceof ExpandableNotificationRow)) { |
| // We don't care about non-notification views. |
| continue; |
| } |
| |
| ExpandableNotificationRow targetChild = toShow.get(j); |
| if (child != targetChild) { |
| // Oops, wrong notification at this position. Put the right one |
| // here and advance both lists. |
| if (mVisualStabilityManager.canReorderNotification(targetChild)) { |
| mStackScroller.changeViewPosition(targetChild, i); |
| } else { |
| mVisualStabilityManager.addReorderingAllowedCallback(this); |
| } |
| } |
| j++; |
| |
| } |
| |
| mVisualStabilityManager.onReorderingFinished(); |
| // clear the map again for the next usage |
| mTmpChildOrderMap.clear(); |
| |
| updateRowStates(); |
| updateSpeedBumpIndex(); |
| updateClearAll(); |
| updateEmptyShadeView(); |
| |
| updateQsExpansionEnabled(); |
| |
| // Let's also update the icons |
| mNotificationIconAreaController.updateNotificationIcons(mNotificationData); |
| } |
| |
| /** @return true if the entry needs redaction when on the lockscreen. */ |
| private boolean needsRedaction(Entry ent) { |
| int userId = ent.notification.getUserId(); |
| |
| boolean currentUserWantsRedaction = !userAllowsPrivateNotificationsInPublic(mCurrentUserId); |
| boolean notiUserWantsRedaction = !userAllowsPrivateNotificationsInPublic(userId); |
| boolean redactedLockscreen = currentUserWantsRedaction || notiUserWantsRedaction; |
| |
| boolean notificationRequestsRedaction = |
| ent.notification.getNotification().visibility == Notification.VISIBILITY_PRIVATE; |
| boolean userForcesRedaction = packageHasVisibilityOverride(ent.notification.getKey()); |
| |
| return userForcesRedaction || notificationRequestsRedaction && redactedLockscreen; |
| } |
| |
| /** |
| * Disable QS if device not provisioned. |
| * If the user switcher is simple then disable QS during setup because |
| * the user intends to use the lock screen user switcher, QS in not needed. |
| */ |
| private void updateQsExpansionEnabled() { |
| mNotificationPanel.setQsExpansionEnabled(isDeviceProvisioned() |
| && (mUserSetup || mUserSwitcherController == null |
| || !mUserSwitcherController.isSimpleUserSwitcher()) |
| && ((mDisabled2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) == 0) |
| && !mDozing |
| && !ONLY_CORE_APPS); |
| } |
| |
| private void addNotificationChildrenAndSort() { |
| // Let's now add all notification children which are missing |
| boolean orderChanged = false; |
| for (int i = 0; i < mStackScroller.getChildCount(); i++) { |
| View view = mStackScroller.getChildAt(i); |
| if (!(view instanceof ExpandableNotificationRow)) { |
| // We don't care about non-notification views. |
| continue; |
| } |
| |
| ExpandableNotificationRow parent = (ExpandableNotificationRow) view; |
| List<ExpandableNotificationRow> children = parent.getNotificationChildren(); |
| List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent); |
| |
| for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size(); |
| childIndex++) { |
| ExpandableNotificationRow childView = orderedChildren.get(childIndex); |
| if (children == null || !children.contains(childView)) { |
| if (childView.getParent() != null) { |
| Log.wtf(TAG, "trying to add a notification child that already has " + |
| "a parent. class:" + childView.getParent().getClass() + |
| "\n child: " + childView); |
| // This shouldn't happen. We can recover by removing it though. |
| ((ViewGroup) childView.getParent()).removeView(childView); |
| } |
| mVisualStabilityManager.notifyViewAddition(childView); |
| parent.addChildNotification(childView, childIndex); |
| mStackScroller.notifyGroupChildAdded(childView); |
| } |
| } |
| |
| // Finally after removing and adding has been beformed we can apply the order. |
| orderChanged |= parent.applyChildOrder(orderedChildren, mVisualStabilityManager, this); |
| } |
| if (orderChanged) { |
| mStackScroller.generateChildOrderChangedEvent(); |
| } |
| } |
| |
| private void removeNotificationChildren() { |
| // First let's remove all children which don't belong in the parents |
| ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>(); |
| for (int i = 0; i < mStackScroller.getChildCount(); i++) { |
| View view = mStackScroller.getChildAt(i); |
| if (!(view instanceof ExpandableNotificationRow)) { |
| // We don't care about non-notification views. |
| continue; |
| } |
| |
| ExpandableNotificationRow parent = (ExpandableNotificationRow) view; |
| List<ExpandableNotificationRow> children = parent.getNotificationChildren(); |
| List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent); |
| |
| if (children != null) { |
| toRemove.clear(); |
| for (ExpandableNotificationRow childRow : children) { |
| if ((orderedChildren == null |
| || !orderedChildren.contains(childRow)) |
| && !childRow.keepInParent()) { |
| toRemove.add(childRow); |
| } |
| } |
| for (ExpandableNotificationRow remove : toRemove) { |
| parent.removeChildNotification(remove); |
| if (mNotificationData.get(remove.getStatusBarNotification().getKey()) == null) { |
| // We only want to add an animation if the view is completely removed |
| // otherwise it's just a transfer |
| mStackScroller.notifyGroupChildRemoved(remove, |
| parent.getChildrenContainer()); |
| } |
| } |
| } |
| } |
| } |
| |
| public void addQsTile(ComponentName tile) { |
| mQSPanel.getHost().addTile(tile); |
| } |
| |
| public void remQsTile(ComponentName tile) { |
| mQSPanel.getHost().removeTile(tile); |
| } |
| |
| public void clickTile(ComponentName tile) { |
| mQSPanel.clickTile(tile); |
| } |
| |
| private boolean packageHasVisibilityOverride(String key) { |
| return mNotificationData.getVisibilityOverride(key) == Notification.VISIBILITY_PRIVATE; |
| } |
| |
| private void updateClearAll() { |
| boolean showDismissView = |
| mState != StatusBarState.KEYGUARD && |
| hasActiveClearableNotifications(); |
| mStackScroller.updateDismissView(showDismissView); |
| } |
| |
| /** |
| * Return whether there are any clearable notifications |
| */ |
| private boolean hasActiveClearableNotifications() { |
| int childCount = mStackScroller.getChildCount(); |
| for (int i = 0; i < childCount; i++) { |
| View child = mStackScroller.getChildAt(i); |
| if (!(child instanceof ExpandableNotificationRow)) { |
| continue; |
| } |
| if (((ExpandableNotificationRow) child).canViewBeDismissed()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private void updateEmptyShadeView() { |
| boolean showEmptyShadeView = |
| mState != StatusBarState.KEYGUARD && |
| mNotificationData.getActiveNotifications().size() == 0; |
| mNotificationPanel.showEmptyShadeView(showEmptyShadeView); |
| } |
| |
| private void updateSpeedBumpIndex() { |
| int speedBumpIndex = 0; |
| int currentIndex = 0; |
| final int N = mStackScroller.getChildCount(); |
| for (int i = 0; i < N; i++) { |
| View view = mStackScroller.getChildAt(i); |
| if (view.getVisibility() == View.GONE || !(view instanceof ExpandableNotificationRow)) { |
| continue; |
| } |
| ExpandableNotificationRow row = (ExpandableNotificationRow) view; |
| currentIndex++; |
| if (!mNotificationData.isAmbient(row.getStatusBarNotification().getKey())) { |
| speedBumpIndex = currentIndex; |
| } |
| } |
| boolean noAmbient = speedBumpIndex == N; |
| mStackScroller.updateSpeedBumpIndex(speedBumpIndex, noAmbient); |
| } |
| |
| public static boolean isTopLevelChild(Entry entry) { |
| return entry.row.getParent() instanceof NotificationStackScrollLayout; |
| } |
| |
| protected void updateNotifications() { |
| mNotificationData.filterAndSort(); |
| |
| updateNotificationShade(); |
| } |
| |
| public void requestNotificationUpdate() { |
| updateNotifications(); |
| } |
| |
| protected void setAreThereNotifications() { |
| |
| if (SPEW) { |
| final boolean clearable = hasActiveNotifications() && |
| hasActiveClearableNotifications(); |
| Log.d(TAG, "setAreThereNotifications: N=" + |
| mNotificationData.getActiveNotifications().size() + " any=" + |
| hasActiveNotifications() + " clearable=" + clearable); |
| } |
| |
| if (mStatusBarView != null) { |
| final View nlo = mStatusBarView.findViewById(R.id.notification_lights_out); |
| final boolean showDot = hasActiveNotifications() && !areLightsOn(); |
| if (showDot != (nlo.getAlpha() == 1.0f)) { |
| if (showDot) { |
| nlo.setAlpha(0f); |
| nlo.setVisibility(View.VISIBLE); |
| } |
| nlo.animate() |
| .alpha(showDot ? 1 : 0) |
| .setDuration(showDot ? 750 : 250) |
| .setInterpolator(new AccelerateInterpolator(2.0f)) |
| .setListener(showDot ? null : new AnimatorListenerAdapter() { |
| @Override |
| public void onAnimationEnd(Animator _a) { |
| nlo.setVisibility(View.GONE); |
| } |
| }) |
| .start(); |
| } |
| } |
| |
| findAndUpdateMediaNotifications(); |
| } |
| |
| public void findAndUpdateMediaNotifications() { |
| boolean metaDataChanged = false; |
| |
| synchronized (mNotificationData) { |
| ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications(); |
| final int N = activeNotifications.size(); |
| |
| // Promote the media notification with a controller in 'playing' state, if any. |
| Entry mediaNotification = null; |
| MediaController controller = null; |
| for (int i = 0; i < N; i++) { |
| final Entry entry = activeNotifications.get(i); |
| if (isMediaNotification(entry)) { |
| final MediaSession.Token token = |
| entry.notification.getNotification().extras |
| .getParcelable(Notification.EXTRA_MEDIA_SESSION); |
| if (token != null) { |
| MediaController aController = new MediaController(mContext, token); |
| if (PlaybackState.STATE_PLAYING == |
| getMediaControllerPlaybackState(aController)) { |
| if (DEBUG_MEDIA) { |
| Log.v(TAG, "DEBUG_MEDIA: found mediastyle controller matching " |
| + entry.notification.getKey()); |
| } |
| mediaNotification = entry; |
| controller = aController; |
| break; |
| } |
| } |
| } |
| } |
| if (mediaNotification == null) { |
| // Still nothing? OK, let's just look for live media sessions and see if they match |
| // one of our notifications. This will catch apps that aren't (yet!) using media |
| // notifications. |
| |
| if (mMediaSessionManager != null) { |
| final List<MediaController> sessions |
| = mMediaSessionManager.getActiveSessionsForUser( |
| null, |
| UserHandle.USER_ALL); |
| |
| for (MediaController aController : sessions) { |
| if (PlaybackState.STATE_PLAYING == |
| getMediaControllerPlaybackState(aController)) { |
| // now to see if we have one like this |
| final String pkg = aController.getPackageName(); |
| |
| for (int i = 0; i < N; i++) { |
| final Entry entry = activeNotifications.get(i); |
| if (entry.notification.getPackageName().equals(pkg)) { |
| if (DEBUG_MEDIA) { |
| Log.v(TAG, "DEBUG_MEDIA: found controller matching " |
| + entry.notification.getKey()); |
| } |
| controller = aController; |
| mediaNotification = entry; |
| break; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| if (controller != null && !sameSessions(mMediaController, controller)) { |
| // We have a new media session |
| clearCurrentMediaNotification(); |
| mMediaController = controller; |
| mMediaController.registerCallback(mMediaListener); |
| mMediaMetadata = mMediaController.getMetadata(); |
| if (DEBUG_MEDIA) { |
| Log.v(TAG, "DEBUG_MEDIA: insert listener, receive metadata: " |
| + mMediaMetadata); |
| } |
| |
| if (mediaNotification != null) { |
| mMediaNotificationKey = mediaNotification.notification.getKey(); |
| if (DEBUG_MEDIA) { |
| Log.v(TAG, "DEBUG_MEDIA: Found new media notification: key=" |
| + mMediaNotificationKey + " controller=" + mMediaController); |
| } |
| } |
| metaDataChanged = true; |
| } |
| } |
| |
| if (metaDataChanged) { |
| updateNotifications(); |
| } |
| updateMediaMetaData(metaDataChanged, true); |
| } |
| |
| private int getMediaControllerPlaybackState(MediaController controller) { |
| if (controller != null) { |
| final PlaybackState playbackState = controller.getPlaybackState(); |
| if (playbackState != null) { |
| return playbackState.getState(); |
| } |
| } |
| return PlaybackState.STATE_NONE; |
| } |
| |
| private boolean isPlaybackActive(int state) { |
| if (state != PlaybackState.STATE_STOPPED |
| && state != PlaybackState.STATE_ERROR |
| && state != PlaybackState.STATE_NONE) { |
| return true; |
| } |
| return false; |
| } |
| |
| private void clearCurrentMediaNotification() { |
| mMediaNotificationKey = null; |
| mMediaMetadata = null; |
| if (mMediaController != null) { |
| if (DEBUG_MEDIA) { |
| Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: " |
| + mMediaController.getPackageName()); |
| } |
| mMediaController.unregisterCallback(mMediaListener); |
| } |
| mMediaController = null; |
| } |
| |
| private boolean sameSessions(MediaController a, MediaController b) { |
| if (a == b) return true; |
| if (a == null) return false; |
| return a.controlsSameSession(b); |
| } |
| |
| /** |
| * Hide the album artwork that is fading out and release its bitmap. |
| */ |
| protected Runnable mHideBackdropFront = new Runnable() { |
| @Override |
| public void run() { |
| if (DEBUG_MEDIA) { |
| Log.v(TAG, "DEBUG_MEDIA: removing fade layer"); |
| } |
| mBackdropFront.setVisibility(View.INVISIBLE); |
| mBackdropFront.animate().cancel(); |
| mBackdropFront.setImageDrawable(null); |
| } |
| }; |
| |
| /** |
| * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper. |
| */ |
| public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) { |
| Trace.beginSection("StatusBar#updateMediaMetaData"); |
| if (!SHOW_LOCKSCREEN_MEDIA_ARTWORK) { |
| Trace.endSection(); |
| return; |
| } |
| |
| if (mBackdrop == null) { |
| Trace.endSection(); |
| return; // called too early |
| } |
| |
| if (mLaunchTransitionFadingAway) { |
| mBackdrop.setVisibility(View.INVISIBLE); |
| Trace.endSection(); |
| return; |
| } |
| |
| if (DEBUG_MEDIA) { |
| Log.v(TAG, "DEBUG_MEDIA: updating album art for notification " + mMediaNotificationKey |
| + " metadata=" + mMediaMetadata |
| + " metaDataChanged=" + metaDataChanged |
| + " state=" + mState); |
| } |
| |
| Drawable artworkDrawable = null; |
| if (mMediaMetadata != null) { |
| Bitmap artworkBitmap = null; |
| artworkBitmap = mMediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART); |
| if (artworkBitmap == null) { |
| artworkBitmap = mMediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART); |
| // might still be null |
| } |
| if (artworkBitmap != null) { |
| artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), artworkBitmap); |
| } |
| } |
| boolean allowWhenShade = false; |
| if (ENABLE_LOCKSCREEN_WALLPAPER && artworkDrawable == null) { |
| Bitmap lockWallpaper = mLockscreenWallpaper.getBitmap(); |
| if (lockWallpaper != null) { |
| artworkDrawable = new LockscreenWallpaper.WallpaperDrawable( |
| mBackdropBack.getResources(), lockWallpaper); |
| // We're in the SHADE mode on the SIM screen - yet we still need to show |
| // the lockscreen wallpaper in that mode. |
| allowWhenShade = mStatusBarKeyguardViewManager != null |
| && mStatusBarKeyguardViewManager.isShowing(); |
| } |
| } |
| |
| boolean hideBecauseOccluded = mStatusBarKeyguardViewManager != null |
| && mStatusBarKeyguardViewManager.isOccluded(); |
| |
| final boolean hasArtwork = artworkDrawable != null; |
| |
| if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK) |
| && (mState != StatusBarState.SHADE || allowWhenShade) |
| && mFingerprintUnlockController.getMode() |
| != FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING |
| && !hideBecauseOccluded) { |
| // time to show some art! |
| if (mBackdrop.getVisibility() != View.VISIBLE) { |
| mBackdrop.setVisibility(View.VISIBLE); |
| if (allowEnterAnimation) { |
| mBackdrop.setAlpha(SRC_MIN_ALPHA); |
| mBackdrop.animate().alpha(1f); |
| } else { |
| mBackdrop.animate().cancel(); |
| mBackdrop.setAlpha(1f); |
| } |
| mStatusBarWindowManager.setBackdropShowing(true); |
| metaDataChanged = true; |
| if (DEBUG_MEDIA) { |
| Log.v(TAG, "DEBUG_MEDIA: Fading in album artwork"); |
| } |
| } |
| if (metaDataChanged) { |
| if (mBackdropBack.getDrawable() != null) { |
| Drawable drawable = |
| mBackdropBack.getDrawable().getConstantState() |
| .newDrawable(mBackdropFront.getResources()).mutate(); |
| mBackdropFront.setImageDrawable(drawable); |
| if (mScrimSrcModeEnabled) { |
| mBackdropFront.getDrawable().mutate().setXfermode(mSrcOverXferMode); |
| } |
| mBackdropFront.setAlpha(1f); |
| mBackdropFront.setVisibility(View.VISIBLE); |
| } else { |
| mBackdropFront.setVisibility(View.INVISIBLE); |
| } |
| |
| if (DEBUG_MEDIA_FAKE_ARTWORK) { |
| final int c = 0xFF000000 | (int)(Math.random() * 0xFFFFFF); |
| Log.v(TAG, String.format("DEBUG_MEDIA: setting new color: 0x%08x", c)); |
| mBackdropBack.setBackgroundColor(0xFFFFFFFF); |
| mBackdropBack.setImageDrawable(new ColorDrawable(c)); |
| } else { |
| mBackdropBack.setImageDrawable(artworkDrawable); |
| } |
| if (mScrimSrcModeEnabled) { |
| mBackdropBack.getDrawable().mutate().setXfermode(mSrcXferMode); |
| } |
| |
| if (mBackdropFront.getVisibility() == View.VISIBLE) { |
| if (DEBUG_MEDIA) { |
| Log.v(TAG, "DEBUG_MEDIA: Crossfading album artwork from " |
| + mBackdropFront.getDrawable() |
| + " to " |
| + mBackdropBack.getDrawable()); |
| } |
| mBackdropFront.animate() |
| .setDuration(250) |
| .alpha(0f).withEndAction(mHideBackdropFront); |
| } |
| } |
| } else { |
| // need to hide the album art, either because we are unlocked or because |
| // the metadata isn't there to support it |
| if (mBackdrop.getVisibility() != View.GONE) { |
| if (DEBUG_MEDIA) { |
| Log.v(TAG, "DEBUG_MEDIA: Fading out album artwork"); |
| } |
| if (mFingerprintUnlockController.getMode() |
| == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING |
| || hideBecauseOccluded) { |
| |
| // We are unlocking directly - no animation! |
| mBackdrop.setVisibility(View.GONE); |
| mBackdropBack.setImageDrawable(null); |
| mStatusBarWindowManager.setBackdropShowing(false); |
| } else { |
| mStatusBarWindowManager.setBackdropShowing(false); |
| mBackdrop.animate() |
| .alpha(SRC_MIN_ALPHA) |
| .setInterpolator(Interpolators.ACCELERATE_DECELERATE) |
| .setDuration(300) |
| .setStartDelay(0) |
| .withEndAction(new Runnable() { |
| @Override |
| public void run() { |
| mBackdrop.setVisibility(View.GONE); |
| mBackdropFront.animate().cancel(); |
| mBackdropBack.setImageDrawable(null); |
| mHandler.post(mHideBackdropFront); |
| } |
| }); |
| if (mKeyguardFadingAway) { |
| mBackdrop.animate() |
| // Make it disappear faster, as the focus should be on the activity |
| // behind. |
| .setDuration(mKeyguardFadingAwayDuration / 2) |
| .setStartDelay(mKeyguardFadingAwayDelay) |
| .setInterpolator(Interpolators.LINEAR) |
| .start(); |
| } |
| } |
| } |
| } |
| Trace.endSection(); |
| } |
| |
| private void updateReportRejectedTouchVisibility() { |
| if (mReportRejectedTouch == null) { |
| return; |
| } |
| mReportRejectedTouch.setVisibility(mState == StatusBarState.KEYGUARD |
| && mFalsingManager.isReportingEnabled() ? View.VISIBLE : View.INVISIBLE); |
| } |
| |
| /** |
| * State is one or more of the DISABLE constants from StatusBarManager. |
| */ |
| @Override |
| public void disable(int state1, int state2, boolean animate) { |
| animate &= mStatusBarWindowState != WINDOW_STATE_HIDDEN; |
| mDisabledUnmodified1 = state1; |
| mDisabledUnmodified2 = state2; |
| final int old1 = mDisabled1; |
| final int diff1 = state1 ^ old1; |
| mDisabled1 = state1; |
| |
| final int old2 = mDisabled2; |
| final int diff2 = state2 ^ old2; |
| mDisabled2 = state2; |
| |
| if (DEBUG) { |
| Log.d(TAG, String.format("disable1: 0x%08x -> 0x%08x (diff1: 0x%08x)", |
| old1, state1, diff1)); |
| Log.d(TAG, String.format("disable2: 0x%08x -> 0x%08x (diff2: 0x%08x)", |
| old2, state2, diff2)); |
| } |
| |
| StringBuilder flagdbg = new StringBuilder(); |
| flagdbg.append("disable: < "); |
| flagdbg.append(((state1 & StatusBarManager.DISABLE_EXPAND) != 0) ? "EXPAND" : "expand"); |
| flagdbg.append(((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) ? "* " : " "); |
| flagdbg.append(((state1 & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) ? "ICONS" : "icons"); |
| flagdbg.append(((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) ? "* " : " "); |
| flagdbg.append(((state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) ? "ALERTS" : "alerts"); |
| flagdbg.append(((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) ? "* " : " "); |
| flagdbg.append(((state1 & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) ? "SYSTEM_INFO" : "system_info"); |
| flagdbg.append(((diff1 & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) ? "* " : " "); |
| flagdbg.append(((state1 & StatusBarManager.DISABLE_BACK) != 0) ? "BACK" : "back"); |
| flagdbg.append(((diff1 & StatusBarManager.DISABLE_BACK) != 0) ? "* " : " "); |
| flagdbg.append(((state1 & StatusBarManager.DISABLE_HOME) != 0) ? "HOME" : "home"); |
| flagdbg.append(((diff1 & StatusBarManager.DISABLE_HOME) != 0) ? "* " : " "); |
| flagdbg.append(((state1 & StatusBarManager.DISABLE_RECENT) != 0) ? "RECENT" : "recent"); |
| flagdbg.append(((diff1 & StatusBarManager.DISABLE_RECENT) != 0) ? "* " : " "); |
| flagdbg.append(((state1 & StatusBarManager.DISABLE_CLOCK) != 0) ? "CLOCK" : "clock"); |
| flagdbg.append(((diff1 & StatusBarManager.DISABLE_CLOCK) != 0) ? "* " : " "); |
| flagdbg.append(((state1 & StatusBarManager.DISABLE_SEARCH) != 0) ? "SEARCH" : "search"); |
| flagdbg.append(((diff1 & StatusBarManager.DISABLE_SEARCH) != 0) ? "* " : " "); |
| flagdbg.append(((state2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) != 0) ? "QUICK_SETTINGS" |
| : "quick_settings"); |
| flagdbg.append(((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) != 0) ? "* " : " "); |
| flagdbg.append(">"); |
| Log.d(TAG, flagdbg.toString()); |
| |
| if ((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) { |
| if ((state1 & StatusBarManager.DISABLE_EXPAND) != 0) { |
| animateCollapsePanels(); |
| } |
| } |
| |
| if ((diff1 & StatusBarManager.DISABLE_RECENT) != 0) { |
| if ((state1 & StatusBarManager.DISABLE_RECENT) != 0) { |
| // close recents if it's visible |
| mHandler.removeMessages(MSG_HIDE_RECENT_APPS); |
| mHandler.sendEmptyMessage(MSG_HIDE_RECENT_APPS); |
| } |
| } |
| |
| if ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) { |
| mDisableNotificationAlerts = |
| (state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0; |
| mHeadsUpObserver.onChange(true); |
| } |
| |
| if ((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) != 0) { |
| updateQsExpansionEnabled(); |
| } |
| } |
| |
| /** |
| * Reapplies the disable flags as last requested by StatusBarManager. |
| * |
| * This needs to be called if state used by {@link #adjustDisableFlags} changes. |
| */ |
| public void recomputeDisableFlags(boolean animate) { |
| mCommandQueue.recomputeDisableFlags(animate); |
| } |
| |
| protected H createHandler() { |
| return new StatusBar.H(); |
| } |
| |
| @Override |
| public void startActivity(Intent intent, boolean dismissShade) { |
| startActivityDismissingKeyguard(intent, false, dismissShade); |
| } |
| |
| @Override |
| public void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade) { |
| startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade); |
| } |
| |
| @Override |
| public void startActivity(Intent intent, boolean dismissShade, Callback callback) { |
| startActivityDismissingKeyguard(intent, false, dismissShade, callback); |
| } |
| |
| public void setQsExpanded(boolean expanded) { |
| mStatusBarWindowManager.setQsExpanded(expanded); |
| mKeyguardStatusView.setImportantForAccessibility(expanded |
| ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS |
| : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO); |
| } |
| |
| public boolean isGoingToNotificationShade() { |
| return mLeaveOpenOnKeyguardHide; |
| } |
| |
| public boolean isWakeUpComingFromTouch() { |
| return mWakeUpComingFromTouch; |
| } |
| |
| public boolean isFalsingThresholdNeeded() { |
| return getBarState() == StatusBarState.KEYGUARD; |
| } |
| |
| public boolean isDozing() { |
| return mDozing; |
| } |
| |
| @Override // NotificationData.Environment |
| public String getCurrentMediaNotificationKey() { |
| return mMediaNotificationKey; |
| } |
| |
| public boolean isScrimSrcModeEnabled() { |
| return mScrimSrcModeEnabled; |
| } |
| |
| /** |
| * To be called when there's a state change in StatusBarKeyguardViewManager. |
| */ |
| public void onKeyguardViewManagerStatesUpdated() { |
| logStateToEventlog(); |
| } |
| |
| @Override // UnlockMethodCache.OnUnlockMethodChangedListener |
| public void onUnlockMethodStateChanged() { |
| logStateToEventlog(); |
| } |
| |
| @Override |
| public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) { |
| if (inPinnedMode) { |
| mStatusBarWindowManager.setHeadsUpShowing(true); |
| mStatusBarWindowManager.setForceStatusBarVisible(true); |
| if (mNotificationPanel.isFullyCollapsed()) { |
| // We need to ensure that the touchable region is updated before the window will be |
| // resized, in order to not catch any touches. A layout will ensure that |
| // onComputeInternalInsets will be called and after that we can resize the layout. Let's |
| // make sure that the window stays small for one frame until the touchableRegion is set. |
| mNotificationPanel.requestLayout(); |
| mStatusBarWindowManager.setForceWindowCollapsed(true); |
| mNotificationPanel.post(new Runnable() { |
| @Override |
| public void run() { |
| mStatusBarWindowManager.setForceWindowCollapsed(false); |
| } |
| }); |
| } |
| } else { |
| if (!mNotificationPanel.isFullyCollapsed() || mNotificationPanel.isTracking()) { |
| // We are currently tracking or is open and the shade doesn't need to be kept |
| // open artificially. |
| mStatusBarWindowManager.setHeadsUpShowing(false); |
| } else { |
| // we need to keep the panel open artificially, let's wait until the animation |
| // is finished. |
| mHeadsUpManager.setHeadsUpGoingAway(true); |
| mStackScroller.runAfterAnimationFinished(new Runnable() { |
| @Override |
| public void run() { |
| if (!mHeadsUpManager.hasPinnedHeadsUp()) { |
| mStatusBarWindowManager.setHeadsUpShowing(false); |
| mHeadsUpManager.setHeadsUpGoingAway(false); |
| } |
| removeRemoteInputEntriesKeptUntilCollapsed(); |
| } |
| }); |
| } |
| } |
| } |
| |
| @Override |
| public void onHeadsUpPinned(ExpandableNotificationRow headsUp) { |
| dismissVolumeDialog(); |
| } |
| |
| @Override |
| public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) { |
| } |
| |
| @Override |
| public void onHeadsUpStateChanged(Entry entry, boolean isHeadsUp) { |
| if (!isHeadsUp && mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) { |
| removeNotification(entry.key, mLatestRankingMap); |
| mHeadsUpEntriesToRemoveOnSwitch.remove(entry); |
| if (mHeadsUpEntriesToRemoveOnSwitch.isEmpty()) { |
| mLatestRankingMap = null; |
| } |
| } else { |
| updateNotificationRanking(null); |
| if (isHeadsUp) { |
| mDozeServiceHost.fireNotificationHeadsUp(); |
| } |
| } |
| |
| } |
| |
| protected void updateHeadsUp(String key, Entry entry, boolean shouldPeek, |
| boolean alertAgain) { |
| final boolean wasHeadsUp = isHeadsUp(key); |
| if (wasHeadsUp) { |
| if (!shouldPeek) { |
| // We don't want this to be interrupting anymore, lets remove it |
| mHeadsUpManager.removeNotification(key, false /* ignoreEarliestRemovalTime */); |
| } else { |
| mHeadsUpManager.updateNotification(entry, alertAgain); |
| } |
| } else if (shouldPeek && alertAgain) { |
| // This notification was updated to be a heads-up, show it! |
| mHeadsUpManager.showNotification(entry); |
| } |
| } |
| |
| protected void setHeadsUpUser(int newUserId) { |
| if (mHeadsUpManager != null) { |
| mHeadsUpManager.setUser(newUserId); |
| } |
| } |
| |
| public boolean isHeadsUp(String key) { |
| return mHeadsUpManager.isHeadsUp(key); |
| } |
| |
| protected boolean isSnoozedPackage(StatusBarNotification sbn) { |
| return mHeadsUpManager.isSnoozed(sbn.getPackageName()); |
| } |
| |
| public boolean isKeyguardCurrentlySecure() { |
| return !mUnlockMethodCache.canSkipBouncer(); |
| } |
| |
| public void setPanelExpanded(boolean isExpanded) { |
| mPanelExpanded = isExpanded; |
| mStatusBarWindowManager.setPanelExpanded(isExpanded); |
| mVisualStabilityManager.setPanelExpanded(isExpanded); |
| if (isExpanded && getBarState() != StatusBarState.KEYGUARD) { |
| if (DEBUG) { |
| Log.v(TAG, "clearing notification effects from setPanelExpanded"); |
| } |
| clearNotificationEffects(); |
| } |
| |
| if (!isExpanded) { |
| removeRemoteInputEntriesKeptUntilCollapsed(); |
| } |
| } |
| |
| private void removeRemoteInputEntriesKeptUntilCollapsed() { |
| for (int i = 0; i < mRemoteInputEntriesToRemoveOnCollapse.size(); i++) { |
| Entry entry = mRemoteInputEntriesToRemoveOnCollapse.valueAt(i); |
| mRemoteInputController.removeRemoteInput(entry, null); |
| removeNotification(entry.key, mLatestRankingMap); |
| } |
| mRemoteInputEntriesToRemoveOnCollapse.clear(); |
| } |
| |
| public void onScreenTurnedOff() { |
| mFalsingManager.onScreenOff(); |
| } |
| |
| public NotificationStackScrollLayout getNotificationScrollLayout() { |
| return mStackScroller; |
| } |
| |
| public boolean isPulsing() { |
| return mDozeScrimController.isPulsing(); |
| } |
| |
| @Override |
| public void onReorderingAllowed() { |
| updateNotifications(); |
| } |
| |
| public boolean isLaunchTransitionFadingAway() { |
| return mLaunchTransitionFadingAway; |
| } |
| |
| public boolean hideStatusBarIconsWhenExpanded() { |
| return mNotificationPanel.hideStatusBarIconsWhenExpanded(); |
| } |
| |
| /** |
| * All changes to the status bar and notifications funnel through here and are batched. |
| */ |
| protected class H extends Handler { |
| @Override |
| public void handleMessage(Message m) { |
| switch (m.what) { |
| case MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU: |
| toggleKeyboardShortcuts(m.arg1); |
| break; |
| case MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU: |
| dismissKeyboardShortcuts(); |
| break; |
| // End old BaseStatusBar.H handling. |
| case MSG_OPEN_NOTIFICATION_PANEL: |
| animateExpandNotificationsPanel(); |
| break; |
| case MSG_OPEN_SETTINGS_PANEL: |
| animateExpandSettingsPanel((String) m.obj); |
| break; |
| case MSG_CLOSE_PANELS: |
| animateCollapsePanels(); |
| break; |
| case MSG_LAUNCH_TRANSITION_TIMEOUT: |
| onLaunchTransitionTimeout(); |
| break; |
| } |
| } |
| } |
| |
| public void maybeEscalateHeadsUp() { |
| Collection<HeadsUpManager.HeadsUpEntry> entries = mHeadsUpManager.getAllEntries(); |
| for (HeadsUpManager.HeadsUpEntry entry : entries) { |
| final StatusBarNotification sbn = entry.entry.notification; |
| final Notification notification = sbn.getNotification(); |
| if (notification.fullScreenIntent != null) { |
| if (DEBUG) { |
| Log.d(TAG, "converting a heads up to fullScreen"); |
| } |
| try { |
| EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION, |
| sbn.getKey()); |
| notification.fullScreenIntent.send(); |
| entry.entry.notifyFullScreenIntentLaunched(); |
| } catch (PendingIntent.CanceledException e) { |
| } |
| } |
| } |
| mHeadsUpManager.releaseAllImmediately(); |
| } |
| |
| /** |
| * Called for system navigation gestures. First action opens the panel, second opens |
| * settings. Down action closes the entire panel. |
| */ |
| @Override |
| public void handleSystemNavigationKey(int key) { |
| if (SPEW) Log.d(TAG, "handleSystemNavigationKey: " + key); |
| if (!panelsEnabled() || !mKeyguardMonitor.isDeviceInteractive() |
| || mKeyguardMonitor.isShowing() && !mKeyguardMonitor.isOccluded()) { |
| return; |
| } |
| |
| // Panels are not available in setup |
| if (!mUserSetup) return; |
| |
| if (KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP == key) { |
| mMetricsLogger.action(MetricsEvent.ACTION_SYSTEM_NAVIGATION_KEY_UP); |
| mNotificationPanel.collapse(false /* delayed */, 1.0f /* speedUpFactor */); |
| } else if (KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN == key) { |
| mMetricsLogger.action(MetricsEvent.ACTION_SYSTEM_NAVIGATION_KEY_DOWN); |
| if (mNotificationPanel.isFullyCollapsed()) { |
| mNotificationPanel.expand(true /* animate */); |
| mMetricsLogger.count(NotificationPanelView.COUNTER_PANEL_OPEN, 1); |
| } else if (!mNotificationPanel.isInSettings() && !mNotificationPanel.isExpanding()){ |
| mNotificationPanel.flingSettings(0 /* velocity */, true /* expand */); |
| mMetricsLogger.count(NotificationPanelView.COUNTER_PANEL_OPEN_QS, 1); |
| } |
| } |
| |
| } |
| |
| boolean panelsEnabled() { |
| return (mDisabled1 & StatusBarManager.DISABLE_EXPAND) == 0 && !ONLY_CORE_APPS; |
| } |
| |
| void makeExpandedVisible(boolean force) { |
| if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible); |
| if (!force && (mExpandedVisible || !panelsEnabled())) { |
| return; |
| } |
| |
| mExpandedVisible = true; |
| |
| // Expand the window to encompass the full screen in anticipation of the drag. |
| // This is only possible to do atomically because the status bar is at the top of the screen! |
| mStatusBarWindowManager.setPanelVisible(true); |
| |
| visibilityChanged(true); |
| mWaitingForKeyguardExit = false; |
| recomputeDisableFlags(!force /* animate */); |
| setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true); |
| } |
| |
| public void animateCollapsePanels() { |
| animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); |
| } |
| |
| private final Runnable mAnimateCollapsePanels = new Runnable() { |
| @Override |
| public void run() { |
| animateCollapsePanels(); |
| } |
| }; |
| |
| public void postAnimateCollapsePanels() { |
| mHandler.post(mAnimateCollapsePanels); |
| } |
| |
| public void postAnimateOpenPanels() { |
| mHandler.sendEmptyMessage(MSG_OPEN_SETTINGS_PANEL); |
| } |
| |
| @Override |
| public void animateCollapsePanels(int flags) { |
| animateCollapsePanels(flags, false /* force */, false /* delayed */, |
| 1.0f /* speedUpFactor */); |
| } |
| |
| public void animateCollapsePanels(int flags, boolean force) { |
| animateCollapsePanels(flags, force, false /* delayed */, 1.0f /* speedUpFactor */); |
| } |
| |
| public void animateCollapsePanels(int flags, boolean force, boolean delayed) { |
| animateCollapsePanels(flags, force, delayed, 1.0f /* speedUpFactor */); |
| } |
| |
| public void animateCollapsePanels(int flags, boolean force, boolean delayed, |
| float speedUpFactor) { |
| if (!force && mState != StatusBarState.SHADE) { |
| runPostCollapseRunnables(); |
| return; |
| } |
| if (SPEW) { |
| Log.d(TAG, "animateCollapse():" |
| + " mExpandedVisible=" + mExpandedVisible |
| + " flags=" + flags); |
| } |
| |
| if ((flags & CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL) == 0) { |
| if (!mHandler.hasMessages(MSG_HIDE_RECENT_APPS)) { |
| mHandler.removeMessages(MSG_HIDE_RECENT_APPS); |
| mHandler.sendEmptyMessage(MSG_HIDE_RECENT_APPS); |
| } |
| } |
| |
| if (mStatusBarWindow != null && mNotificationPanel.canPanelBeCollapsed()) { |
| // release focus immediately to kick off focus change transition |
| mStatusBarWindowManager.setStatusBarFocusable(false); |
| |
| mStatusBarWindow.cancelExpandHelper(); |
| mStatusBarView.collapsePanel(true /* animate */, delayed, speedUpFactor); |
| } |
| } |
| |
| private void runPostCollapseRunnables() { |
| ArrayList<Runnable> clonedList = new ArrayList<>(mPostCollapseRunnables); |
| mPostCollapseRunnables.clear(); |
| int size = clonedList.size(); |
| for (int i = 0; i < size; i++) { |
| clonedList.get(i).run(); |
| } |
| mStatusBarKeyguardViewManager.readyForKeyguardDone(); |
| } |
| |
| @Override |
| public void animateExpandNotificationsPanel() { |
| if (SPEW) Log.d(TAG, "animateExpand: mExpandedVisible=" + mExpandedVisible); |
| if (!panelsEnabled()) { |
| return ; |
| } |
| |
| mNotificationPanel.expand(true /* animate */); |
| |
| if (false) postStartTracing(); |
| } |
| |
| @Override |
| public void animateExpandSettingsPanel(String subPanel) { |
| if (SPEW) Log.d(TAG, "animateExpand: mExpandedVisible=" + mExpandedVisible); |
| if (!panelsEnabled()) { |
| return; |
| } |
| |
| // Settings are not available in setup |
| if (!mUserSetup) return; |
| |
| |
| if (subPanel != null) { |
| mQSPanel.openDetails(subPanel); |
| } |
| mNotificationPanel.expandWithQs(); |
| |
| if (false) postStartTracing(); |
| } |
| |
| public void animateCollapseQuickSettings() { |
| if (mState == StatusBarState.SHADE) { |
| mStatusBarView.collapsePanel(true, false /* delayed */, 1.0f /* speedUpFactor */); |
| } |
| } |
| |
| void makeExpandedInvisible() { |
| if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible |
| + " mExpandedVisible=" + mExpandedVisible); |
| |
| if (!mExpandedVisible || mStatusBarWindow == null) { |
| return; |
| } |
| |
| // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868) |
| mStatusBarView.collapsePanel(/*animate=*/ false, false /* delayed*/, |
| 1.0f /* speedUpFactor */); |
| |
| mNotificationPanel.closeQs(); |
| |
| mExpandedVisible = false; |
| visibilityChanged(false); |
| |
| // Shrink the window to the size of the status bar only |
| mStatusBarWindowManager.setPanelVisible(false); |
| mStatusBarWindowManager.setForceStatusBarVisible(false); |
| |
| // Close any "App info" popups that might have snuck on-screen |
| dismissPopups(); |
| |
| runPostCollapseRunnables(); |
| setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false); |
| showBouncerIfKeyguard(); |
| recomputeDisableFlags(mNotificationPanel.hideStatusBarIconsWhenExpanded() /* animate */); |
| |
| // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in |
| // the bouncer appear animation. |
| if (!mStatusBarKeyguardViewManager.isShowing()) { |
| WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); |
| } |
| } |
| |
| public boolean interceptTouchEvent(MotionEvent event) { |
| if (DEBUG_GESTURES) { |
| if (event.getActionMasked() != MotionEvent.ACTION_MOVE) { |
| EventLog.writeEvent(EventLogTags.SYSUI_STATUSBAR_TOUCH, |
| event.getActionMasked(), (int) event.getX(), (int) event.getY(), |
| mDisabled1, mDisabled2); |
| } |
| |
| } |
| |
| if (SPEW) { |
| Log.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event + " mDisabled1=" |
| + mDisabled1 + " mDisabled2=" + mDisabled2 + " mTracking=" + mTracking); |
| } else if (CHATTY) { |
| if (event.getAction() != MotionEvent.ACTION_MOVE) { |
| Log.d(TAG, String.format( |
| "panel: %s at (%f, %f) mDisabled1=0x%08x mDisabled2=0x%08x", |
| MotionEvent.actionToString(event.getAction()), |
| event.getRawX(), event.getRawY(), mDisabled1, mDisabled2)); |
| } |
| } |
| |
| if (DEBUG_GESTURES) { |
| mGestureRec.add(event); |
| } |
| |
| if (mStatusBarWindowState == WINDOW_STATE_SHOWING) { |
| final boolean upOrCancel = |
| event.getAction() == MotionEvent.ACTION_UP || |
| event.getAction() == MotionEvent.ACTION_CANCEL; |
| if (upOrCancel && !mExpandedVisible) { |
| setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false); |
| } else { |
| setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true); |
| } |
| } |
| return false; |
| } |
| |
| public GestureRecorder getGestureRecorder() { |
| return mGestureRec; |
| } |
| |
| public FingerprintUnlockController getFingerprintUnlockController() { |
| return mFingerprintUnlockController; |
| } |
| |
| @Override // CommandQueue |
| public void setWindowState(int window, int state) { |
| boolean showing = state == WINDOW_STATE_SHOWING; |
| if (mStatusBarWindow != null |
| && window == StatusBarManager.WINDOW_STATUS_BAR |
| && mStatusBarWindowState != state) { |
| mStatusBarWindowState = state; |
| if (DEBUG_WINDOW_STATE) Log.d(TAG, "Status bar " + windowStateToString(state)); |
| if (!showing && mState == StatusBarState.SHADE) { |
| mStatusBarView.collapsePanel(false /* animate */, false /* delayed */, |
| 1.0f /* speedUpFactor */); |
| } |
| } |
| } |
| |
| @Override // CommandQueue |
| public void setSystemUiVisibility(int vis, int fullscreenStackVis, int dockedStackVis, |
| int mask, Rect fullscreenStackBounds, Rect dockedStackBounds) { |
| final int oldVal = mSystemUiVisibility; |
| final int newVal = (oldVal&~mask) | (vis&mask); |
| final int diff = newVal ^ oldVal; |
| if (DEBUG) Log.d(TAG, String.format( |
| "setSystemUiVisibility vis=%s mask=%s oldVal=%s newVal=%s diff=%s", |
| Integer.toHexString(vis), Integer.toHexString(mask), |
| Integer.toHexString(oldVal), Integer.toHexString(newVal), |
| Integer.toHexString(diff))); |
| boolean sbModeChanged = false; |
| if (diff != 0) { |
| mSystemUiVisibility = newVal; |
| |
| // update low profile |
| if ((diff & View.SYSTEM_UI_FLAG_LOW_PROFILE) != 0) { |
| setAreThereNotifications(); |
| } |
| |
| // ready to unhide |
| if ((vis & View.STATUS_BAR_UNHIDE) != 0) { |
| mSystemUiVisibility &= ~View.STATUS_BAR_UNHIDE; |
| mNoAnimationOnNextBarModeChange = true; |
| } |
| |
| // update status bar mode |
| final int sbMode = computeStatusBarMode(oldVal, newVal); |
| |
| sbModeChanged = sbMode != -1; |
| if (sbModeChanged && sbMode != mStatusBarMode) { |
| if (sbMode != mStatusBarMode) { |
| mStatusBarMode = sbMode; |
| checkBarModes(); |
| } |
| touchAutoHide(); |
| } |
| |
| if ((vis & View.NAVIGATION_BAR_UNHIDE) != 0) { |
| mSystemUiVisibility &= ~View.NAVIGATION_BAR_UNHIDE; |
| } |
| |
| // send updated sysui visibility to window manager |
| notifyUiVisibilityChanged(mSystemUiVisibility); |
| } |
| |
| mLightBarController.onSystemUiVisibilityChanged(fullscreenStackVis, dockedStackVis, |
| mask, fullscreenStackBounds, dockedStackBounds, sbModeChanged, mStatusBarMode); |
| } |
| |
| void touchAutoHide() { |
| // update transient bar autohide |
| if (mStatusBarMode == MODE_SEMI_TRANSPARENT || (mNavigationBar != null |
| && mNavigationBar.isSemiTransparent())) { |
| scheduleAutohide(); |
| } else { |
| cancelAutohide(); |
| } |
| } |
| |
| protected int computeStatusBarMode(int oldVal, int newVal) { |
| return computeBarMode(oldVal, newVal, View.STATUS_BAR_TRANSIENT, |
| View.STATUS_BAR_TRANSLUCENT, View.STATUS_BAR_TRANSPARENT); |
| } |
| |
| protected BarTransitions getStatusBarTransitions() { |
| return mStatusBarView.getBarTransitions(); |
| } |
| |
| protected int computeBarMode(int oldVis, int newVis, |
| int transientFlag, int translucentFlag, int transparentFlag) { |
| final int oldMode = barMode(oldVis, transientFlag, translucentFlag, transparentFlag); |
| final int newMode = barMode(newVis, transientFlag, translucentFlag, transparentFlag); |
| if (oldMode == newMode) { |
| return -1; // no mode change |
| } |
| return newMode; |
| } |
| |
| private int barMode(int vis, int transientFlag, int translucentFlag, int transparentFlag) { |
| int lightsOutTransparent = View.SYSTEM_UI_FLAG_LOW_PROFILE | transparentFlag; |
| return (vis & transientFlag) != 0 ? MODE_SEMI_TRANSPARENT |
| : (vis & translucentFlag) != 0 ? MODE_TRANSLUCENT |
| : (vis & lightsOutTransparent) == lightsOutTransparent ? MODE_LIGHTS_OUT_TRANSPARENT |
| : (vis & transparentFlag) != 0 ? MODE_TRANSPARENT |
| : (vis & View.SYSTEM_UI_FLAG_LOW_PROFILE) != 0 ? MODE_LIGHTS_OUT |
| : MODE_OPAQUE; |
| } |
| |
| void checkBarModes() { |
| if (mDemoMode) return; |
| if (mStatusBarView != null) checkBarMode(mStatusBarMode, mStatusBarWindowState, |
| getStatusBarTransitions()); |
| if (mNavigationBar != null) mNavigationBar.checkNavBarModes(); |
| mNoAnimationOnNextBarModeChange = false; |
| } |
| |
| // Called by NavigationBarFragment |
| void setQsScrimEnabled(boolean scrimEnabled) { |
| mNotificationPanel.setQsScrimEnabled(scrimEnabled); |
| } |
| |
| void checkBarMode(int mode, int windowState, BarTransitions transitions) { |
| final boolean powerSave = mBatteryController.isPowerSave(); |
| final boolean anim = !mNoAnimationOnNextBarModeChange && mDeviceInteractive |
| && windowState != WINDOW_STATE_HIDDEN && !powerSave; |
| if (powerSave && getBarState() == StatusBarState.SHADE) { |
| mode = MODE_WARNING; |
| } |
| transitions.transitionTo(mode, anim); |
| } |
| |
| private void finishBarAnimations() { |
| if (mStatusBarView != null) { |
| mStatusBarView.getBarTransitions().finishAnimations(); |
| } |
| if (mNavigationBar != null) { |
| mNavigationBar.finishBarAnimations(); |
| } |
| } |
| |
| private final Runnable mCheckBarModes = new Runnable() { |
| @Override |
| public void run() { |
| checkBarModes(); |
| } |
| }; |
| |
| public void setInteracting(int barWindow, boolean interacting) { |
| final boolean changing = ((mInteractingWindows & barWindow) != 0) != interacting; |
| mInteractingWindows = interacting |
| ? (mInteractingWindows | barWindow) |
| : (mInteractingWindows & ~barWindow); |
| if (mInteractingWindows != 0) { |
| suspendAutohide(); |
| } else { |
| resumeSuspendedAutohide(); |
| } |
| // manually dismiss the volume panel when interacting with the nav bar |
| if (changing && interacting && barWindow == StatusBarManager.WINDOW_NAVIGATION_BAR) { |
| dismissVolumeDialog(); |
| } |
| checkBarModes(); |
| } |
| |
| private void dismissVolumeDialog() { |
| if (mVolumeComponent != null) { |
| mVolumeComponent.dismissNow(); |
| } |
| } |
| |
| private void resumeSuspendedAutohide() { |
| if (mAutohideSuspended) { |
| scheduleAutohide(); |
| mHandler.postDelayed(mCheckBarModes, 500); // longer than home -> launcher |
| } |
| } |
| |
| private void suspendAutohide() { |
| mHandler.removeCallbacks(mAutohide); |
| mHandler.removeCallbacks(mCheckBarModes); |
| mAutohideSuspended = (mSystemUiVisibility & STATUS_OR_NAV_TRANSIENT) != 0; |
| } |
| |
| private void cancelAutohide() { |
| mAutohideSuspended = false; |
| mHandler.removeCallbacks(mAutohide); |
| } |
| |
| private void scheduleAutohide() { |
| cancelAutohide(); |
| mHandler.postDelayed(mAutohide, AUTOHIDE_TIMEOUT_MS); |
| } |
| |
| void checkUserAutohide(View v, MotionEvent event) { |
| if ((mSystemUiVisibility & STATUS_OR_NAV_TRANSIENT) != 0 // a transient bar is revealed |
| && event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar |
| && event.getX() == 0 && event.getY() == 0 // a touch outside both bars |
| && !mRemoteInputController.isRemoteInputActive()) { // not due to typing in IME |
| userAutohide(); |
| } |
| } |
| |
| private void checkRemoteInputOutside(MotionEvent event) { |
| if (event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar |
| && event.getX() == 0 && event.getY() == 0 // a touch outside both bars |
| && mRemoteInputController.isRemoteInputActive()) { |
| mRemoteInputController.closeRemoteInputs(); |
| } |
| } |
| |
| private void userAutohide() { |
| cancelAutohide(); |
| mHandler.postDelayed(mAutohide, 350); // longer than app gesture -> flag clear |
| } |
| |
| private boolean areLightsOn() { |
| return 0 == (mSystemUiVisibility & View.SYSTEM_UI_FLAG_LOW_PROFILE); |
| } |
| |
| public void setLightsOn(boolean on) { |
| Log.v(TAG, "setLightsOn(" + on + ")"); |
| if (on) { |
| setSystemUiVisibility(0, 0, 0, View.SYSTEM_UI_FLAG_LOW_PROFILE, |
| mLastFullscreenStackBounds, mLastDockedStackBounds); |
| } else { |
| setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE, 0, 0, |
| View.SYSTEM_UI_FLAG_LOW_PROFILE, mLastFullscreenStackBounds, |
| mLastDockedStackBounds); |
| } |
| } |
| |
| private void notifyUiVisibilityChanged(int vis) { |
| try { |
| if (mLastDispatchedSystemUiVisibility != vis) { |
| mWindowManagerService.statusBarVisibilityChanged(vis); |
| mLastDispatchedSystemUiVisibility = vis; |
| } |
| } catch (RemoteException ex) { |
| } |
| } |
| |
| @Override |
| public void topAppWindowChanged(boolean showMenu) { |
| if (SPEW) { |
| Log.d(TAG, (showMenu?"showing":"hiding") + " the MENU button"); |
| } |
| |
| // See above re: lights-out policy for legacy apps. |
| if (showMenu) setLightsOn(true); |
| } |
| |
| public static String viewInfo(View v) { |
| return "[(" + v.getLeft() + "," + v.getTop() + ")(" + v.getRight() + "," + v.getBottom() |
| + ") " + v.getWidth() + "x" + v.getHeight() + "]"; |
| } |
| |
| @Override |
| public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| synchronized (mQueueLock) { |
| pw.println("Current Status Bar state:"); |
| pw.println(" mExpandedVisible=" + mExpandedVisible |
| + ", mTrackingPosition=" + mTrackingPosition); |
| pw.println(" mTracking=" + mTracking); |
| pw.println(" mDisplayMetrics=" + mDisplayMetrics); |
| pw.println(" mStackScroller: " + viewInfo(mStackScroller)); |
| pw.println(" mStackScroller: " + viewInfo(mStackScroller) |
| + " scroll " + mStackScroller.getScrollX() |
| + "," + mStackScroller.getScrollY()); |
| } |
| |
| pw.print(" mInteractingWindows="); pw.println(mInteractingWindows); |
| pw.print(" mStatusBarWindowState="); |
| pw.println(windowStateToString(mStatusBarWindowState)); |
| pw.print(" mStatusBarMode="); |
| pw.println(BarTransitions.modeToString(mStatusBarMode)); |
| pw.print(" mDozing="); pw.println(mDozing); |
| pw.print(" mZenMode="); |
| pw.println(Settings.Global.zenModeToString(mZenMode)); |
| pw.print(" mUseHeadsUp="); |
| pw.println(mUseHeadsUp); |
| dumpBarTransitions(pw, "mStatusBarView", mStatusBarView.getBarTransitions()); |
| |
| pw.print(" mMediaSessionManager="); |
| pw.println(mMediaSessionManager); |
| pw.print(" mMediaNotificationKey="); |
| pw.println(mMediaNotificationKey); |
| pw.print(" mMediaController="); |
| pw.print(mMediaController); |
| if (mMediaController != null) { |
| pw.print(" state=" + mMediaController.getPlaybackState()); |
| } |
| pw.println(); |
| pw.print(" mMediaMetadata="); |
| pw.print(mMediaMetadata); |
| if (mMediaMetadata != null) { |
| pw.print(" title=" + mMediaMetadata.getText(MediaMetadata.METADATA_KEY_TITLE)); |
| } |
| pw.println(); |
| |
| pw.println(" Panels: "); |
| if (mNotificationPanel != null) { |
| pw.println(" mNotificationPanel=" + |
| mNotificationPanel + " params=" + mNotificationPanel.getLayoutParams().debug("")); |
| pw.print (" "); |
| mNotificationPanel.dump(fd, pw, args); |
| } |
| |
| DozeLog.dump(pw); |
| |
| if (DUMPTRUCK) { |
| synchronized (mNotificationData) { |
| mNotificationData.dump(pw, " "); |
| } |
| |
| if (false) { |
| pw.println("see the logcat for a dump of the views we have created."); |
| // must happen on ui thread |
| mHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| mStatusBarView.getLocationOnScreen(mAbsPos); |
| Log.d(TAG, "mStatusBarView: ----- (" + mAbsPos[0] + "," + mAbsPos[1] |
| + ") " + mStatusBarView.getWidth() + "x" |
| + getStatusBarHeight()); |
| mStatusBarView.debug(); |
| } |
| }); |
| } |
| } |
| |
| if (DEBUG_GESTURES) { |
| pw.print(" status bar gestures: "); |
| mGestureRec.dump(fd, pw, args); |
| } |
| |
| if (mHeadsUpManager != null) { |
| mHeadsUpManager.dump(fd, pw, args); |
| } else { |
| pw.println(" mHeadsUpManager: null"); |
| } |
| if (mGroupManager != null) { |
| mGroupManager.dump(fd, pw, args); |
| } else { |
| pw.println(" mGroupManager: null"); |
| } |
| |
| mLightBarController.dump(fd, pw, args); |
| |
| if (KeyguardUpdateMonitor.getInstance(mContext) != null) { |
| KeyguardUpdateMonitor.getInstance(mContext).dump(fd, pw, args); |
| } |
| |
| FalsingManager.getInstance(mContext).dump(pw); |
| FalsingLog.dump(pw); |
| |
| pw.println("SharedPreferences:"); |
| for (Map.Entry<String, ?> entry : Prefs.getAll(mContext).entrySet()) { |
| pw.print(" "); pw.print(entry.getKey()); pw.print("="); pw.println(entry.getValue()); |
| } |
| } |
| |
| static void dumpBarTransitions(PrintWriter pw, String var, BarTransitions transitions) { |
| pw.print(" "); pw.print(var); pw.print(".BarTransitions.mMode="); |
| pw.println(BarTransitions.modeToString(transitions.getMode())); |
| } |
| |
| public void createAndAddWindows() { |
| addStatusBarWindow(); |
| } |
| |
| private void addStatusBarWindow() { |
| makeStatusBarView(); |
| mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class); |
| mRemoteInputController = new RemoteInputController(mHeadsUpManager); |
| mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight()); |
| } |
| |
| // called by makeStatusbar and also by PhoneStatusBarView |
| void updateDisplaySize() { |
| mDisplay.getMetrics(mDisplayMetrics); |
| mDisplay.getSize(mCurrentDisplaySize); |
| if (DEBUG_GESTURES) { |
| mGestureRec.tag("display", |
| String.format("%dx%d", mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)); |
| } |
| } |
| |
| float getDisplayDensity() { |
| return mDisplayMetrics.density; |
| } |
| |
| public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned, |
| boolean dismissShade) { |
| startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade, null /* callback */); |
| } |
| |
| public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned, |
| final boolean dismissShade, final Callback callback) { |
| if (onlyProvisioned && !isDeviceProvisioned()) return; |
| |
| final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity( |
| mContext, intent, mCurrentUserId); |
| Runnable runnable = new Runnable() { |
| @Override |
| public void run() { |
| mAssistManager.hideAssist(); |
| intent.setFlags( |
| Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); |
| int result = ActivityManager.START_CANCELED; |
| ActivityOptions options = new ActivityOptions(getActivityOptions()); |
| if (intent == KeyguardBottomAreaView.INSECURE_CAMERA_INTENT) { |
| // Normally an activity will set it's requested rotation |
| // animation on its window. However when launching an activity |
| // causes the orientation to change this is too late. In these cases |
| // the default animation is used. This doesn't look good for |
| // the camera (as it rotates the camera contents out of sync |
| // with physical reality). So, we ask the WindowManager to |
| // force the crossfade animation if an orientation change |
| // happens to occur during the launch. |
| options.setRotationAnimationHint( |
| WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS); |
| } |
| try { |
| result = ActivityManager.getService().startActivityAsUser( |
| null, mContext.getBasePackageName(), |
| intent, |
| intent.resolveTypeIfNeeded(mContext.getContentResolver()), |
| null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, |
| options.toBundle(), UserHandle.CURRENT.getIdentifier()); |
| } catch (RemoteException e) { |
| Log.w(TAG, "Unable to start activity", e); |
| } |
| if (callback != null) { |
| callback.onActivityStarted(result); |
| } |
| } |
| }; |
| Runnable cancelRunnable = new Runnable() { |
| @Override |
| public void run() { |
| if (callback != null) { |
| callback.onActivityStarted(ActivityManager.START_CANCELED); |
| } |
| } |
| }; |
| executeRunnableDismissingKeyguard(runnable, cancelRunnable, dismissShade, |
| afterKeyguardGone, true /* deferred */); |
| } |
| |
| public void readyForKeyguardDone() { |
| mStatusBarKeyguardViewManager.readyForKeyguardDone(); |
| } |
| |
| public void executeRunnableDismissingKeyguard(final Runnable runnable, |
| final Runnable cancelAction, |
| final boolean dismissShade, |
| final boolean afterKeyguardGone, |
| final boolean deferred) { |
| dismissKeyguardThenExecute(() -> { |
| if (runnable != null) { |
| if (mStatusBarKeyguardViewManager.isShowing() |
| && mStatusBarKeyguardViewManager.isOccluded()) { |
| mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable); |
| } else { |
| AsyncTask.execute(runnable); |
| } |
| } |
| if (dismissShade) { |
| if (mExpandedVisible) { |
| animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */, |
| true /* delayed*/); |
| } else { |
| |
| // Do it after DismissAction has been processed to conserve the needed ordering. |
| mHandler.post(this::runPostCollapseRunnables); |
| } |
| } |
| return deferred; |
| }, cancelAction, afterKeyguardGone); |
| } |
| |
| private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| if (DEBUG) Log.v(TAG, "onReceive: " + intent); |
| String action = intent.getAction(); |
| if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { |
| KeyboardShortcuts.dismiss(); |
| if (mRemoteInputController != null) { |
| mRemoteInputController.closeRemoteInputs(); |
| } |
| if (isCurrentProfile(getSendingUserId())) { |
| int flags = CommandQueue.FLAG_EXCLUDE_NONE; |
| String reason = intent.getStringExtra("reason"); |
| if (reason != null && reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) { |
| flags |= CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL; |
| } |
| animateCollapsePanels(flags); |
| } |
| } |
| else if (Intent.ACTION_SCREEN_OFF.equals(action)) { |
| notifyHeadsUpScreenOff(); |
| finishBarAnimations(); |
| resetUserExpandedStates(); |
| } |
| else if (DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG.equals(action)) { |
| mQSPanel.showDeviceMonitoringDialog(); |
| } |
| } |
| }; |
| |
| private BroadcastReceiver mDemoReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| if (DEBUG) Log.v(TAG, "onReceive: " + intent); |
| String action = intent.getAction(); |
| if (ACTION_DEMO.equals(action)) { |
| Bundle bundle = intent.getExtras(); |
| if (bundle != null) { |
| String command = bundle.getString("command", "").trim().toLowerCase(); |
| if (command.length() > 0) { |
| try { |
| dispatchDemoCommand(command, bundle); |
| } catch (Throwable t) { |
| Log.w(TAG, "Error running demo command, intent=" + intent, t); |
| } |
| } |
| } |
| } else if (ACTION_FAKE_ARTWORK.equals(action)) { |
| if (DEBUG_MEDIA_FAKE_ARTWORK) { |
| updateMediaMetaData(true, true); |
| } |
| } |
| } |
| }; |
| |
| public void resetUserExpandedStates() { |
| ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications(); |
| final int notificationCount = activeNotifications.size(); |
| for (int i = 0; i < notificationCount; i++) { |
| NotificationData.Entry entry = activeNotifications.get(i); |
| if (entry.row != null) { |
| entry.row.resetUserExpansion(); |
| } |
| } |
| } |
| |
| protected void dismissKeyguardThenExecute(OnDismissAction action, boolean afterKeyguardGone) { |
| dismissKeyguardThenExecute(action, null /* cancelRunnable */, afterKeyguardGone); |
| } |
| |
| private void dismissKeyguardThenExecute(OnDismissAction action, Runnable cancelAction, |
| boolean afterKeyguardGone) { |
| if (mStatusBarKeyguardViewManager.isShowing()) { |
| mStatusBarKeyguardViewManager.dismissWithAction(action, cancelAction, |
| afterKeyguardGone); |
| } else { |
| action.onDismiss(); |
| } |
| } |
| |
| // SystemUIService notifies SystemBars of configuration changes, which then calls down here |
| @Override |
| protected void onConfigurationChanged(Configuration newConfig) { |
| updateResources(); |
| updateDisplaySize(); // populates mDisplayMetrics |
| |
| if (DEBUG) { |
| Log.v(TAG, "configuration changed: " + mContext.getResources().getConfiguration()); |
| } |
| |
| updateRowStates(); |
| mScreenPinningRequest.onConfigurationChanged(); |
| } |
| |
| public void userSwitched(int newUserId) { |
| // Begin old BaseStatusBar.userSwitched |
| setHeadsUpUser(newUserId); |
| // End old BaseStatusBar.userSwitched |
| if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId); |
| animateCollapsePanels(); |
| updatePublicMode(); |
| mNotificationData.filterAndSort(); |
| if (mReinflateNotificationsOnUserSwitched) { |
| updateNotificationsOnDensityOrFontScaleChanged(); |
| mReinflateNotificationsOnUserSwitched = false; |
| } |
| updateNotificationShade(); |
| clearCurrentMediaNotification(); |
| setLockscreenUser(newUserId); |
| } |
| |
| protected void setLockscreenUser(int newUserId) { |
| mLockscreenWallpaper.setCurrentUser(newUserId); |
| mScrimController.setCurrentUser(newUserId); |
| updateMediaMetaData(true, false); |
| } |
| |
| /** |
| * Reload some of our resources when the configuration changes. |
| * |
| * We don't reload everything when the configuration changes -- we probably |
| * should, but getting that smooth is tough. Someday we'll fix that. In the |
| * meantime, just update the things that we know change. |
| */ |
| void updateResources() { |
| // Update the quick setting tiles |
| if (mQSPanel != null) { |
| mQSPanel.updateResources(); |
| } |
| |
| loadDimens(); |
| |
| if (mNotificationPanel != null) { |
| mNotificationPanel.updateResources(); |
| } |
| if (mBrightnessMirrorController != null) { |
| mBrightnessMirrorController.updateResources(); |
| } |
| } |
| |
| protected void loadDimens() { |
| final Resources res = mContext.getResources(); |
| |
| int oldBarHeight = mNaturalBarHeight; |
| mNaturalBarHeight = res.getDimensionPixelSize( |
| com.android.internal.R.dimen.status_bar_height); |
| if (mStatusBarWindowManager != null && mNaturalBarHeight != oldBarHeight) { |
| mStatusBarWindowManager.setBarHeight(mNaturalBarHeight); |
| } |
| mMaxAllowedKeyguardNotifications = res.getInteger( |
| R.integer.keyguard_max_notification_count); |
| |
| if (DEBUG) Log.v(TAG, "defineSlots"); |
| } |
| |
| // Visibility reporting |
| |
| protected void handleVisibleToUserChanged(boolean visibleToUser) { |
| if (visibleToUser) { |
| handleVisibleToUserChangedImpl(visibleToUser); |
| startNotificationLogging(); |
| } else { |
| stopNotificationLogging(); |
| handleVisibleToUserChangedImpl(visibleToUser); |
| } |
| } |
| |
| /** |
| * The LEDs are turned off when the notification panel is shown, even just a little bit. |
| * See also StatusBar.setPanelExpanded for another place where we attempt to do this. |
| */ |
| // Old BaseStatusBar.handleVisibileToUserChanged |
| private void handleVisibleToUserChangedImpl(boolean visibleToUser) { |
| try { |
| if (visibleToUser) { |
| boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp(); |
| boolean clearNotificationEffects = |
| !isPanelFullyCollapsed() && |
| (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED); |
| int notificationLoad = mNotificationData.getActiveNotifications().size(); |
| if (pinnedHeadsUp && isPanelFullyCollapsed()) { |
| notificationLoad = 1; |
| } else { |
| mMetricsLogger.histogram("note_load", notificationLoad); |
| } |
| mBarService.onPanelRevealed(clearNotificationEffects, notificationLoad); |
| } else { |
| mBarService.onPanelHidden(); |
| } |
| } catch (RemoteException ex) { |
| // Won't fail unless the world has ended. |
| } |
| } |
| |
| private void stopNotificationLogging() { |
| // Report all notifications as invisible and turn down the |
| // reporter. |
| if (!mCurrentlyVisibleNotifications.isEmpty()) { |
| logNotificationVisibilityChanges(Collections.<NotificationVisibility>emptyList(), |
| mCurrentlyVisibleNotifications); |
| recycleAllVisibilityObjects(mCurrentlyVisibleNotifications); |
| } |
| mHandler.removeCallbacks(mVisibilityReporter); |
| mStackScroller.setChildLocationsChangedListener(null); |
| } |
| |
| private void startNotificationLogging() { |
| mStackScroller.setChildLocationsChangedListener(mNotificationLocationsChangedListener); |
| // Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't |
| // cause the scroller to emit child location events. Hence generate |
| // one ourselves to guarantee that we're reporting visible |
| // notifications. |
| // (Note that in cases where the scroller does emit events, this |
| // additional event doesn't break anything.) |
| mNotificationLocationsChangedListener.onChildLocationsChanged(mStackScroller); |
| } |
| |
| private void logNotificationVisibilityChanges( |
| Collection<NotificationVisibility> newlyVisible, |
| Collection<NotificationVisibility> noLongerVisible) { |
| if (newlyVisible.isEmpty() && noLongerVisible.isEmpty()) { |
| return; |
| } |
| NotificationVisibility[] newlyVisibleAr = |
| newlyVisible.toArray(new NotificationVisibility[newlyVisible.size()]); |
| NotificationVisibility[] noLongerVisibleAr = |
| noLongerVisible.toArray(new NotificationVisibility[noLongerVisible.size()]); |
| try { |
| mBarService.onNotificationVisibilityChanged(newlyVisibleAr, noLongerVisibleAr); |
| } catch (RemoteException e) { |
| // Ignore. |
| } |
| |
| final int N = newlyVisible.size(); |
| if (N > 0) { |
| String[] newlyVisibleKeyAr = new String[N]; |
| for (int i = 0; i < N; i++) { |
| newlyVisibleKeyAr[i] = newlyVisibleAr[i].key; |
| } |
| |
| setNotificationsShown(newlyVisibleKeyAr); |
| } |
| } |
| |
| public void onKeyguardOccludedChanged(boolean keyguardOccluded) { |
| mNavigationBar.onKeyguardOccludedChanged(keyguardOccluded); |
| } |
| |
| // State logging |
| |
| private void logStateToEventlog() { |
| boolean isShowing = mStatusBarKeyguardViewManager.isShowing(); |
| boolean isOccluded = mStatusBarKeyguardViewManager.isOccluded(); |
| boolean isBouncerShowing = mStatusBarKeyguardViewManager.isBouncerShowing(); |
| boolean isSecure = mUnlockMethodCache.isMethodSecure(); |
| boolean canSkipBouncer = mUnlockMethodCache.canSkipBouncer(); |
| int stateFingerprint = getLoggingFingerprint(mState, |
| isShowing, |
| isOccluded, |
| isBouncerShowing, |
| isSecure, |
| canSkipBouncer); |
| if (stateFingerprint != mLastLoggedStateFingerprint) { |
| if (mStatusBarStateLog == null) { |
| mStatusBarStateLog = new LogMaker(MetricsEvent.VIEW_UNKNOWN); |
| } |
| mMetricsLogger.write(mStatusBarStateLog |
| .setCategory(isBouncerShowing ? MetricsEvent.BOUNCER : MetricsEvent.LOCKSCREEN) |
| .setType(isShowing ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE) |
| .setSubtype(isSecure ? 1 : 0)); |
| EventLogTags.writeSysuiStatusBarState(mState, |
| isShowing ? 1 : 0, |
| isOccluded ? 1 : 0, |
| isBouncerShowing ? 1 : 0, |
| isSecure ? 1 : 0, |
| canSkipBouncer ? 1 : 0); |
| mLastLoggedStateFingerprint = stateFingerprint; |
| } |
| } |
| |
| /** |
| * Returns a fingerprint of fields logged to eventlog |
| */ |
| private static int getLoggingFingerprint(int statusBarState, boolean keyguardShowing, |
| boolean keyguardOccluded, boolean bouncerShowing, boolean secure, |
| boolean currentlyInsecure) { |
| // Reserve 8 bits for statusBarState. We'll never go higher than |
| // that, right? Riiiight. |
| return (statusBarState & 0xFF) |
| | ((keyguardShowing ? 1 : 0) << 8) |
| | ((keyguardOccluded ? 1 : 0) << 9) |
| | ((bouncerShowing ? 1 : 0) << 10) |
| | ((secure ? 1 : 0) << 11) |
| | ((currentlyInsecure ? 1 : 0) << 12); |
| } |
| |
| // |
| // tracing |
| // |
| |
| void postStartTracing() { |
| mHandler.postDelayed(mStartTracing, 3000); |
| } |
| |
| void vibrate() { |
| android.os.Vibrator vib = (android.os.Vibrator)mContext.getSystemService( |
| Context.VIBRATOR_SERVICE); |
| vib.vibrate(250, VIBRATION_ATTRIBUTES); |
| } |
| |
| Runnable mStartTracing = new Runnable() { |
| @Override |
| public void run() { |
| vibrate(); |
| SystemClock.sleep(250); |
| Log.d(TAG, "startTracing"); |
| android.os.Debug.startMethodTracing("/data/statusbar-traces/trace"); |
| mHandler.postDelayed(mStopTracing, 10000); |
| } |
| }; |
| |
| Runnable mStopTracing = new Runnable() { |
| @Override |
| public void run() { |
| android.os.Debug.stopMethodTracing(); |
| Log.d(TAG, "stopTracing"); |
| vibrate(); |
| } |
| }; |
| |
| @Override |
| public void postQSRunnableDismissingKeyguard(final Runnable runnable) { |
| mHandler.post(() -> { |
| mLeaveOpenOnKeyguardHide = true; |
| executeRunnableDismissingKeyguard(() -> mHandler.post(runnable), null, false, false, |
| false); |
| }); |
| } |
| |
| @Override |
| public void postStartActivityDismissingKeyguard(final PendingIntent intent) { |
| mHandler.post(() -> startPendingIntentDismissingKeyguard(intent)); |
| } |
| |
| @Override |
| public void postStartActivityDismissingKeyguard(final Intent intent, int delay) { |
| mHandler.postDelayed(() -> |
| handleStartActivityDismissingKeyguard(intent, true /*onlyProvisioned*/), delay); |
| } |
| |
| private void handleStartActivityDismissingKeyguard(Intent intent, boolean onlyProvisioned) { |
| startActivityDismissingKeyguard(intent, onlyProvisioned, true /* dismissShade */); |
| } |
| |
| private static class FastColorDrawable extends Drawable { |
| private final int mColor; |
| |
| public FastColorDrawable(int color) { |
| mColor = 0xff000000 | color; |
| } |
| |
| @Override |
| public void draw(Canvas canvas) { |
| canvas.drawColor(mColor, PorterDuff.Mode.SRC); |
| } |
| |
| @Override |
| public void setAlpha(int alpha) { |
| } |
| |
| @Override |
| public void setColorFilter(ColorFilter colorFilter) { |
| } |
| |
| @Override |
| public int getOpacity() { |
| return PixelFormat.OPAQUE; |
| } |
| |
| @Override |
| public void setBounds(int left, int top, int right, int bottom) { |
| } |
| |
| @Override |
| public void setBounds(Rect bounds) { |
| } |
| } |
| |
| public void destroy() { |
| // Begin old BaseStatusBar.destroy(). |
| mContext.unregisterReceiver(mBaseBroadcastReceiver); |
| try { |
| mNotificationListener.unregisterAsSystemService(); |
| } catch (RemoteException e) { |
| // Ignore. |
| } |
| mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener); |
| // End old BaseStatusBar.destroy(). |
| if (mStatusBarWindow != null) { |
| mWindowManager.removeViewImmediate(mStatusBarWindow); |
| mStatusBarWindow = null; |
| } |
| if (mNavigationBarView != null) { |
| mWindowManager.removeViewImmediate(mNavigationBarView); |
| mNavigationBarView = null; |
| } |
| mContext.unregisterReceiver(mBroadcastReceiver); |
| mContext.unregisterReceiver(mDemoReceiver); |
| mAssistManager.destroy(); |
| |
| if (mQSPanel != null && mQSPanel.getHost() != null) { |
| mQSPanel.getHost().destroy(); |
| } |
| Dependency.get(ActivityStarterDelegate.class).setActivityStarterImpl(null); |
| mDeviceProvisionedController.removeCallback(mUserSetupObserver); |
| Dependency.get(ConfigurationController.class).removeCallback(mConfigurationListener); |
| } |
| |
| private boolean mDemoModeAllowed; |
| private boolean mDemoMode; |
| |
| @Override |
| public void dispatchDemoCommand(String command, Bundle args) { |
| if (!mDemoModeAllowed) { |
| mDemoModeAllowed = Settings.Global.getInt(mContext.getContentResolver(), |
| DEMO_MODE_ALLOWED, 0) != 0; |
| } |
| if (!mDemoModeAllowed) return; |
| if (command.equals(COMMAND_ENTER)) { |
| mDemoMode = true; |
| } else if (command.equals(COMMAND_EXIT)) { |
| mDemoMode = false; |
| checkBarModes(); |
| } else if (!mDemoMode) { |
| // automatically enter demo mode on first demo command |
| dispatchDemoCommand(COMMAND_ENTER, new Bundle()); |
| } |
| boolean modeChange = command.equals(COMMAND_ENTER) || command.equals(COMMAND_EXIT); |
| if ((modeChange || command.equals(COMMAND_VOLUME)) && mVolumeComponent != null) { |
| mVolumeComponent.dispatchDemoCommand(command, args); |
| } |
| if (modeChange || command.equals(COMMAND_CLOCK)) { |
| dispatchDemoCommandToView(command, args, R.id.clock); |
| } |
| if (modeChange || command.equals(COMMAND_BATTERY)) { |
| mBatteryController.dispatchDemoCommand(command, args); |
| } |
| if (modeChange || command.equals(COMMAND_STATUS)) { |
| ((StatusBarIconControllerImpl) mIconController).dispatchDemoCommand(command, args); |
| } |
| if (mNetworkController != null && (modeChange || command.equals(COMMAND_NETWORK))) { |
| mNetworkController.dispatchDemoCommand(command, args); |
| } |
| if (modeChange || command.equals(COMMAND_NOTIFICATIONS)) { |
| View notifications = mStatusBarView == null ? null |
| : mStatusBarView.findViewById(R.id.notification_icon_area); |
| if (notifications != null) { |
| String visible = args.getString("visible"); |
| int vis = mDemoMode && "false".equals(visible) ? View.INVISIBLE : View.VISIBLE; |
| notifications.setVisibility(vis); |
| } |
| } |
| if (command.equals(COMMAND_BARS)) { |
| String mode = args.getString("mode"); |
| int barMode = "opaque".equals(mode) ? MODE_OPAQUE : |
| "translucent".equals(mode) ? MODE_TRANSLUCENT : |
| "semi-transparent".equals(mode) ? MODE_SEMI_TRANSPARENT : |
| "transparent".equals(mode) ? MODE_TRANSPARENT : |
| "warning".equals(mode) ? MODE_WARNING : |
| -1; |
| if (barMode != -1) { |
| boolean animate = true; |
| if (mStatusBarView != null) { |
| mStatusBarView.getBarTransitions().transitionTo(barMode, animate); |
| } |
| if (mNavigationBar != null) { |
| mNavigationBar.getBarTransitions().transitionTo(barMode, animate); |
| } |
| } |
| } |
| } |
| |
| private void dispatchDemoCommandToView(String command, Bundle args, int id) { |
| if (mStatusBarView == null) return; |
| View v = mStatusBarView.findViewById(id); |
| if (v instanceof DemoMode) { |
| ((DemoMode)v).dispatchDemoCommand(command, args); |
| } |
| } |
| |
| /** |
| * @return The {@link StatusBarState} the status bar is in. |
| */ |
| public int getBarState() { |
| return mState; |
| } |
| |
| public boolean isPanelFullyCollapsed() { |
| return mNotificationPanel.isFullyCollapsed(); |
| } |
| |
| public void showKeyguard() { |
| if (mLaunchTransitionFadingAway) { |
| mNotificationPanel.animate().cancel(); |
| onLaunchTransitionFadingEnded(); |
| } |
| mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT); |
| if (mUserSwitcherController != null && mUserSwitcherController.useFullscreenUserSwitcher()) { |
| setBarState(StatusBarState.FULLSCREEN_USER_SWITCHER); |
| } else { |
| setBarState(StatusBarState.KEYGUARD); |
| } |
| updateKeyguardState(false /* goingToFullShade */, false /* fromShadeLocked */); |
| if (!mDeviceInteractive) { |
| |
| // If the screen is off already, we need to disable touch events because these might |
| // collapse the panel after we expanded it, and thus we would end up with a blank |
| // Keyguard. |
| mNotificationPanel.setTouchDisabled(true); |
| } |
| if (mState == StatusBarState.KEYGUARD) { |
| instantExpandNotificationsPanel(); |
| } else if (mState == StatusBarState.FULLSCREEN_USER_SWITCHER) { |
| instantCollapseNotificationPanel(); |
| } |
| mLeaveOpenOnKeyguardHide = false; |
| if (mDraggedDownRow != null) { |
| mDraggedDownRow.setUserLocked(false); |
| mDraggedDownRow.notifyHeightChanged(false /* needsAnimation */); |
| mDraggedDownRow = null; |
| } |
| mPendingRemoteInputView = null; |
| mAssistManager.onLockscreenShown(); |
| } |
| |
| private void onLaunchTransitionFadingEnded() { |
| mNotificationPanel.setAlpha(1.0f); |
| mNotificationPanel.onAffordanceLaunchEnded(); |
| releaseGestureWakeLock(); |
| runLaunchTransitionEndRunnable(); |
| mLaunchTransitionFadingAway = false; |
| mScrimController.forceHideScrims(false /* hide */); |
| updateMediaMetaData(true /* metaDataChanged */, true); |
| } |
| |
| public boolean isCollapsing() { |
| return mNotificationPanel.isCollapsing(); |
| } |
| |
| public void addPostCollapseAction(Runnable r) { |
| mPostCollapseRunnables.add(r); |
| } |
| |
| public boolean isInLaunchTransition() { |
| return mNotificationPanel.isLaunchTransitionRunning() |
| || mNotificationPanel.isLaunchTransitionFinished(); |
| } |
| |
| /** |
| * Fades the content of the keyguard away after the launch transition is done. |
| * |
| * @param beforeFading the runnable to be run when the circle is fully expanded and the fading |
| * starts |
| * @param endRunnable the runnable to be run when the transition is done |
| */ |
| public void fadeKeyguardAfterLaunchTransition(final Runnable beforeFading, |
| Runnable endRunnable) { |
| mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT); |
| mLaunchTransitionEndRunnable = endRunnable; |
| Runnable hideRunnable = new Runnable() { |
| @Override |
| public void run() { |
| mLaunchTransitionFadingAway = true; |
| if (beforeFading != null) { |
| beforeFading.run(); |
| } |
| mScrimController.forceHideScrims(true /* hide */); |
| updateMediaMetaData(false, true); |
| mNotificationPanel.setAlpha(1); |
| mStackScroller.setParentNotFullyVisible(true); |
| mNotificationPanel.animate() |
| .alpha(0) |
| .setStartDelay(FADE_KEYGUARD_START_DELAY) |
| .setDuration(FADE_KEYGUARD_DURATION) |
| .withLayer() |
| .withEndAction(new Runnable() { |
| @Override |
| public void run() { |
| onLaunchTransitionFadingEnded(); |
| } |
| }); |
| mCommandQueue.appTransitionStarting(SystemClock.uptimeMillis(), |
| LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true); |
| } |
| }; |
| if (mNotificationPanel.isLaunchTransitionRunning()) { |
| mNotificationPanel.setLaunchTransitionEndRunnable(hideRunnable); |
| } else { |
| hideRunnable.run(); |
| } |
| } |
| |
| /** |
| * Fades the content of the Keyguard while we are dozing and makes it invisible when finished |
| * fading. |
| */ |
| public void fadeKeyguardWhilePulsing() { |
| mNotificationPanel.animate() |
| .alpha(0f) |
| .setStartDelay(0) |
| .setDuration(FADE_KEYGUARD_DURATION_PULSING) |
| .setInterpolator(ScrimController.KEYGUARD_FADE_OUT_INTERPOLATOR) |
| .start(); |
| } |
| |
| /** |
| * Plays the animation when an activity that was occluding Keyguard goes away. |
| */ |
| public void animateKeyguardUnoccluding() { |
| mScrimController.animateKeyguardUnoccluding(500); |
| mNotificationPanel.setExpandedFraction(0f); |
| animateExpandNotificationsPanel(); |
| } |
| |
| /** |
| * Starts the timeout when we try to start the affordances on Keyguard. We usually rely that |
| * Keyguard goes away via fadeKeyguardAfterLaunchTransition, however, that might not happen |
| * because the launched app crashed or something else went wrong. |
| */ |
| public void startLaunchTransitionTimeout() { |
| mHandler.sendEmptyMessageDelayed(MSG_LAUNCH_TRANSITION_TIMEOUT, |
| LAUNCH_TRANSITION_TIMEOUT_MS); |
| } |
| |
| private void onLaunchTransitionTimeout() { |
| Log.w(TAG, "Launch transition: Timeout!"); |
| mNotificationPanel.onAffordanceLaunchEnded(); |
| releaseGestureWakeLock(); |
| mNotificationPanel.resetViews(); |
| } |
| |
| private void runLaunchTransitionEndRunnable() { |
| if (mLaunchTransitionEndRunnable != null) { |
| Runnable r = mLaunchTransitionEndRunnable; |
| |
| // mLaunchTransitionEndRunnable might call showKeyguard, which would execute it again, |
| // which would lead to infinite recursion. Protect against it. |
| mLaunchTransitionEndRunnable = null; |
| r.run(); |
| } |
| } |
| |
| /** |
| * @return true if we would like to stay in the shade, false if it should go away entirely |
| */ |
| public boolean hideKeyguard() { |
| Trace.beginSection("StatusBar#hideKeyguard"); |
| boolean staying = mLeaveOpenOnKeyguardHide; |
| setBarState(StatusBarState.SHADE); |
| View viewToClick = null; |
| if (mLeaveOpenOnKeyguardHide) { |
| mLeaveOpenOnKeyguardHide = false; |
| long delay = calculateGoingToFullShadeDelay(); |
| mNotificationPanel.animateToFullShade(delay); |
| if (mDraggedDownRow != null) { |
| mDraggedDownRow.setUserLocked(false); |
| mDraggedDownRow = null; |
| } |
| viewToClick = mPendingRemoteInputView; |
| mPendingRemoteInputView = null; |
| |
| // Disable layout transitions in navbar for this transition because the load is just |
| // too heavy for the CPU and GPU on any device. |
| if (mNavigationBar != null) { |
| mNavigationBar.disableAnimationsDuringHide(delay); |
| } |
| } else if (!mNotificationPanel.isCollapsing()) { |
| instantCollapseNotificationPanel(); |
| } |
| updateKeyguardState(staying, false /* fromShadeLocked */); |
| |
| if (viewToClick != null && viewToClick.isAttachedToWindow()) { |
| viewToClick.callOnClick(); |
| } |
| |
| // Keyguard state has changed, but QS is not listening anymore. Make sure to update the tile |
| // visibilities so next time we open the panel we know the correct height already. |
| if (mQSPanel != null) { |
| mQSPanel.refreshAllTiles(); |
| } |
| mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT); |
| releaseGestureWakeLock(); |
| mNotificationPanel.onAffordanceLaunchEnded(); |
| mNotificationPanel.animate().cancel(); |
| mNotificationPanel.setAlpha(1f); |
| Trace.endSection(); |
| return staying; |
| } |
| |
| private void releaseGestureWakeLock() { |
| if (mGestureWakeLock.isHeld()) { |
| mGestureWakeLock.release(); |
| } |
| } |
| |
| public long calculateGoingToFullShadeDelay() { |
| return mKeyguardFadingAwayDelay + mKeyguardFadingAwayDuration; |
| } |
| |
| /** |
| * Notifies the status bar that Keyguard is going away very soon. |
| */ |
| public void keyguardGoingAway() { |
| |
| // Treat Keyguard exit animation as an app transition to achieve nice transition for status |
| // bar. |
| mKeyguardGoingAway = true; |
| mKeyguardMonitor.notifyKeyguardGoingAway(true); |
| mCommandQueue.appTransitionPending(true); |
| } |
| |
| /** |
| * Notifies the status bar the Keyguard is fading away with the specified timings. |
| * |
| * @param startTime the start time of the animations in uptime millis |
| * @param delay the precalculated animation delay in miliseconds |
| * @param fadeoutDuration the duration of the exit animation, in milliseconds |
| */ |
| public void setKeyguardFadingAway(long startTime, long delay, long fadeoutDuration) { |
| mKeyguardFadingAway = true; |
| mKeyguardFadingAwayDelay = delay; |
| mKeyguardFadingAwayDuration = fadeoutDuration; |
| mWaitingForKeyguardExit = false; |
| mCommandQueue.appTransitionStarting(startTime + fadeoutDuration |
| - LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, |
| LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true); |
| recomputeDisableFlags(fadeoutDuration > 0 /* animate */); |
| mCommandQueue.appTransitionStarting( |
| startTime - LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, |
| LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true); |
| mKeyguardMonitor.notifyKeyguardFadingAway(delay, fadeoutDuration); |
| } |
| |
| public boolean isKeyguardFadingAway() { |
| return mKeyguardFadingAway; |
| } |
| |
| /** |
| * Notifies that the Keyguard fading away animation is done. |
| */ |
| public void finishKeyguardFadingAway() { |
| mKeyguardFadingAway = false; |
| mKeyguardGoingAway = false; |
| mKeyguardMonitor.notifyKeyguardDoneFading(); |
| } |
| |
| public void stopWaitingForKeyguardExit() { |
| mWaitingForKeyguardExit = false; |
| } |
| |
| private void updatePublicMode() { |
| final boolean showingKeyguard = mStatusBarKeyguardViewManager.isShowing(); |
| final boolean devicePublic = showingKeyguard |
| && mStatusBarKeyguardViewManager.isSecure(mCurrentUserId); |
| |
| // Look for public mode users. Users are considered public in either case of: |
| // - device keyguard is shown in secure mode; |
| // - profile is locked with a work challenge. |
| for (int i = mCurrentProfiles.size() - 1; i >= 0; i--) { |
| final int userId = mCurrentProfiles.valueAt(i).id; |
| boolean isProfilePublic = devicePublic; |
| if (!devicePublic && userId != mCurrentUserId) { |
| if (mStatusBarKeyguardViewManager.isSecure(userId)) { |
| isProfilePublic = mKeyguardManager.isDeviceLocked(userId); |
| } |
| } |
| setLockscreenPublicMode(isProfilePublic, userId); |
| } |
| } |
| |
| protected void updateKeyguardState(boolean goingToFullShade, boolean fromShadeLocked) { |
| Trace.beginSection("StatusBar#updateKeyguardState"); |
| if (mState == StatusBarState.KEYGUARD) { |
| mKeyguardIndicationController.setVisible(true); |
| mNotificationPanel.resetViews(); |
| if (mKeyguardUserSwitcher != null) { |
| mKeyguardUserSwitcher.setKeyguard(true, fromShadeLocked); |
| } |
| mStatusBarView.removePendingHideExpandedRunnables(); |
| } else { |
| mKeyguardIndicationController.setVisible(false); |
| if (mKeyguardUserSwitcher != null) { |
| mKeyguardUserSwitcher.setKeyguard(false, |
| goingToFullShade || |
| mState == StatusBarState.SHADE_LOCKED || |
| fromShadeLocked); |
| } |
| } |
| if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) { |
| mScrimController.setKeyguardShowing(true); |
| } else { |
| mScrimController.setKeyguardShowing(false); |
| } |
| mNotificationPanel.setBarState(mState, mKeyguardFadingAway, goingToFullShade); |
| updateDozingState(); |
| updatePublicMode(); |
| updateStackScrollerState(goingToFullShade, fromShadeLocked); |
| updateNotifications(); |
| checkBarModes(); |
| updateMediaMetaData(false, mState != StatusBarState.KEYGUARD); |
| mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(), |
| mStatusBarKeyguardViewManager.isSecure(), |
| mStatusBarKeyguardViewManager.isOccluded()); |
| Trace.endSection(); |
| } |
| |
| private void updateDozingState() { |
| Trace.beginSection("StatusBar#updateDozingState"); |
| boolean animate = !mDozing && mDozeScrimController.isPulsing(); |
| mNotificationPanel.setDozing(mDozing, animate); |
| mStackScroller.setDark(mDozing, animate, mWakeUpTouchLocation); |
| mScrimController.setDozing(mDozing); |
| mKeyguardIndicationController.setDozing(mDozing); |
| mNotificationPanel.setDark(mDozing); |
| updateQsExpansionEnabled(); |
| |
| // Immediately abort the dozing from the doze scrim controller in case of wake-and-unlock |
| // for pulsing so the Keyguard fade-out animation scrim can take over. |
| mDozeScrimController.setDozing(mDozing && |
| mFingerprintUnlockController.getMode() |
| != FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING, animate); |
| updateRowStates(); |
| Trace.endSection(); |
| } |
| |
| public void updateStackScrollerState(boolean goingToFullShade, boolean fromShadeLocked) { |
| if (mStackScroller == null) return; |
| boolean onKeyguard = mState == StatusBarState.KEYGUARD; |
| boolean publicMode = isAnyProfilePublicMode(); |
| mStackScroller.setHideSensitive(publicMode, goingToFullShade); |
| mStackScroller.setDimmed(onKeyguard, fromShadeLocked /* animate */); |
| mStackScroller.setExpandingEnabled(!onKeyguard); |
| ActivatableNotificationView activatedChild = mStackScroller.getActivatedChild(); |
| mStackScroller.setActivatedChild(null); |
| if (activatedChild != null) { |
| activatedChild.makeInactive(false /* animate */); |
| } |
| } |
| |
| public void userActivity() { |
| if (mState == StatusBarState.KEYGUARD) { |
| mKeyguardViewMediatorCallback.userActivity(); |
| } |
| } |
| |
| public boolean interceptMediaKey(KeyEvent event) { |
| return mState == StatusBarState.KEYGUARD |
| && mStatusBarKeyguardViewManager.interceptMediaKey(event); |
| } |
| |
| protected boolean shouldUnlockOnMenuPressed() { |
| return mDeviceInteractive && mState != StatusBarState.SHADE |
| && mStatusBarKeyguardViewManager.shouldDismissOnMenuPressed(); |
| } |
| |
| public boolean onMenuPressed() { |
| if (shouldUnlockOnMenuPressed()) { |
| animateCollapsePanels( |
| CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */); |
| return true; |
| } |
| return false; |
| } |
| |
| public void endAffordanceLaunch() { |
| releaseGestureWakeLock(); |
| mNotificationPanel.onAffordanceLaunchEnded(); |
| } |
| |
| public boolean onBackPressed() { |
| if (mStatusBarKeyguardViewManager.onBackPressed()) { |
| return true; |
| } |
| if (mNotificationPanel.isQsExpanded()) { |
| if (mNotificationPanel.isQsDetailShowing()) { |
| mNotificationPanel.closeQsDetail(); |
| } else { |
| mNotificationPanel.animateCloseQs(); |
| } |
| return true; |
| } |
| if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED) { |
| animateCollapsePanels(); |
| return true; |
| } |
| if (mKeyguardUserSwitcher.hideIfNotSimple(true)) { |
| return true; |
| } |
| return false; |
| } |
| |
| public boolean onSpacePressed() { |
| if (mDeviceInteractive && mState != StatusBarState.SHADE) { |
| animateCollapsePanels( |
| CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */); |
| return true; |
| } |
| return false; |
| } |
| |
| private void showBouncerIfKeyguard() { |
| if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) { |
| showBouncer(); |
| } |
| } |
| |
| protected void showBouncer() { |
| mWaitingForKeyguardExit = mStatusBarKeyguardViewManager.isShowing(); |
| mStatusBarKeyguardViewManager.dismiss(); |
| } |
| |
| private void instantExpandNotificationsPanel() { |
| |
| // Make our window larger and the panel expanded. |
| makeExpandedVisible(true); |
| mNotificationPanel.expand(false /* animate */); |
| } |
| |
| private void instantCollapseNotificationPanel() { |
| mNotificationPanel.instantCollapse(); |
| } |
| |
| @Override |
| public void onActivated(ActivatableNotificationView view) { |
| mLockscreenGestureLogger.write( |
| MetricsEvent.ACTION_LS_NOTE, |
| 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */); |
| mKeyguardIndicationController.showTransientIndication(R.string.notification_tap_again); |
| ActivatableNotificationView previousView = mStackScroller.getActivatedChild(); |
| if (previousView != null) { |
| previousView.makeInactive(true /* animate */); |
| } |
| mStackScroller.setActivatedChild(view); |
| } |
| |
| /** |
| * @param state The {@link StatusBarState} to set. |
| */ |
| public void setBarState(int state) { |
| // If we're visible and switched to SHADE_LOCKED (the user dragged |
| // down on the lockscreen), clear notification LED, vibration, |
| // ringing. |
| // Other transitions are covered in handleVisibleToUserChanged(). |
| if (state != mState && mVisible && (state == StatusBarState.SHADE_LOCKED |
| || (state == StatusBarState.SHADE && isGoingToNotificationShade()))) { |
| clearNotificationEffects(); |
| } |
| if (state == StatusBarState.KEYGUARD) { |
| removeRemoteInputEntriesKeptUntilCollapsed(); |
| maybeEscalateHeadsUp(); |
| } |
| mState = state; |
| mGroupManager.setStatusBarState(state); |
| mHeadsUpManager.setStatusBarState(state); |
| mFalsingManager.setStatusBarState(state); |
| mStatusBarWindowManager.setStatusBarState(state); |
| mStackScroller.setStatusBarState(state); |
| updateReportRejectedTouchVisibility(); |
| updateDozing(); |
| mNotificationShelf.setStatusBarState(state); |
| } |
| |
| @Override |
| public void onActivationReset(ActivatableNotificationView view) { |
| if (view == mStackScroller.getActivatedChild()) { |
| mKeyguardIndicationController.hideTransientIndication(); |
| mStackScroller.setActivatedChild(null); |
| } |
| } |
| |
| public void onTrackingStarted() { |
| runPostCollapseRunnables(); |
| } |
| |
| public void onClosingFinished() { |
| runPostCollapseRunnables(); |
| if (!isPanelFullyCollapsed()) { |
| // if we set it not to be focusable when collapsing, we have to undo it when we aborted |
| // the closing |
| mStatusBarWindowManager.setStatusBarFocusable(true); |
| } |
| } |
| |
| public void onUnlockHintStarted() { |
| mFalsingManager.onUnlockHintStarted(); |
| mKeyguardIndicationController.showTransientIndication(R.string.keyguard_unlock); |
| } |
| |
| public void onHintFinished() { |
| // Delay the reset a bit so the user can read the text. |
| mKeyguardIndicationController.hideTransientIndicationDelayed(HINT_RESET_DELAY_MS); |
| } |
| |
| public void onCameraHintStarted() { |
| mFalsingManager.onCameraHintStarted(); |
| mKeyguardIndicationController.showTransientIndication(R.string.camera_hint); |
| } |
| |
| public void onVoiceAssistHintStarted() { |
| mFalsingManager.onLeftAffordanceHintStarted(); |
| mKeyguardIndicationController.showTransientIndication(R.string.voice_hint); |
| } |
| |
| public void onPhoneHintStarted() { |
| mFalsingManager.onLeftAffordanceHintStarted(); |
| mKeyguardIndicationController.showTransientIndication(R.string.phone_hint); |
| } |
| |
| public void onTrackingStopped(boolean expand) { |
| if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) { |
| if (!expand && !mUnlockMethodCache.canSkipBouncer()) { |
| showBouncerIfKeyguard(); |
| } |
| } |
| } |
| |
| protected int getMaxKeyguardNotifications(boolean recompute) { |
| if (recompute) { |
| mMaxKeyguardNotifications = Math.max(1, |
| mNotificationPanel.computeMaxKeyguardNotifications( |
| mMaxAllowedKeyguardNotifications)); |
| return mMaxKeyguardNotifications; |
| } |
| return mMaxKeyguardNotifications; |
| } |
| |
| public int getMaxKeyguardNotifications() { |
| return getMaxKeyguardNotifications(false /* recompute */); |
| } |
| |
| // TODO: Figure out way to remove this. |
| public NavigationBarView getNavigationBarView() { |
| return (NavigationBarView) mNavigationBar.getView(); |
| } |
| |
| // ---------------------- DragDownHelper.OnDragDownListener ------------------------------------ |
| |
| |
| /* Only ever called as a consequence of a lockscreen expansion gesture. */ |
| @Override |
| public boolean onDraggedDown(View startingChild, int dragLengthY) { |
| if (hasActiveNotifications() && (!isDozing() || isPulsing())) { |
| mLockscreenGestureLogger.write( |
| MetricsEvent.ACTION_LS_SHADE, |
| (int) (dragLengthY / mDisplayMetrics.density), |
| 0 /* velocityDp - N/A */); |
| |
| // We have notifications, go to locked shade. |
| goToLockedShade(startingChild); |
| if (startingChild instanceof ExpandableNotificationRow) { |
| ExpandableNotificationRow row = (ExpandableNotificationRow) startingChild; |
| row.onExpandedByGesture(true /* drag down is always an open */); |
| } |
| return true; |
| } else { |
| // abort gesture. |
| return false; |
| } |
| } |
| |
| @Override |
| public void onDragDownReset() { |
| mStackScroller.setDimmed(true /* dimmed */, true /* animated */); |
| mStackScroller.resetScrollPosition(); |
| } |
| |
| @Override |
| public void onCrossedThreshold(boolean above) { |
| mStackScroller.setDimmed(!above /* dimmed */, true /* animate */); |
| } |
| |
| @Override |
| public void onTouchSlopExceeded() { |
| mStackScroller.removeLongPressCallback(); |
| } |
| |
| @Override |
| public void setEmptyDragAmount(float amount) { |
| mNotificationPanel.setEmptyDragAmount(amount); |
| } |
| |
| /** |
| * If secure with redaction: Show bouncer, go to unlocked shade. |
| * |
| * <p>If secure without redaction or no security: Go to {@link StatusBarState#SHADE_LOCKED}.</p> |
| * |
| * @param expandView The view to expand after going to the shade. |
| */ |
| public void goToLockedShade(View expandView) { |
| int userId = mCurrentUserId; |
| ExpandableNotificationRow row = null; |
| if (expandView instanceof ExpandableNotificationRow) { |
| row = (ExpandableNotificationRow) expandView; |
| row.setUserExpanded(true /* userExpanded */, true /* allowChildExpansion */); |
| // Indicate that the group expansion is changing at this time -- this way the group |
| // and children backgrounds / divider animations will look correct. |
| row.setGroupExpansionChanging(true); |
| if (row.getStatusBarNotification() != null) { |
| userId = row.getStatusBarNotification().getUserId(); |
| } |
| } |
| boolean fullShadeNeedsBouncer = !userAllowsPrivateNotificationsInPublic(mCurrentUserId) |
| || !mShowLockscreenNotifications || mFalsingManager.shouldEnforceBouncer(); |
| if (isLockscreenPublicMode(userId) && fullShadeNeedsBouncer) { |
| mLeaveOpenOnKeyguardHide = true; |
| showBouncerIfKeyguard(); |
| mDraggedDownRow = row; |
| mPendingRemoteInputView = null; |
| } else { |
| mNotificationPanel.animateToFullShade(0 /* delay */); |
| setBarState(StatusBarState.SHADE_LOCKED); |
| updateKeyguardState(false /* goingToFullShade */, false /* fromShadeLocked */); |
| } |
| } |
| |
| public void onLockedNotificationImportanceChange(OnDismissAction dismissAction) { |
| mLeaveOpenOnKeyguardHide = true; |
| dismissKeyguardThenExecute(dismissAction, true /* afterKeyguardGone */); |
| } |
| |
| protected void onLockedRemoteInput(ExpandableNotificationRow row, View clicked) { |
| mLeaveOpenOnKeyguardHide = true; |
| showBouncer(); |
| mPendingRemoteInputView = clicked; |
| } |
| |
| protected void onMakeExpandedVisibleForRemoteInput(ExpandableNotificationRow row, |
| View clickedView) { |
| if (isKeyguardShowing()) { |
| onLockedRemoteInput(row, clickedView); |
| } else { |
| row.setUserExpanded(true); |
| row.getPrivateLayout().setOnExpandedVisibleListener(clickedView::performClick); |
| } |
| } |
| |
| protected boolean startWorkChallengeIfNecessary(int userId, IntentSender intendSender, |
| String notificationKey) { |
| // Clear pending remote view, as we do not want to trigger pending remote input view when |
| // it's called by other code |
| mPendingWorkRemoteInputView = null; |
| // Begin old BaseStatusBar.startWorkChallengeIfNecessary. |
| final Intent newIntent = mKeyguardManager.createConfirmDeviceCredentialIntent(null, |
| null, userId); |
| if (newIntent == null) { |
| return false; |
| } |
| final Intent callBackIntent = new Intent(NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION); |
| callBackIntent.putExtra(Intent.EXTRA_INTENT, intendSender); |
| callBackIntent.putExtra(Intent.EXTRA_INDEX, notificationKey); |
| callBackIntent.setPackage(mContext.getPackageName()); |
| |
| PendingIntent callBackPendingIntent = PendingIntent.getBroadcast( |
| mContext, |
| 0, |
| callBackIntent, |
| PendingIntent.FLAG_CANCEL_CURRENT | |
| PendingIntent.FLAG_ONE_SHOT | |
| PendingIntent.FLAG_IMMUTABLE); |
| newIntent.putExtra( |
| Intent.EXTRA_INTENT, |
| callBackPendingIntent.getIntentSender()); |
| try { |
| ActivityManager.getService().startConfirmDeviceCredentialIntent(newIntent, |
| null /*options*/); |
| } catch (RemoteException ex) { |
| // ignore |
| } |
| return true; |
| // End old BaseStatusBar.startWorkChallengeIfNecessary. |
| } |
| |
| protected void onLockedWorkRemoteInput(int userId, ExpandableNotificationRow row, |
| View clicked) { |
| // Collapse notification and show work challenge |
| animateCollapsePanels(); |
| startWorkChallengeIfNecessary(userId, null, null); |
| // Add pending remote input view after starting work challenge, as starting work challenge |
| // will clear all previous pending review view |
| mPendingWorkRemoteInputView = clicked; |
| } |
| |
| private boolean isAnyProfilePublicMode() { |
| for (int i = mCurrentProfiles.size() - 1; i >= 0; i--) { |
| if (isLockscreenPublicMode(mCurrentProfiles.valueAt(i).id)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| protected void onWorkChallengeChanged() { |
| updatePublicMode(); |
| updateNotifications(); |
| if (mPendingWorkRemoteInputView != null && !isAnyProfilePublicMode()) { |
| // Expand notification panel and the notification row, then click on remote input view |
| final Runnable clickPendingViewRunnable = new Runnable() { |
| @Override |
| public void run() { |
| final View pendingWorkRemoteInputView = mPendingWorkRemoteInputView; |
| if (pendingWorkRemoteInputView == null) { |
| return; |
| } |
| |
| // Climb up the hierarchy until we get to the container for this row. |
| ViewParent p = pendingWorkRemoteInputView.getParent(); |
| while (!(p instanceof ExpandableNotificationRow)) { |
| if (p == null) { |
| return; |
| } |
| p = p.getParent(); |
| } |
| |
| final ExpandableNotificationRow row = (ExpandableNotificationRow) p; |
| ViewParent viewParent = row.getParent(); |
| if (viewParent instanceof NotificationStackScrollLayout) { |
| final NotificationStackScrollLayout scrollLayout = |
| (NotificationStackScrollLayout) viewParent; |
| row.makeActionsVisibile(); |
| row.post(new Runnable() { |
| @Override |
| public void run() { |
| final Runnable finishScrollingCallback = new Runnable() { |
| @Override |
| public void run() { |
| mPendingWorkRemoteInputView.callOnClick(); |
| mPendingWorkRemoteInputView = null; |
| scrollLayout.setFinishScrollingCallback(null); |
| } |
| }; |
| if (scrollLayout.scrollTo(row)) { |
| // It scrolls! So call it when it's finished. |
| scrollLayout.setFinishScrollingCallback( |
| finishScrollingCallback); |
| } else { |
| // It does not scroll, so call it now! |
| finishScrollingCallback.run(); |
| } |
| } |
| }); |
| } |
| } |
| }; |
| mNotificationPanel.getViewTreeObserver().addOnGlobalLayoutListener( |
| new ViewTreeObserver.OnGlobalLayoutListener() { |
| @Override |
| public void onGlobalLayout() { |
| if (mNotificationPanel.mStatusBar.getStatusBarWindow() |
| .getHeight() != mNotificationPanel.mStatusBar |
| .getStatusBarHeight()) { |
| mNotificationPanel.getViewTreeObserver() |
| .removeOnGlobalLayoutListener(this); |
| mNotificationPanel.post(clickPendingViewRunnable); |
| } |
| } |
| }); |
| instantExpandNotificationsPanel(); |
| } |
| } |
| |
| @Override |
| public void onExpandClicked(Entry clickedEntry, boolean nowExpanded) { |
| mHeadsUpManager.setExpanded(clickedEntry, nowExpanded); |
| if (mState == StatusBarState.KEYGUARD && nowExpanded) { |
| goToLockedShade(clickedEntry.row); |
| } |
| } |
| |
| /** |
| * Goes back to the keyguard after hanging around in {@link StatusBarState#SHADE_LOCKED}. |
| */ |
| public void goToKeyguard() { |
| if (mState == StatusBarState.SHADE_LOCKED) { |
| mStackScroller.onGoToKeyguard(); |
| setBarState(StatusBarState.KEYGUARD); |
| updateKeyguardState(false /* goingToFullShade */, true /* fromShadeLocked*/); |
| } |
| } |
| |
| public long getKeyguardFadingAwayDelay() { |
| return mKeyguardFadingAwayDelay; |
| } |
| |
| public long getKeyguardFadingAwayDuration() { |
| return mKeyguardFadingAwayDuration; |
| } |
| |
| public void setBouncerShowing(boolean bouncerShowing) { |
| mBouncerShowing = bouncerShowing; |
| mStatusBarView.setBouncerShowing(bouncerShowing); |
| recomputeDisableFlags(true /* animate */); |
| } |
| |
| public void onStartedGoingToSleep() { |
| mStartedGoingToSleep = true; |
| } |
| |
| public void onFinishedGoingToSleep() { |
| mNotificationPanel.onAffordanceLaunchEnded(); |
| releaseGestureWakeLock(); |
| mLaunchCameraOnScreenTurningOn = false; |
| mStartedGoingToSleep = false; |
| mDeviceInteractive = false; |
| mWakeUpComingFromTouch = false; |
| mWakeUpTouchLocation = null; |
| mStackScroller.setAnimationsEnabled(false); |
| mVisualStabilityManager.setScreenOn(false); |
| updateVisibleToUser(); |
| if (mLaunchCameraOnFinishedGoingToSleep) { |
| mLaunchCameraOnFinishedGoingToSleep = false; |
| |
| // This gets executed before we will show Keyguard, so post it in order that the state |
| // is correct. |
| mHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| onCameraLaunchGestureDetected(mLastCameraLaunchSource); |
| } |
| }); |
| } |
| } |
| |
| public void onStartedWakingUp() { |
| mDeviceInteractive = true; |
| mStackScroller.setAnimationsEnabled(true); |
| mVisualStabilityManager.setScreenOn(true); |
| mNotificationPanel.setTouchDisabled(false); |
| updateVisibleToUser(); |
| } |
| |
| public void onScreenTurningOn() { |
| mScreenTurningOn = true; |
| mFalsingManager.onScreenTurningOn(); |
| mNotificationPanel.onScreenTurningOn(); |
| if (mLaunchCameraOnScreenTurningOn) { |
| mNotificationPanel.launchCamera(false, mLastCameraLaunchSource); |
| mLaunchCameraOnScreenTurningOn = false; |
| } |
| } |
| |
| private void vibrateForCameraGesture() { |
| // Make sure to pass -1 for repeat so VibratorService doesn't stop us when going to sleep. |
| mVibrator.vibrate(mCameraLaunchGestureVibePattern, -1 /* repeat */); |
| } |
| |
| public void onScreenTurnedOn() { |
| mScreenTurningOn = false; |
| mDozeScrimController.onScreenTurnedOn(); |
| } |
| |
| @Override |
| public void showScreenPinningRequest(int taskId) { |
| if (mKeyguardMonitor.isShowing()) { |
| // Don't allow apps to trigger this from keyguard. |
| return; |
| } |
| // Show screen pinning request, since this comes from an app, show 'no thanks', button. |
| showScreenPinningRequest(taskId, true); |
| } |
| |
| public void showScreenPinningRequest(int taskId, boolean allowCancel) { |
| mScreenPinningRequest.showPrompt(taskId, allowCancel); |
| } |
| |
| public boolean hasActiveNotifications() { |
| return !mNotificationData.getActiveNotifications().isEmpty(); |
| } |
| |
| public void wakeUpIfDozing(long time, View where) { |
| if (mDozing) { |
| PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); |
| pm.wakeUp(time, "com.android.systemui:NODOZE"); |
| mWakeUpComingFromTouch = true; |
| where.getLocationInWindow(mTmpInt2); |
| mWakeUpTouchLocation = new PointF(mTmpInt2[0] + where.getWidth() / 2, |
| mTmpInt2[1] + where.getHeight() / 2); |
| mNotificationPanel.setTouchDisabled(false); |
| mStatusBarKeyguardViewManager.notifyDeviceWakeUpRequested(); |
| mFalsingManager.onScreenOnFromTouch(); |
| } |
| } |
| |
| @Override |
| public void appTransitionCancelled() { |
| EventBus.getDefault().send(new AppTransitionFinishedEvent()); |
| } |
| |
| @Override |
| public void appTransitionFinished() { |
| EventBus.getDefault().send(new AppTransitionFinishedEvent()); |
| } |
| |
| @Override |
| public void onCameraLaunchGestureDetected(int source) { |
| mLastCameraLaunchSource = source; |
| if (mStartedGoingToSleep) { |
| mLaunchCameraOnFinishedGoingToSleep = true; |
| return; |
| } |
| if (!mNotificationPanel.canCameraGestureBeLaunched( |
| mStatusBarKeyguardViewManager.isShowing() && mExpandedVisible)) { |
| return; |
| } |
| if (!mDeviceInteractive) { |
| PowerManager pm = mContext.getSystemService(PowerManager.class); |
| pm.wakeUp(SystemClock.uptimeMillis(), "com.android.systemui:CAMERA_GESTURE"); |
| mStatusBarKeyguardViewManager.notifyDeviceWakeUpRequested(); |
| } |
| vibrateForCameraGesture(); |
| if (!mStatusBarKeyguardViewManager.isShowing()) { |
| startActivity(KeyguardBottomAreaView.INSECURE_CAMERA_INTENT, |
| true /* dismissShade */); |
| } else { |
| if (!mDeviceInteractive) { |
| // Avoid flickering of the scrim when we instant launch the camera and the bouncer |
| // comes on. |
| mScrimController.dontAnimateBouncerChangesUntilNextFrame(); |
| mGestureWakeLock.acquire(LAUNCH_TRANSITION_TIMEOUT_MS + 1000L); |
| } |
| if (mScreenTurningOn || mStatusBarKeyguardViewManager.isScreenTurnedOn()) { |
| mNotificationPanel.launchCamera(mDeviceInteractive /* animate */, source); |
| } else { |
| // We need to defer the camera launch until the screen comes on, since otherwise |
| // we will dismiss us too early since we are waiting on an activity to be drawn and |
| // incorrectly get notified because of the screen on event (which resumes and pauses |
| // some activities) |
| mLaunchCameraOnScreenTurningOn = true; |
| } |
| } |
| } |
| |
| public void notifyFpAuthModeChanged() { |
| updateDozing(); |
| } |
| |
| private void updateDozing() { |
| Trace.beginSection("StatusBar#updateDozing"); |
| // When in wake-and-unlock while pulsing, keep dozing state until fully unlocked. |
| mDozing = mDozingRequested && mState == StatusBarState.KEYGUARD |
| || mFingerprintUnlockController.getMode() |
| == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING; |
| mStatusBarWindowManager.setDozing(mDozing); |
| updateDozingState(); |
| Trace.endSection(); |
| } |
| |
| public boolean isKeyguardShowing() { |
| return mStatusBarKeyguardViewManager.isShowing(); |
| } |
| |
| private final class DozeServiceHost implements DozeHost { |
| private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); |
| |
| @Override |
| public String toString() { |
| return "PSB.DozeServiceHost[mCallbacks=" + mCallbacks.size() + "]"; |
| } |
| |
| public void firePowerSaveChanged(boolean active) { |
| for (Callback callback : mCallbacks) { |
| callback.onPowerSaveChanged(active); |
| } |
| } |
| |
| public void fireNotificationHeadsUp() { |
| for (Callback callback : mCallbacks) { |
| callback.onNotificationHeadsUp(); |
| } |
| } |
| |
| @Override |
| public void addCallback(@NonNull Callback callback) { |
| mCallbacks.add(callback); |
| } |
| |
| @Override |
| public void removeCallback(@NonNull Callback callback) { |
| mCallbacks.remove(callback); |
| } |
| |
| @Override |
| public void startDozing() { |
| if (!mDozingRequested) { |
| mDozingRequested = true; |
| DozeLog.traceDozing(mContext, mDozing); |
| updateDozing(); |
| } |
| } |
| |
| @Override |
| public void pulseWhileDozing(@NonNull PulseCallback callback, int reason) { |
| mDozeScrimController.pulse(new PulseCallback() { |
| |
| @Override |
| public void onPulseStarted() { |
| callback.onPulseStarted(); |
| Collection<HeadsUpManager.HeadsUpEntry> pulsingEntries = |
| mHeadsUpManager.getAllEntries(); |
| if (!pulsingEntries.isEmpty()) { |
| // Only pulse the stack scroller if there's actually something to show. |
| // Otherwise just show the always-on screen. |
| setPulsing(pulsingEntries); |
| } |
| } |
| |
| @Override |
| public void onPulseFinished() { |
| callback.onPulseFinished(); |
| setPulsing(null); |
| } |
| |
| private void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing) { |
| mStackScroller.setPulsing(pulsing); |
| mNotificationPanel.setPulsing(pulsing != null); |
| mVisualStabilityManager.setPulsing(pulsing != null); |
| } |
| }, reason); |
| } |
| |
| @Override |
| public void stopDozing() { |
| if (mDozingRequested) { |
| mDozingRequested = false; |
| DozeLog.traceDozing(mContext, mDozing); |
| updateDozing(); |
| } |
| } |
| |
| @Override |
| public void dozeTimeTick() { |
| mKeyguardStatusView.refreshTime(); |
| } |
| |
| @Override |
| public boolean isPowerSaveActive() { |
| return mBatteryController.isPowerSave(); |
| } |
| |
| @Override |
| public boolean isPulsingBlocked() { |
| return mFingerprintUnlockController.getMode() |
| == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK; |
| } |
| |
| @Override |
| public void startPendingIntentDismissingKeyguard(PendingIntent intent) { |
| StatusBar.this.startPendingIntentDismissingKeyguard(intent); |
| } |
| |
| @Override |
| public void abortPulsing() { |
| mDozeScrimController.abortPulsing(); |
| } |
| |
| @Override |
| public void extendPulse() { |
| mDozeScrimController.extendPulse(); |
| } |
| |
| } |
| |
| // Begin Extra BaseStatusBar methods. |
| |
| protected CommandQueue mCommandQueue; |
| protected IStatusBarService mBarService; |
| |
| // all notifications |
| protected NotificationData mNotificationData; |
| protected NotificationStackScrollLayout mStackScroller; |
| |
| protected NotificationGroupManager mGroupManager = new NotificationGroupManager(); |
| |
| protected RemoteInputController mRemoteInputController; |
| |
| // for heads up notifications |
| protected HeadsUpManager mHeadsUpManager; |
| |
| // handling reordering |
| protected VisualStabilityManager mVisualStabilityManager = new VisualStabilityManager(); |
| |
| protected int mCurrentUserId = 0; |
| final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>(); |
| |
| protected int mLayoutDirection = -1; // invalid |
| protected AccessibilityManager mAccessibilityManager; |
| |
| protected boolean mDeviceInteractive; |
| |
| protected boolean mVisible; |
| protected ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>(); |
| protected ArraySet<Entry> mRemoteInputEntriesToRemoveOnCollapse = new ArraySet<>(); |
| |
| /** |
| * Notifications with keys in this set are not actually around anymore. We kept them around |
| * when they were canceled in response to a remote input interaction. This allows us to show |
| * what you replied and allows you to continue typing into it. |
| */ |
| protected ArraySet<String> mKeysKeptForRemoteInput = new ArraySet<>(); |
| |
| // mScreenOnFromKeyguard && mVisible. |
| private boolean mVisibleToUser; |
| |
| private Locale mLocale; |
| |
| protected boolean mUseHeadsUp = false; |
| protected boolean mHeadsUpTicker = false; |
| protected boolean mDisableNotificationAlerts = false; |
| |
| protected DevicePolicyManager mDevicePolicyManager; |
| protected IDreamManager mDreamManager; |
| protected PowerManager mPowerManager; |
| protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; |
| |
| // public mode, private notifications, etc |
| private final SparseBooleanArray mLockscreenPublicMode = new SparseBooleanArray(); |
| private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray(); |
| private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray(); |
| |
| private UserManager mUserManager; |
| |
| protected KeyguardManager mKeyguardManager; |
| private LockPatternUtils mLockPatternUtils; |
| private DeviceProvisionedController mDeviceProvisionedController; |
| |
| // UI-specific methods |
| |
| protected WindowManager mWindowManager; |
| protected IWindowManager mWindowManagerService; |
| |
| protected Display mDisplay; |
| |
| protected RecentsComponent mRecents; |
| |
| protected int mZenMode; |
| |
| // which notification is currently being longpress-examined by the user |
| private NotificationGuts mNotificationGutsExposed; |
| private MenuItem mGutsMenuItem; |
| |
| private KeyboardShortcuts mKeyboardShortcuts; |
| |
| protected NotificationShelf mNotificationShelf; |
| protected DismissView mDismissView; |
| protected EmptyShadeView mEmptyShadeView; |
| |
| private NotificationClicker mNotificationClicker = new NotificationClicker(); |
| |
| protected AssistManager mAssistManager; |
| |
| protected boolean mVrMode; |
| |
| private Set<String> mNonBlockablePkgs; |
| |
| @Override // NotificationData.Environment |
| public boolean isDeviceProvisioned() { |
| return mDeviceProvisionedController.isDeviceProvisioned(); |
| } |
| |
| private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() { |
| @Override |
| public void onVrStateChanged(boolean enabled) { |
| mVrMode = enabled; |
| } |
| }; |
| |
| public boolean isDeviceInVrMode() { |
| return mVrMode; |
| } |
| |
| private final DeviceProvisionedListener mDeviceProvisionedListener = |
| new DeviceProvisionedListener() { |
| @Override |
| public void onDeviceProvisionedChanged() { |
| updateNotifications(); |
| } |
| }; |
| |
| protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) { |
| @Override |
| public void onChange(boolean selfChange) { |
| final int mode = Settings.Global.getInt(mContext.getContentResolver(), |
| Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF); |
| setZenMode(mode); |
| |
| updateLockscreenNotificationSetting(); |
| } |
| }; |
| |
| private final ContentObserver mLockscreenSettingsObserver = new ContentObserver(mHandler) { |
| @Override |
| public void onChange(boolean selfChange) { |
| // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or |
| // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ... |
| mUsersAllowingPrivateNotifications.clear(); |
| mUsersAllowingNotifications.clear(); |
| // ... and refresh all the notifications |
| updateLockscreenNotificationSetting(); |
| updateNotifications(); |
| } |
| }; |
| |
| private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() { |
| |
| @Override |
| public boolean onClickHandler( |
| final View view, final PendingIntent pendingIntent, final Intent fillInIntent) { |
| wakeUpIfDozing(SystemClock.uptimeMillis(), view); |
| |
| |
| if (handleRemoteInput(view, pendingIntent, fillInIntent)) { |
| return true; |
| } |
| |
| if (DEBUG) { |
| Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent); |
| } |
| logActionClick(view); |
| // The intent we are sending is for the application, which |
| // won't have permission to immediately start an activity after |
| // the user switches to home. We know it is safe to do at this |
| // point, so make sure new activity switches are now allowed. |
| try { |
| ActivityManager.getService().resumeAppSwitches(); |
| } catch (RemoteException e) { |
| } |
| final boolean isActivity = pendingIntent.isActivity(); |
| if (isActivity) { |
| final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing(); |
| final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity( |
| mContext, pendingIntent.getIntent(), mCurrentUserId); |
| dismissKeyguardThenExecute(new OnDismissAction() { |
| @Override |
| public boolean onDismiss() { |
| try { |
| ActivityManager.getService().resumeAppSwitches(); |
| } catch (RemoteException e) { |
| } |
| |
| boolean handled = superOnClickHandler(view, pendingIntent, fillInIntent); |
| |
| // close the shade if it was open |
| if (handled) { |
| animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, |
| true /* force */); |
| visibilityChanged(false); |
| mAssistManager.hideAssist(); |
| } |
| |
| // Wait for activity start. |
| return handled; |
| } |
| }, afterKeyguardGone); |
| return true; |
| } else { |
| return superOnClickHandler(view, pendingIntent, fillInIntent); |
| } |
| } |
| |
| private void logActionClick(View view) { |
| ViewParent parent = view.getParent(); |
| String key = getNotificationKeyForParent(parent); |
| if (key == null) { |
| Log.w(TAG, "Couldn't determine notification for click."); |
| return; |
| } |
| int index = -1; |
| // If this is a default template, determine the index of the button. |
| if (view.getId() == com.android.internal.R.id.action0 && |
| parent != null && parent instanceof ViewGroup) { |
| ViewGroup actionGroup = (ViewGroup) parent; |
| index = actionGroup.indexOfChild(view); |
| } |
| try { |
| mBarService.onNotificationActionClick(key, index); |
| } catch (RemoteException e) { |
| // Ignore |
| } |
| } |
| |
| private String getNotificationKeyForParent(ViewParent parent) { |
| while (parent != null) { |
| if (parent instanceof ExpandableNotificationRow) { |
| return ((ExpandableNotificationRow) parent).getStatusBarNotification().getKey(); |
| } |
| parent = parent.getParent(); |
| } |
| return null; |
| } |
| |
| private boolean superOnClickHandler(View view, PendingIntent pendingIntent, |
| Intent fillInIntent) { |
| return super.onClickHandler(view, pendingIntent, fillInIntent, |
| StackId.FULLSCREEN_WORKSPACE_STACK_ID); |
| } |
| |
| private boolean handleRemoteInput(View view, PendingIntent pendingIntent, Intent fillInIntent) { |
| Object tag = view.getTag(com.android.internal.R.id.remote_input_tag); |
| RemoteInput[] inputs = null; |
| if (tag instanceof RemoteInput[]) { |
| inputs = (RemoteInput[]) tag; |
| } |
| |
| if (inputs == null) { |
| return false; |
| } |
| |
| RemoteInput input = null; |
| |
| for (RemoteInput i : inputs) { |
| if (i.getAllowFreeFormInput()) { |
| input = i; |
| } |
| } |
| |
| if (input == null) { |
| return false; |
| } |
| |
| ViewParent p = view.getParent(); |
| RemoteInputView riv = null; |
| while (p != null) { |
| if (p instanceof View) { |
| View pv = (View) p; |
| if (pv.isRootNamespace()) { |
| riv = findRemoteInputView(pv); |
| break; |
| } |
| } |
| p = p.getParent(); |
| } |
| ExpandableNotificationRow row = null; |
| while (p != null) { |
| if (p instanceof ExpandableNotificationRow) { |
| row = (ExpandableNotificationRow) p; |
| break; |
| } |
| p = p.getParent(); |
| } |
| |
| if (row == null) { |
| return false; |
| } |
| |
| row.setUserExpanded(true); |
| |
| if (!mAllowLockscreenRemoteInput) { |
| final int userId = pendingIntent.getCreatorUserHandle().getIdentifier(); |
| if (isLockscreenPublicMode(userId)) { |
| onLockedRemoteInput(row, view); |
| return true; |
| } |
| if (mUserManager.getUserInfo(userId).isManagedProfile() |
| && mKeyguardManager.isDeviceLocked(userId)) { |
| onLockedWorkRemoteInput(userId, row, view); |
| return true; |
| } |
| } |
| |
| if (riv == null) { |
| riv = findRemoteInputView(row.getPrivateLayout().getExpandedChild()); |
| if (riv == null) { |
| return false; |
| } |
| if (!row.getPrivateLayout().getExpandedChild().isShown()) { |
| onMakeExpandedVisibleForRemoteInput(row, view); |
| return true; |
| } |
| } |
| |
| int width = view.getWidth(); |
| if (view instanceof TextView) { |
| // Center the reveal on the text which might be off-center from the TextView |
| TextView tv = (TextView) view; |
| if (tv.getLayout() != null) { |
| int innerWidth = (int) tv.getLayout().getLineWidth(0); |
| innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight(); |
| width = Math.min(width, innerWidth); |
| } |
| } |
| int cx = view.getLeft() + width / 2; |
| int cy = view.getTop() + view.getHeight() / 2; |
| int w = riv.getWidth(); |
| int h = riv.getHeight(); |
| int r = Math.max( |
| Math.max(cx + cy, cx + (h - cy)), |
| Math.max((w - cx) + cy, (w - cx) + (h - cy))); |
| |
| riv.setRevealParameters(cx, cy, r); |
| riv.setPendingIntent(pendingIntent); |
| riv.setRemoteInput(inputs, input); |
| riv.focusAnimated(); |
| |
| return true; |
| } |
| |
| private RemoteInputView findRemoteInputView(View v) { |
| if (v == null) { |
| return null; |
| } |
| return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG); |
| } |
| }; |
| |
| private final BroadcastReceiver mBaseBroadcastReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| String action = intent.getAction(); |
| if (Intent.ACTION_USER_SWITCHED.equals(action)) { |
| mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); |
| updateCurrentProfilesCache(); |
| if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house"); |
| |
| updateLockscreenNotificationSetting(); |
| |
| userSwitched(mCurrentUserId); |
| } else if (Intent.ACTION_USER_ADDED.equals(action)) { |
| updateCurrentProfilesCache(); |
| } else if (Intent.ACTION_USER_PRESENT.equals(action)) { |
| List<ActivityManager.RecentTaskInfo> recentTask = null; |
| try { |
| recentTask = ActivityManager.getService().getRecentTasks(1, |
| ActivityManager.RECENT_WITH_EXCLUDED |
| | ActivityManager.RECENT_INCLUDE_PROFILES, |
| mCurrentUserId).getList(); |
| } catch (RemoteException e) { |
| // Abandon hope activity manager not running. |
| } |
| if (recentTask != null && recentTask.size() > 0) { |
| UserInfo user = mUserManager.getUserInfo(recentTask.get(0).userId); |
| if (user != null && user.isManagedProfile()) { |
| Toast toast = Toast.makeText(mContext, |
| R.string.managed_profile_foreground_toast, |
| Toast.LENGTH_SHORT); |
| TextView text = (TextView) toast.getView().findViewById( |
| android.R.id.message); |
| text.setCompoundDrawablesRelativeWithIntrinsicBounds( |
| R.drawable.stat_sys_managed_profile_status, 0, 0, 0); |
| int paddingPx = mContext.getResources().getDimensionPixelSize( |
| R.dimen.managed_profile_toast_padding); |
| text.setCompoundDrawablePadding(paddingPx); |
| toast.show(); |
| } |
| } |
| } else if (BANNER_ACTION_CANCEL.equals(action) || BANNER_ACTION_SETUP.equals(action)) { |
| NotificationManager noMan = (NotificationManager) |
| mContext.getSystemService(Context.NOTIFICATION_SERVICE); |
| noMan.cancel(SystemMessage.NOTE_HIDDEN_NOTIFICATIONS); |
| |
| Settings.Secure.putInt(mContext.getContentResolver(), |
| Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0); |
| if (BANNER_ACTION_SETUP.equals(action)) { |
| animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, |
| true /* force */); |
| mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_REDACTION) |
| .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) |
| |
| ); |
| } |
| } else if (NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION.equals(action)) { |
| final IntentSender intentSender = intent.getParcelableExtra(Intent.EXTRA_INTENT); |
| final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX); |
| if (intentSender != null) { |
| try { |
| mContext.startIntentSender(intentSender, null, 0, 0, 0); |
| } catch (IntentSender.SendIntentException e) { |
| /* ignore */ |
| } |
| } |
| if (notificationKey != null) { |
| try { |
| mBarService.onNotificationClick(notificationKey); |
| } catch (RemoteException e) { |
| /* ignore */ |
| } |
| } |
| } |
| } |
| }; |
| |
| private final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| final String action = intent.getAction(); |
| final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); |
| |
| if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action) && |
| isCurrentProfile(getSendingUserId())) { |
| mUsersAllowingPrivateNotifications.clear(); |
| updateLockscreenNotificationSetting(); |
| updateNotifications(); |
| } else if (Intent.ACTION_DEVICE_LOCKED_CHANGED.equals(action)) { |
| if (userId != mCurrentUserId && isCurrentProfile(userId)) { |
| onWorkChallengeChanged(); |
| } |
| } |
| } |
| }; |
| |
| private final NotificationListenerService mNotificationListener = |
| new NotificationListenerService() { |
| @Override |
| public void onListenerConnected() { |
| if (DEBUG) Log.d(TAG, "onListenerConnected"); |
| final StatusBarNotification[] notifications = getActiveNotifications(); |
| if (notifications == null) { |
| Log.w(TAG, "onListenerConnected unable to get active notifications."); |
| return; |
| } |
| final RankingMap currentRanking = getCurrentRanking(); |
| mHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| for (StatusBarNotification sbn : notifications) { |
| try { |
| addNotification(sbn, currentRanking, null /* oldEntry */); |
| } catch (InflationException e) { |
| handleInflationException(sbn, e); |
| } |
| } |
| } |
| }); |
| } |
| |
| @Override |
| public void onNotificationPosted(final StatusBarNotification sbn, |
| final RankingMap rankingMap) { |
| if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn); |
| if (sbn != null) { |
| mHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| processForRemoteInput(sbn.getNotification()); |
| String key = sbn.getKey(); |
| mKeysKeptForRemoteInput.remove(key); |
| boolean isUpdate = mNotificationData.get(key) != null; |
| // In case we don't allow child notifications, we ignore children of |
| // notifications that have a summary, since we're not going to show them |
| // anyway. This is true also when the summary is canceled, |
| // because children are automatically canceled by NoMan in that case. |
| if (!ENABLE_CHILD_NOTIFICATIONS |
| && mGroupManager.isChildInGroupWithSummary(sbn)) { |
| if (DEBUG) { |
| Log.d(TAG, "Ignoring group child due to existing summary: " + sbn); |
| } |
| |
| // Remove existing notification to avoid stale data. |
| if (isUpdate) { |
| removeNotification(key, rankingMap); |
| } else { |
| mNotificationData.updateRanking(rankingMap); |
| } |
| return; |
| } |
| try { |
| if (isUpdate) { |
| updateNotification(sbn, rankingMap); |
| } else { |
| addNotification(sbn, rankingMap, null /* oldEntry */); |
| } |
| } catch (InflationException e) { |
| handleInflationException(sbn, e); |
| } |
| } |
| }); |
| } |
| } |
| |
| @Override |
| public void onNotificationRemoved(StatusBarNotification sbn, |
| final RankingMap rankingMap) { |
| if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn); |
| if (sbn != null) { |
| final String key = sbn.getKey(); |
| mHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| removeNotification(key, rankingMap); |
| } |
| }); |
| } |
| } |
| |
| @Override |
| public void onNotificationRankingUpdate(final RankingMap rankingMap) { |
| if (DEBUG) Log.d(TAG, "onRankingUpdate"); |
| if (rankingMap != null) { |
| mHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| updateNotificationRanking(rankingMap); |
| } |
| }); |
| } } |
| |
| }; |
| |
| private void updateCurrentProfilesCache() { |
| synchronized (mCurrentProfiles) { |
| mCurrentProfiles.clear(); |
| if (mUserManager != null) { |
| for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) { |
| mCurrentProfiles.put(user.id, user); |
| } |
| } |
| } |
| } |
| |
| protected void notifyUserAboutHiddenNotifications() { |
| if (0 != Settings.Secure.getInt(mContext.getContentResolver(), |
| Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 1)) { |
| Log.d(TAG, "user hasn't seen notification about hidden notifications"); |
| if (!mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())) { |
| Log.d(TAG, "insecure lockscreen, skipping notification"); |
| Settings.Secure.putInt(mContext.getContentResolver(), |
| Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0); |
| return; |
| } |
| Log.d(TAG, "disabling lockecreen notifications and alerting the user"); |
| // disable lockscreen notifications until user acts on the banner. |
| Settings.Secure.putInt(mContext.getContentResolver(), |
| Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0); |
| Settings.Secure.putInt(mContext.getContentResolver(), |
| Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0); |
| |
| final String packageName = mContext.getPackageName(); |
| PendingIntent cancelIntent = PendingIntent.getBroadcast(mContext, 0, |
| new Intent(BANNER_ACTION_CANCEL).setPackage(packageName), |
| PendingIntent.FLAG_CANCEL_CURRENT); |
| PendingIntent setupIntent = PendingIntent.getBroadcast(mContext, 0, |
| new Intent(BANNER_ACTION_SETUP).setPackage(packageName), |
| PendingIntent.FLAG_CANCEL_CURRENT); |
| |
| final int colorRes = com.android.internal.R.color.system_notification_accent_color; |
| Notification.Builder note = |
| new Notification.Builder(mContext, NotificationChannels.GENERAL) |
| .setSmallIcon(R.drawable.ic_android) |
| .setContentTitle(mContext.getString( |
| R.string.hidden_notifications_title)) |
| .setContentText(mContext.getString(R.string.hidden_notifications_text)) |
| .setOngoing(true) |
| .setColor(mContext.getColor(colorRes)) |
| .setContentIntent(setupIntent) |
| .addAction(R.drawable.ic_close, |
| mContext.getString(R.string.hidden_notifications_cancel), |
| cancelIntent) |
| .addAction(R.drawable.ic_settings, |
| mContext.getString(R.string.hidden_notifications_setup), |
| setupIntent); |
| overrideNotificationAppName(mContext, note); |
| |
| NotificationManager noMan = |
| (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); |
| noMan.notify(SystemMessage.NOTE_HIDDEN_NOTIFICATIONS, note.build()); |
| } |
| } |
| |
| @Override // NotificationData.Environment |
| public boolean isNotificationForCurrentProfiles(StatusBarNotification n) { |
| final int thisUserId = mCurrentUserId; |
| final int notificationUserId = n.getUserId(); |
| if (DEBUG && MULTIUSER_DEBUG) { |
| Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d", |
| n, thisUserId, notificationUserId)); |
| } |
| return isCurrentProfile(notificationUserId); |
| } |
| |
| protected void setNotificationShown(StatusBarNotification n) { |
| setNotificationsShown(new String[]{n.getKey()}); |
| } |
| |
| protected void setNotificationsShown(String[] keys) { |
| try { |
| mNotificationListener.setNotificationsShown(keys); |
| } catch (RuntimeException e) { |
| Log.d(TAG, "failed setNotificationsShown: ", e); |
| } |
| } |
| |
| protected boolean isCurrentProfile(int userId) { |
| synchronized (mCurrentProfiles) { |
| return userId == UserHandle.USER_ALL || mCurrentProfiles.get(userId) != null; |
| } |
| } |
| |
| @Override |
| public NotificationGroupManager getGroupManager() { |
| return mGroupManager; |
| } |
| |
| public boolean isMediaNotification(NotificationData.Entry entry) { |
| // TODO: confirm that there's a valid media key |
| return entry.getExpandedContentView() != null && |
| entry.getExpandedContentView() |
| .findViewById(com.android.internal.R.id.media_actions) != null; |
| } |
| |
| // The button in the guts that links to the system notification settings for that app |
| private void startAppNotificationSettingsActivity(String packageName, final int appUid, |
| final NotificationChannel channel) { |
| final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS); |
| intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName); |
| intent.putExtra(Settings.EXTRA_APP_UID, appUid); |
| if (channel != null) { |
| intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId()); |
| } |
| startNotificationGutsIntent(intent, appUid); |
| } |
| |
| private void startNotificationGutsIntent(final Intent intent, final int appUid) { |
| dismissKeyguardThenExecute(new OnDismissAction() { |
| @Override |
| public boolean onDismiss() { |
| AsyncTask.execute(new Runnable() { |
| @Override |
| public void run() { |
| TaskStackBuilder.create(mContext) |
| .addNextIntentWithParentStack(intent) |
| .startActivities(getActivityOptions(), |
| new UserHandle(UserHandle.getUserId(appUid))); |
| } |
| }); |
| animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */); |
| return true; |
| } |
| }, false /* afterKeyguardGone */); |
| } |
| |
| public void setNotificationSnoozed(StatusBarNotification sbn, SnoozeOption snoozeOption) { |
| if (snoozeOption.criterion != null) { |
| mNotificationListener.snoozeNotification(sbn.getKey(), snoozeOption.criterion.getId()); |
| } else { |
| mNotificationListener.snoozeNotification(sbn.getKey(), |
| snoozeOption.snoozeForMinutes * 60 * 1000); |
| } |
| } |
| |
| private void bindGuts(final ExpandableNotificationRow row, MenuItem item) { |
| row.inflateGuts(); |
| row.setGutsView(item); |
| final StatusBarNotification sbn = row.getStatusBarNotification(); |
| row.setTag(sbn.getPackageName()); |
| final NotificationGuts guts = row.getGuts(); |
| guts.setClosedListener((NotificationGuts g) -> { |
| if (!g.willBeRemoved() && !row.isRemoved()) { |
| mStackScroller.onHeightChanged(row, !isPanelFullyCollapsed() /* needsAnimation */); |
| } |
| mNotificationGutsExposed = null; |
| mGutsMenuItem = null; |
| }); |
| |
| View gutsView = item.getGutsView(); |
| if (gutsView instanceof NotificationSnooze) { |
| NotificationSnooze snoozeGuts = (NotificationSnooze) gutsView; |
| snoozeGuts.setSnoozeListener(mStackScroller.getSwipeActionHelper()); |
| snoozeGuts.setStatusBarNotification(sbn); |
| snoozeGuts.setSnoozeOptions(row.getEntry().snoozeCriteria); |
| guts.setHeightChangedListener((NotificationGuts g) -> { |
| mStackScroller.onHeightChanged(row, row.isShown() /* needsAnimation */); |
| }); |
| } |
| |
| if (gutsView instanceof NotificationInfo) { |
| final UserHandle userHandle = sbn.getUser(); |
| PackageManager pmUser = getPackageManagerForUser(mContext, |
| userHandle.getIdentifier()); |
| final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface( |
| ServiceManager.getService(Context.NOTIFICATION_SERVICE)); |
| final String pkg = sbn.getPackageName(); |
| NotificationInfo info = (NotificationInfo) gutsView; |
| // Settings link is only valid for notifications that specify a user, unless this is the |
| // system user. |
| NotificationInfo.OnSettingsClickListener onSettingsClick = null; |
| if (!userHandle.equals(UserHandle.ALL) || mCurrentUserId == UserHandle.USER_SYSTEM) { |
| onSettingsClick = (View v, NotificationChannel channel, int appUid) -> { |
| mMetricsLogger.action(MetricsEvent.ACTION_NOTE_INFO); |
| guts.resetFalsingCheck(); |
| startAppNotificationSettingsActivity(pkg, appUid, channel); |
| }; |
| } |
| final NotificationInfo.OnAppSettingsClickListener onAppSettingsClick = (View v, |
| Intent intent) -> { |
| mMetricsLogger.action(MetricsEvent.ACTION_APP_NOTE_SETTINGS); |
| guts.resetFalsingCheck(); |
| startNotificationGutsIntent(intent, sbn.getUid()); |
| }; |
| final View.OnClickListener onDoneClick = (View v) -> { |
| saveAndCloseNotificationMenu(info, row, guts, v); |
| }; |
| final NotificationInfo.CheckSaveListener checkSaveListener = |
| (Runnable saveImportance) -> { |
| // If the user has security enabled, show challenge if the setting is changed. |
| if (isLockscreenPublicMode(userHandle.getIdentifier()) |
| && (mState == StatusBarState.KEYGUARD |
| || mState == StatusBarState.SHADE_LOCKED)) { |
| onLockedNotificationImportanceChange(() -> { |
| saveImportance.run(); |
| return true; |
| }); |
| } else { |
| saveImportance.run(); |
| } |
| }; |
| |
| ArraySet<NotificationChannel> channels = new ArraySet<NotificationChannel>(); |
| channels.add(row.getEntry().channel); |
| if (row.isSummaryWithChildren()) { |
| // If this is a summary, then add in the children notification channels for the |
| // same user and pkg. |
| final List<ExpandableNotificationRow> childrenRows = row.getNotificationChildren(); |
| final int numChildren = childrenRows.size(); |
| for (int i = 0; i < numChildren; i++) { |
| final ExpandableNotificationRow childRow = childrenRows.get(i); |
| final NotificationChannel childChannel = childRow.getEntry().channel; |
| final StatusBarNotification childSbn = childRow.getStatusBarNotification(); |
| if (childSbn.getUser().equals(userHandle) && |
| childSbn.getPackageName().equals(pkg)) { |
| channels.add(childChannel); |
| } |
| } |
| } |
| try { |
| info.bindNotification(pmUser, iNotificationManager, pkg, new ArrayList(channels), |
| row.getEntry().channel.getImportance(), sbn, onSettingsClick, |
| onAppSettingsClick, onDoneClick, checkSaveListener, |
| mNonBlockablePkgs); |
| } catch (RemoteException e) { |
| Log.e(TAG, e.toString()); |
| } |
| } |
| } |
| |
| private void saveAndCloseNotificationMenu(NotificationInfo info, |
| ExpandableNotificationRow row, NotificationGuts guts, View done) { |
| guts.resetFalsingCheck(); |
| int[] rowLocation = new int[2]; |
| int[] doneLocation = new int[2]; |
| row.getLocationOnScreen(rowLocation); |
| done.getLocationOnScreen(doneLocation); |
| |
| final int centerX = done.getWidth() / 2; |
| final int centerY = done.getHeight() / 2; |
| final int x = doneLocation[0] - rowLocation[0] + centerX; |
| final int y = doneLocation[1] - rowLocation[1] + centerY; |
| dismissPopups(x, y); |
| } |
| |
| protected SwipeHelper.LongPressListener getNotificationLongClicker() { |
| return new SwipeHelper.LongPressListener() { |
| @Override |
| public boolean onLongPress(View v, final int x, final int y, |
| MenuItem item) { |
| if (!(v instanceof ExpandableNotificationRow)) { |
| return false; |
| } |
| if (v.getWindowToken() == null) { |
| Log.e(TAG, "Trying to show notification guts, but not attached to window"); |
| return false; |
| } |
| |
| final ExpandableNotificationRow row = (ExpandableNotificationRow) v; |
| if (row.isDark()) { |
| return false; |
| } |
| bindGuts(row, item); |
| NotificationGuts guts = row.getGuts(); |
| |
| // Assume we are a status_bar_notification_row |
| if (guts == null) { |
| // This view has no guts. Examples are the more card or the dismiss all view |
| return false; |
| } |
| |
| // Already showing? |
| if (guts.getVisibility() == View.VISIBLE) { |
| dismissPopups(x, y); |
| return false; |
| } |
| |
| mMetricsLogger.action(MetricsEvent.ACTION_NOTE_CONTROLS); |
| |
| // ensure that it's laid but not visible until actually laid out |
| guts.setVisibility(View.INVISIBLE); |
| // Post to ensure the the guts are properly laid out. |
| guts.post(new Runnable() { |
| @Override |
| public void run() { |
| if (row.getWindowToken() == null) { |
| Log.e(TAG, "Trying to show notification guts, but not attached to " |
| + "window"); |
| return; |
| } |
| dismissPopups(-1 /* x */, -1 /* y */, false /* resetMenu */, |
| false /* animate */); |
| guts.setVisibility(View.VISIBLE); |
| final double horz = Math.max(guts.getWidth() - x, x); |
| final double vert = Math.max(guts.getHeight() - y, y); |
| final float r = (float) Math.hypot(horz, vert); |
| final Animator a |
| = ViewAnimationUtils.createCircularReveal(guts, x, y, 0, r); |
| a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); |
| a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); |
| a.addListener(new AnimatorListenerAdapter() { |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| super.onAnimationEnd(animation); |
| // Move the notification view back over the menu |
| row.resetTranslation(); |
| } |
| }); |
| a.start(); |
| final boolean needsFalsingProtection = |
| (mState == StatusBarState.KEYGUARD && |
| !mAccessibilityManager.isTouchExplorationEnabled()); |
| guts.setExposed(true /* exposed */, needsFalsingProtection); |
| row.closeRemoteInput(); |
| mStackScroller.onHeightChanged(row, true /* needsAnimation */); |
| mNotificationGutsExposed = guts; |
| mGutsMenuItem = item; |
| } |
| }); |
| return true; |
| } |
| }; |
| } |
| |
| /** |
| * Returns the exposed NotificationGuts or null if none are exposed. |
| */ |
| public NotificationGuts getExposedGuts() { |
| return mNotificationGutsExposed; |
| } |
| |
| public void dismissPopups() { |
| dismissPopups(-1 /* x */, -1 /* y */, true /* resetMenu */, false /* animate */); |
| } |
| |
| private void dismissPopups(int x, int y) { |
| dismissPopups(x, y, true /* resetMenu */, false /* animate */); |
| } |
| |
| public void dismissPopups(int x, int y, boolean resetMenu, boolean animate) { |
| if (mNotificationGutsExposed != null) { |
| mNotificationGutsExposed.closeControls(x, y, true /* save */); |
| } |
| if (resetMenu) { |
| mStackScroller.resetExposedMenuView(animate, true /* force */); |
| } |
| } |
| |
| @Override |
| public void toggleSplitScreen() { |
| toggleSplitScreenMode(-1 /* metricsDockAction */, -1 /* metricsUndockAction */); |
| } |
| |
| @Override |
| public void preloadRecentApps() { |
| int msg = MSG_PRELOAD_RECENT_APPS; |
| mHandler.removeMessages(msg); |
| mHandler.sendEmptyMessage(msg); |
| } |
| |
| @Override |
| public void cancelPreloadRecentApps() { |
| int msg = MSG_CANCEL_PRELOAD_RECENT_APPS; |
| mHandler.removeMessages(msg); |
| mHandler.sendEmptyMessage(msg); |
| } |
| |
| @Override |
| public void dismissKeyboardShortcutsMenu() { |
| int msg = MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU; |
| mHandler.removeMessages(msg); |
| mHandler.sendEmptyMessage(msg); |
| } |
| |
| @Override |
| public void toggleKeyboardShortcutsMenu(int deviceId) { |
| int msg = MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU; |
| mHandler.removeMessages(msg); |
| mHandler.obtainMessage(msg, deviceId, 0).sendToTarget(); |
| } |
| |
| protected void sendCloseSystemWindows(String reason) { |
| try { |
| ActivityManager.getService().closeSystemDialogs(reason); |
| } catch (RemoteException e) { |
| } |
| } |
| |
| protected void toggleKeyboardShortcuts(int deviceId) { |
| KeyboardShortcuts.toggle(mContext, deviceId); |
| } |
| |
| protected void dismissKeyboardShortcuts() { |
| KeyboardShortcuts.dismiss(); |
| } |
| |
| /** |
| * Save the current "public" (locked and secure) state of the lockscreen. |
| */ |
| public void setLockscreenPublicMode(boolean publicMode, int userId) { |
| mLockscreenPublicMode.put(userId, publicMode); |
| } |
| |
| public boolean isLockscreenPublicMode(int userId) { |
| return mLockscreenPublicMode.get(userId, false); |
| } |
| |
| /** |
| * Has the given user chosen to allow notifications to be shown even when the lockscreen is in |
| * "public" (secure & locked) mode? |
| */ |
| public boolean userAllowsNotificationsInPublic(int userHandle) { |
| if (userHandle == UserHandle.USER_ALL) { |
| return true; |
| } |
| |
| if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) { |
| final boolean allowed = 0 != Settings.Secure.getIntForUser( |
| mContext.getContentResolver(), |
| Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle); |
| mUsersAllowingNotifications.append(userHandle, allowed); |
| return allowed; |
| } |
| |
| return mUsersAllowingNotifications.get(userHandle); |
| } |
| |
| /** |
| * Has the given user chosen to allow their private (full) notifications to be shown even |
| * when the lockscreen is in "public" (secure & locked) mode? |
| */ |
| public boolean userAllowsPrivateNotificationsInPublic(int userHandle) { |
| if (userHandle == UserHandle.USER_ALL) { |
| return true; |
| } |
| |
| if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { |
| final boolean allowedByUser = 0 != Settings.Secure.getIntForUser( |
| mContext.getContentResolver(), |
| Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle); |
| final boolean allowedByDpm = adminAllowsUnredactedNotifications(userHandle); |
| final boolean allowed = allowedByUser && allowedByDpm; |
| mUsersAllowingPrivateNotifications.append(userHandle, allowed); |
| return allowed; |
| } |
| |
| return mUsersAllowingPrivateNotifications.get(userHandle); |
| } |
| |
| private boolean adminAllowsUnredactedNotifications(int userHandle) { |
| if (userHandle == UserHandle.USER_ALL) { |
| return true; |
| } |
| final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(null /* admin */, |
| userHandle); |
| return (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) == 0; |
| } |
| |
| /** |
| * Returns true if we're on a secure lockscreen and the user wants to hide notification data. |
| * If so, notifications should be hidden. |
| */ |
| @Override // NotificationData.Environment |
| public boolean shouldHideNotifications(int userId) { |
| return isLockscreenPublicMode(userId) && !userAllowsNotificationsInPublic(userId) |
| || (userId != mCurrentUserId && shouldHideNotifications(mCurrentUserId)); |
| } |
| |
| /** |
| * Returns true if we're on a secure lockscreen and the user wants to hide notifications via |
| * package-specific override. |
| */ |
| @Override // NotificationDate.Environment |
| public boolean shouldHideNotifications(String key) { |
| return isLockscreenPublicMode(mCurrentUserId) |
| && mNotificationData.getVisibilityOverride(key) == Notification.VISIBILITY_SECRET; |
| } |
| |
| /** |
| * Returns true if we're on a secure lockscreen. |
| */ |
| @Override // NotificationData.Environment |
| public boolean isSecurelyLocked(int userId) { |
| return isLockscreenPublicMode(userId); |
| } |
| |
| public void onNotificationClear(StatusBarNotification notification) { |
| try { |
| mBarService.onNotificationClear( |
| notification.getPackageName(), |
| notification.getTag(), |
| notification.getId(), |
| notification.getUserId()); |
| } catch (android.os.RemoteException ex) { |
| // oh well |
| } |
| } |
| |
| /** |
| * Called when the notification panel layouts |
| */ |
| public void onPanelLaidOut() { |
| if (mState == StatusBarState.KEYGUARD) { |
| // Since the number of notifications is determined based on the height of the view, we |
| // need to update them. |
| int maxBefore = getMaxKeyguardNotifications(false /* recompute */); |
| int maxNotifications = getMaxKeyguardNotifications(true /* recompute */); |
| if (maxBefore != maxNotifications) { |
| updateRowStates(); |
| } |
| } |
| } |
| |
| protected void inflateViews(Entry entry, ViewGroup parent) throws |
| InflationException { |
| PackageManager pmUser = getPackageManagerForUser(mContext, |
| entry.notification.getUser().getIdentifier()); |
| |
| final StatusBarNotification sbn = entry.notification; |
| ExpandableNotificationRow row; |
| if (entry.row != null) { |
| row = entry.row; |
| entry.reset(); |
| } else { |
| // create the row view |
| LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( |
| Context.LAYOUT_INFLATER_SERVICE); |
| row = (ExpandableNotificationRow) inflater.inflate(R.layout.status_bar_notification_row, |
| parent, false); |
| row.setExpansionLogger(this, entry.notification.getKey()); |
| row.setGroupManager(mGroupManager); |
| row.setHeadsUpManager(mHeadsUpManager); |
| row.setRemoteInputController(mRemoteInputController); |
| row.setOnExpandClickListener(this); |
| row.setRemoteViewClickHandler(mOnClickHandler); |
| row.setInflateExceptionHandler(mInflationExceptionHandler); |
| |
| // Get the app name. |
| // Note that Notification.Builder#bindHeaderAppName has similar logic |
| // but since this field is used in the guts, it must be accurate. |
| // Therefore we will only show the application label, or, failing that, the |
| // package name. No substitutions. |
| final String pkg = sbn.getPackageName(); |
| String appname = pkg; |
| try { |
| final ApplicationInfo info = pmUser.getApplicationInfo(pkg, |
| PackageManager.MATCH_UNINSTALLED_PACKAGES |
| | PackageManager.MATCH_DISABLED_COMPONENTS); |
| if (info != null) { |
| appname = String.valueOf(pmUser.getApplicationLabel(info)); |
| } |
| } catch (NameNotFoundException e) { |
| // Do nothing |
| } |
| row.setAppName(appname); |
| row.setOnDismissRunnable(() -> |
| performRemoveNotification(row.getStatusBarNotification())); |
| row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); |
| if (ENABLE_REMOTE_INPUT) { |
| row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); |
| } |
| } |
| |
| row.setNeedsRedaction(needsRedaction(entry)); |
| boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey()); |
| row.setIsLowPriority(isLowPriority); |
| // bind the click event to the content area |
| mNotificationClicker.register(row, sbn); |
| |
| // Extract target SDK version. |
| try { |
| ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0); |
| entry.targetSdk = info.targetSdkVersion; |
| } catch (NameNotFoundException ex) { |
| Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex); |
| } |
| row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD |
| && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); |
| entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); |
| entry.autoRedacted = entry.notification.getNotification().publicVersion == null; |
| |
| entry.row = row; |
| entry.row.setOnActivatedListener(this); |
| |
| boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn, |
| mNotificationData.getImportance(sbn.getKey())); |
| boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight && mPanelExpanded; |
| row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight); |
| row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp); |
| row.updateNotification(entry); |
| } |
| |
| /** |
| * Adds RemoteInput actions from the WearableExtender; to be removed once more apps support this |
| * via first-class API. |
| * |
| * TODO: Remove once enough apps specify remote inputs on their own. |
| */ |
| private void processForRemoteInput(Notification n) { |
| if (!ENABLE_REMOTE_INPUT) return; |
| |
| if (n.extras != null && n.extras.containsKey("android.wearable.EXTENSIONS") && |
| (n.actions == null || n.actions.length == 0)) { |
| Notification.Action viableAction = null; |
| Notification.WearableExtender we = new Notification.WearableExtender(n); |
| |
| List<Notification.Action> actions = we.getActions(); |
| final int numActions = actions.size(); |
| |
| for (int i = 0; i < numActions; i++) { |
| Notification.Action action = actions.get(i); |
| if (action == null) { |
| continue; |
| } |
| RemoteInput[] remoteInputs = action.getRemoteInputs(); |
| if (remoteInputs == null) { |
| continue; |
| } |
| for (RemoteInput ri : remoteInputs) { |
| if (ri.getAllowFreeFormInput()) { |
| viableAction = action; |
| break; |
| } |
| } |
| if (viableAction != null) { |
| break; |
| } |
| } |
| |
| if (viableAction != null) { |
| Notification.Builder rebuilder = Notification.Builder.recoverBuilder(mContext, n); |
| rebuilder.setActions(viableAction); |
| rebuilder.build(); // will rewrite n |
| } |
| } |
| } |
| |
| public void startPendingIntentDismissingKeyguard(final PendingIntent intent) { |
| if (!isDeviceProvisioned()) return; |
| |
| final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing(); |
| final boolean afterKeyguardGone = intent.isActivity() |
| && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(), |
| mCurrentUserId); |
| dismissKeyguardThenExecute(new OnDismissAction() { |
| @Override |
| public boolean onDismiss() { |
| new Thread() { |
| @Override |
| public void run() { |
| try { |
| // The intent we are sending is for the application, which |
| // won't have permission to immediately start an activity after |
| // the user switches to home. We know it is safe to do at this |
| // point, so make sure new activity switches are now allowed. |
| ActivityManager.getService().resumeAppSwitches(); |
| } catch (RemoteException e) { |
| } |
| try { |
| intent.send(null, 0, null, null, null, null, getActivityOptions()); |
| } catch (PendingIntent.CanceledException e) { |
| // the stack trace isn't very helpful here. |
| // Just log the exception message. |
| Log.w(TAG, "Sending intent failed: " + e); |
| |
| // TODO: Dismiss Keyguard. |
| } |
| if (intent.isActivity()) { |
| mAssistManager.hideAssist(); |
| } |
| } |
| }.start(); |
| |
| // close the shade if it was open |
| animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, |
| true /* force */, true /* delayed */); |
| visibilityChanged(false); |
| |
| return true; |
| } |
| }, afterKeyguardGone); |
| } |
| |
| |
| private final class NotificationClicker implements View.OnClickListener { |
| |
| @Override |
| public void onClick(final View v) { |
| if (!(v instanceof ExpandableNotificationRow)) { |
| Log.e(TAG, "NotificationClicker called on a view that is not a notification row."); |
| return; |
| } |
| |
| wakeUpIfDozing(SystemClock.uptimeMillis(), v); |
| |
| final ExpandableNotificationRow row = (ExpandableNotificationRow) v; |
| final StatusBarNotification sbn = row.getStatusBarNotification(); |
| if (sbn == null) { |
| Log.e(TAG, "NotificationClicker called on an unclickable notification,"); |
| return; |
| } |
| |
| // Check if the notification is displaying the menu, if so slide notification back |
| if (row.getProvider() != null && row.getProvider().isMenuVisible()) { |
| row.animateTranslateNotification(0); |
| return; |
| } |
| |
| Notification notification = sbn.getNotification(); |
| final PendingIntent intent = notification.contentIntent != null |
| ? notification.contentIntent |
| : notification.fullScreenIntent; |
| final String notificationKey = sbn.getKey(); |
| |
| // Mark notification for one frame. |
| row.setJustClicked(true); |
| DejankUtils.postAfterTraversal(new Runnable() { |
| @Override |
| public void run() { |
| row.setJustClicked(false); |
| } |
| }); |
| |
| final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing(); |
| final boolean afterKeyguardGone = intent.isActivity() |
| && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(), |
| mCurrentUserId); |
| dismissKeyguardThenExecute(new OnDismissAction() { |
| @Override |
| public boolean onDismiss() { |
| if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) { |
| // Release the HUN notification to the shade. |
| |
| if (isPanelFullyCollapsed()) { |
| HeadsUpManager.setIsClickedNotification(row, true); |
| } |
| // |
| // In most cases, when FLAG_AUTO_CANCEL is set, the notification will |
| // become canceled shortly by NoMan, but we can't assume that. |
| mHeadsUpManager.releaseImmediately(notificationKey); |
| } |
| StatusBarNotification parentToCancel = null; |
| if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) { |
| StatusBarNotification summarySbn = mGroupManager.getLogicalGroupSummary(sbn) |
| .getStatusBarNotification(); |
| if (shouldAutoCancel(summarySbn)) { |
| parentToCancel = summarySbn; |
| } |
| } |
| final StatusBarNotification parentToCancelFinal = parentToCancel; |
| new Thread() { |
| @Override |
| public void run() { |
| try { |
| // The intent we are sending is for the application, which |
| // won't have permission to immediately start an activity after |
| // the user switches to home. We know it is safe to do at this |
| // point, so make sure new activity switches are now allowed. |
| ActivityManager.getService().resumeAppSwitches(); |
| } catch (RemoteException e) { |
| } |
| if (intent != null) { |
| // If we are launching a work activity and require to launch |
| // separate work challenge, we defer the activity action and cancel |
| // notification until work challenge is unlocked. |
| if (intent.isActivity()) { |
| final int userId = intent.getCreatorUserHandle() |
| .getIdentifier(); |
| if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId) |
| && mKeyguardManager.isDeviceLocked(userId)) { |
| // TODO(b/28935539): should allow certain activities to |
| // bypass work challenge |
| if (startWorkChallengeIfNecessary(userId, |
| intent.getIntentSender(), notificationKey)) { |
| // Show work challenge, do not run PendingIntent and |
| // remove notification |
| return; |
| } |
| } |
| } |
| try { |
| intent.send(null, 0, null, null, null, null, |
| getActivityOptions()); |
| } catch (PendingIntent.CanceledException e) { |
| // the stack trace isn't very helpful here. |
| // Just log the exception message. |
| Log.w(TAG, "Sending contentIntent failed: " + e); |
| |
| // TODO: Dismiss Keyguard. |
| } |
| if (intent.isActivity()) { |
| mAssistManager.hideAssist(); |
| } |
| } |
| |
| try { |
| mBarService.onNotificationClick(notificationKey); |
| } catch (RemoteException ex) { |
| // system process is dead if we're here. |
| } |
| if (parentToCancelFinal != null) { |
| // We have to post it to the UI thread for synchronization |
| mHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| Runnable removeRunnable = new Runnable() { |
| @Override |
| public void run() { |
| performRemoveNotification(parentToCancelFinal); |
| } |
| }; |
| if (isCollapsing()) { |
| // To avoid lags we're only performing the remove |
| // after the shade was collapsed |
| addPostCollapseAction(removeRunnable); |
| } else { |
| removeRunnable.run(); |
| } |
| } |
| }); |
| } |
| } |
| }.start(); |
| |
| // close the shade if it was open |
| animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, |
| true /* force */, true /* delayed */); |
| visibilityChanged(false); |
| |
| return true; |
| } |
| }, afterKeyguardGone); |
| } |
| |
| private boolean shouldAutoCancel(StatusBarNotification sbn) { |
| int flags = sbn.getNotification().flags; |
| if ((flags & Notification.FLAG_AUTO_CANCEL) != Notification.FLAG_AUTO_CANCEL) { |
| return false; |
| } |
| if ((flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) { |
| return false; |
| } |
| return true; |
| } |
| |
| public void register(ExpandableNotificationRow row, StatusBarNotification sbn) { |
| Notification notification = sbn.getNotification(); |
| if (notification.contentIntent != null || notification.fullScreenIntent != null) { |
| row.setOnClickListener(this); |
| } else { |
| row.setOnClickListener(null); |
| } |
| } |
| } |
| |
| protected Bundle getActivityOptions() { |
| // Anything launched from the notification shade should always go into the |
| // fullscreen stack. |
| ActivityOptions options = ActivityOptions.makeBasic(); |
| options.setLaunchStackId(StackId.FULLSCREEN_WORKSPACE_STACK_ID); |
| return options.toBundle(); |
| } |
| |
| protected void visibilityChanged(boolean visible) { |
| if (mVisible != visible) { |
| mVisible = visible; |
| if (!visible) { |
| dismissPopups(); |
| } |
| } |
| updateVisibleToUser(); |
| } |
| |
| protected void updateVisibleToUser() { |
| boolean oldVisibleToUser = mVisibleToUser; |
| mVisibleToUser = mVisible && mDeviceInteractive; |
| |
| if (oldVisibleToUser != mVisibleToUser) { |
| handleVisibleToUserChanged(mVisibleToUser); |
| } |
| } |
| |
| /** |
| * Clear Buzz/Beep/Blink. |
| */ |
| public void clearNotificationEffects() { |
| try { |
| mBarService.clearNotificationEffects(); |
| } catch (RemoteException e) { |
| // Won't fail unless the world has ended. |
| } |
| } |
| |
| /** |
| * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService |
| * about the failure. |
| * |
| * WARNING: this will call back into us. Don't hold any locks. |
| */ |
| void handleNotificationError(StatusBarNotification n, String message) { |
| removeNotification(n.getKey(), null); |
| try { |
| mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(), |
| n.getInitialPid(), message, n.getUserId()); |
| } catch (RemoteException ex) { |
| // The end is nigh. |
| } |
| } |
| |
| protected StatusBarNotification removeNotificationViews(String key, RankingMap ranking) { |
| NotificationData.Entry entry = mNotificationData.remove(key, ranking); |
| if (entry == null) { |
| Log.w(TAG, "removeNotification for unknown key: " + key); |
| return null; |
| } |
| updateNotifications(); |
| Dependency.get(LeakDetector.class).trackGarbage(entry); |
| return entry.notification; |
| } |
| |
| protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn) |
| throws InflationException { |
| if (DEBUG) { |
| Log.d(TAG, "createNotificationViews(notification=" + sbn); |
| } |
| NotificationData.Entry entry = new NotificationData.Entry(sbn); |
| Dependency.get(LeakDetector.class).trackInstance(entry); |
| entry.createIcons(mContext, sbn); |
| // Construct the expanded view. |
| inflateViews(entry, mStackScroller); |
| return entry; |
| } |
| |
| protected void addNotificationViews(Entry entry, RankingMap ranking) { |
| if (entry == null) { |
| return; |
| } |
| // Add the expanded view and icon. |
| mNotificationData.add(entry, ranking); |
| updateNotifications(); |
| } |
| |
| /** |
| * Updates expanded, dimmed and locked states of notification rows. |
| */ |
| protected void updateRowStates() { |
| final int N = mStackScroller.getChildCount(); |
| |
| int visibleNotifications = 0; |
| boolean onKeyguard = mState == StatusBarState.KEYGUARD; |
| int maxNotifications = -1; |
| if (onKeyguard) { |
| maxNotifications = getMaxKeyguardNotifications(true /* recompute */); |
| } |
| mStackScroller.setMaxDisplayedNotifications(maxNotifications); |
| Stack<ExpandableNotificationRow> stack = new Stack<>(); |
| for (int i = N - 1; i >= 0; i--) { |
| View child = mStackScroller.getChildAt(i); |
| if (!(child instanceof ExpandableNotificationRow)) { |
| continue; |
| } |
| stack.push((ExpandableNotificationRow) child); |
| } |
| while(!stack.isEmpty()) { |
| ExpandableNotificationRow row = stack.pop(); |
| NotificationData.Entry entry = row.getEntry(); |
| boolean childNotification = mGroupManager.isChildInGroupWithSummary(entry.notification); |
| if (onKeyguard) { |
| row.setOnKeyguard(true); |
| } else { |
| row.setOnKeyguard(false); |
| row.setSystemExpanded(visibleNotifications == 0 && !childNotification); |
| } |
| entry.row.setShowAmbient(isDozing()); |
| int userId = entry.notification.getUserId(); |
| boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup( |
| entry.notification) && !entry.row.isRemoved(); |
| boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification); |
| if (suppressedSummary |
| || (isLockscreenPublicMode(userId) && !mShowLockscreenNotifications) |
| || (onKeyguard && !showOnKeyguard)) { |
| entry.row.setVisibility(View.GONE); |
| } else { |
| boolean wasGone = entry.row.getVisibility() == View.GONE; |
| if (wasGone) { |
| entry.row.setVisibility(View.VISIBLE); |
| } |
| if (!childNotification && !entry.row.isRemoved()) { |
| if (wasGone) { |
| // notify the scroller of a child addition |
| mStackScroller.generateAddAnimation(entry.row, |
| !showOnKeyguard /* fromMoreCard */); |
| } |
| visibleNotifications++; |
| } |
| } |
| if (row.isSummaryWithChildren()) { |
| List<ExpandableNotificationRow> notificationChildren = |
| row.getNotificationChildren(); |
| int size = notificationChildren.size(); |
| for (int i = size - 1; i >= 0; i--) { |
| stack.push(notificationChildren.get(i)); |
| } |
| } |
| } |
| mNotificationPanel.setNoVisibleNotifications(visibleNotifications == 0); |
| |
| mStackScroller.changeViewPosition(mDismissView, mStackScroller.getChildCount() - 1); |
| mStackScroller.changeViewPosition(mEmptyShadeView, mStackScroller.getChildCount() - 2); |
| mStackScroller.changeViewPosition(mNotificationShelf, mStackScroller.getChildCount() - 3); |
| } |
| |
| public boolean shouldShowOnKeyguard(StatusBarNotification sbn) { |
| return mShowLockscreenNotifications && !mNotificationData.isAmbient(sbn.getKey()); |
| } |
| |
| // extended in StatusBar |
| protected void setShowLockscreenNotifications(boolean show) { |
| mShowLockscreenNotifications = show; |
| } |
| |
| protected void setLockScreenAllowRemoteInput(boolean allowLockscreenRemoteInput) { |
| mAllowLockscreenRemoteInput = allowLockscreenRemoteInput; |
| } |
| |
| private void updateLockscreenNotificationSetting() { |
| final boolean show = Settings.Secure.getIntForUser(mContext.getContentResolver(), |
| Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, |
| 1, |
| mCurrentUserId) != 0; |
| final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures( |
| null /* admin */, mCurrentUserId); |
| final boolean allowedByDpm = (dpmFlags |
| & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0; |
| |
| setShowLockscreenNotifications(show && allowedByDpm); |
| |
| if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) { |
| final boolean remoteInput = Settings.Secure.getIntForUser(mContext.getContentResolver(), |
| Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT, |
| 0, |
| mCurrentUserId) != 0; |
| final boolean remoteInputDpm = |
| (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_REMOTE_INPUT) == 0; |
| |
| setLockScreenAllowRemoteInput(remoteInput && remoteInputDpm); |
| } else { |
| setLockScreenAllowRemoteInput(false); |
| } |
| } |
| |
| public void updateNotification(StatusBarNotification notification, RankingMap ranking) |
| throws InflationException { |
| if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")"); |
| |
| final String key = notification.getKey(); |
| Entry entry = mNotificationData.get(key); |
| if (entry == null) { |
| return; |
| } else { |
| mHeadsUpEntriesToRemoveOnSwitch.remove(entry); |
| mRemoteInputEntriesToRemoveOnCollapse.remove(entry); |
| } |
| |
| Notification n = notification.getNotification(); |
| mNotificationData.updateRanking(ranking); |
| |
| final StatusBarNotification oldNotification = entry.notification; |
| entry.notification = notification; |
| mGroupManager.onEntryUpdated(entry, oldNotification); |
| |
| entry.updateIcons(mContext, n); |
| inflateViews(entry, mStackScroller); |
| |
| boolean shouldPeek = shouldPeek(entry, notification); |
| boolean alertAgain = alertAgain(entry, n); |
| |
| updateHeadsUp(key, entry, shouldPeek, alertAgain); |
| updateNotifications(); |
| |
| if (!notification.isClearable()) { |
| // The user may have performed a dismiss action on the notification, since it's |
| // not clearable we should snap it back. |
| mStackScroller.snapViewIfNeeded(entry.row); |
| } |
| |
| if (DEBUG) { |
| // Is this for you? |
| boolean isForCurrentUser = isNotificationForCurrentProfiles(notification); |
| Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you"); |
| } |
| setAreThereNotifications(); |
| } |
| |
| protected void updatePublicContentView(Entry entry, |
| StatusBarNotification sbn) { |
| final RemoteViews publicContentView = entry.cachedPublicContentView; |
| View inflatedView = entry.getPublicContentView(); |
| if (entry.autoRedacted && publicContentView != null && inflatedView != null) { |
| final boolean disabledByPolicy = |
| !adminAllowsUnredactedNotifications(entry.notification.getUserId()); |
| String notificationHiddenText = mContext.getString(disabledByPolicy |
| ? com.android.internal.R.string.notification_hidden_by_policy_text |
| : com.android.internal.R.string.notification_hidden_text); |
| TextView titleView = (TextView) inflatedView.findViewById(android.R.id.title); |
| if (titleView != null |
| && !titleView.getText().toString().equals(notificationHiddenText)) { |
| titleView.setText(notificationHiddenText); |
| } |
| } |
| } |
| |
| protected void notifyHeadsUpScreenOff() { |
| maybeEscalateHeadsUp(); |
| } |
| |
| private boolean alertAgain(Entry oldEntry, Notification newNotification) { |
| return oldEntry == null || !oldEntry.hasInterrupted() |
| || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0; |
| } |
| |
| protected boolean shouldPeek(Entry entry) { |
| return shouldPeek(entry, entry.notification); |
| } |
| |
| protected boolean shouldPeek(Entry entry, StatusBarNotification sbn) { |
| if (!mUseHeadsUp || isDeviceInVrMode()) { |
| return false; |
| } |
| |
| if (mNotificationData.shouldFilterOut(sbn)) { |
| if (DEBUG) Log.d(TAG, "No peeking: filtered notification: " + sbn.getKey()); |
| return false; |
| } |
| |
| boolean inUse = mPowerManager.isScreenOn(); |
| try { |
| inUse = inUse && !mDreamManager.isDreaming(); |
| } catch (RemoteException e) { |
| Log.d(TAG, "failed to query dream manager", e); |
| } |
| |
| if (!inUse && !isDozing()) { |
| if (DEBUG) { |
| Log.d(TAG, "No peeking: not in use: " + sbn.getKey()); |
| } |
| return false; |
| } |
| |
| if (mNotificationData.shouldSuppressScreenOn(sbn.getKey())) { |
| if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey()); |
| return false; |
| } |
| |
| if (entry.hasJustLaunchedFullScreenIntent()) { |
| if (DEBUG) Log.d(TAG, "No peeking: recent fullscreen: " + sbn.getKey()); |
| return false; |
| } |
| |
| if (isSnoozedPackage(sbn)) { |
| if (DEBUG) Log.d(TAG, "No peeking: snoozed package: " + sbn.getKey()); |
| return false; |
| } |
| |
| // Allow peeking for DEFAULT notifications only if we're on Ambient Display. |
| int importanceLevel = isDozing() ? NotificationManager.IMPORTANCE_DEFAULT |
| : NotificationManager.IMPORTANCE_HIGH; |
| if (mNotificationData.getImportance(sbn.getKey()) < importanceLevel) { |
| if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey()); |
| return false; |
| } |
| |
| if (sbn.getNotification().fullScreenIntent != null) { |
| if (mAccessibilityManager.isTouchExplorationEnabled()) { |
| if (DEBUG) Log.d(TAG, "No peeking: accessible fullscreen: " + sbn.getKey()); |
| return false; |
| } else { |
| // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent |
| return !mStatusBarKeyguardViewManager.isShowing() |
| || mStatusBarKeyguardViewManager.isOccluded(); |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * @return Whether the security bouncer from Keyguard is showing. |
| */ |
| public boolean isBouncerShowing() { |
| return mBouncerShowing; |
| } |
| |
| /** |
| * @return a PackageManger for userId or if userId is < 0 (USER_ALL etc) then |
| * return PackageManager for mContext |
| */ |
| public static PackageManager getPackageManagerForUser(Context context, int userId) { |
| Context contextForUser = context; |
| // UserHandle defines special userId as negative values, e.g. USER_ALL |
| if (userId >= 0) { |
| try { |
| // Create a context for the correct user so if a package isn't installed |
| // for user 0 we can still load information about the package. |
| contextForUser = |
| context.createPackageContextAsUser(context.getPackageName(), |
| Context.CONTEXT_RESTRICTED, |
| new UserHandle(userId)); |
| } catch (NameNotFoundException e) { |
| // Shouldn't fail to find the package name for system ui. |
| } |
| } |
| return contextForUser.getPackageManager(); |
| } |
| |
| @Override |
| public void logNotificationExpansion(String key, boolean userAction, boolean expanded) { |
| try { |
| mBarService.onNotificationExpansionChanged(key, userAction, expanded); |
| } catch (RemoteException e) { |
| // Ignore. |
| } |
| } |
| |
| public boolean isKeyguardSecure() { |
| if (mStatusBarKeyguardViewManager == null) { |
| // startKeyguard() hasn't been called yet, so we don't know. |
| // Make sure anything that needs to know isKeyguardSecure() checks and re-checks this |
| // value onVisibilityChanged(). |
| Slog.w(TAG, "isKeyguardSecure() called before startKeyguard(), returning false", |
| new Throwable()); |
| return false; |
| } |
| return mStatusBarKeyguardViewManager.isSecure(); |
| } |
| |
| @Override |
| public void showAssistDisclosure() { |
| if (mAssistManager != null) { |
| mAssistManager.showDisclosure(); |
| } |
| } |
| |
| @Override |
| public void startAssist(Bundle args) { |
| if (mAssistManager != null) { |
| mAssistManager.startAssist(args); |
| } |
| } |
| // End Extra BaseStatusBarMethods. |
| } |