1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.statusbar.phone; 18 19 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS; 20 21 import static com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.LIGHTS_OUT_NOTIF_VIEW; 22 23 import android.animation.Animator; 24 import android.animation.AnimatorListenerAdapter; 25 import android.view.View; 26 import android.view.WindowInsets.Type.InsetsType; 27 import android.view.WindowInsetsController.Appearance; 28 import android.view.WindowInsetsController.Behavior; 29 import android.view.WindowManager; 30 import android.view.animation.AccelerateInterpolator; 31 32 import androidx.lifecycle.Observer; 33 34 import com.android.internal.annotations.VisibleForTesting; 35 import com.android.internal.statusbar.LetterboxDetails; 36 import com.android.internal.view.AppearanceRegion; 37 import com.android.systemui.statusbar.CommandQueue; 38 import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore; 39 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor; 40 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope; 41 import com.android.systemui.util.ViewController; 42 43 import javax.inject.Inject; 44 import javax.inject.Named; 45 46 /** 47 * Apps can request a low profile mode {@link View#SYSTEM_UI_FLAG_LOW_PROFILE} 48 * where status bar and navigation icons dim. In this mode, a notification dot appears 49 * where the notification icons would appear if they would be shown outside of this mode. 50 * 51 * This controller shows and hides the notification dot in the status bar to indicate 52 * whether there are notifications when the device is in {@link View#SYSTEM_UI_FLAG_LOW_PROFILE}. 53 */ 54 @StatusBarFragmentScope 55 public class LegacyLightsOutNotifController extends ViewController<View> { 56 private final CommandQueue mCommandQueue; 57 private final NotifLiveDataStore mNotifDataStore; 58 private final WindowManager mWindowManager; 59 private final Observer<Boolean> mObserver = hasNotifs -> updateLightsOutView(); 60 61 /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int, int) */ 62 @VisibleForTesting @Appearance int mAppearance; 63 64 private int mDisplayId; 65 66 @Inject LegacyLightsOutNotifController( @amedLIGHTS_OUT_NOTIF_VIEW) View lightsOutNotifView, WindowManager windowManager, NotifLiveDataStore notifDataStore, CommandQueue commandQueue)67 LegacyLightsOutNotifController( 68 @Named(LIGHTS_OUT_NOTIF_VIEW) View lightsOutNotifView, 69 WindowManager windowManager, 70 NotifLiveDataStore notifDataStore, 71 CommandQueue commandQueue) { 72 super(lightsOutNotifView); 73 mWindowManager = windowManager; 74 mNotifDataStore = notifDataStore; 75 mCommandQueue = commandQueue; 76 } 77 78 @Override onInit()79 protected void onInit() { 80 super.onInit(); 81 NotificationsLiveDataStoreRefactor.assertInLegacyMode(); 82 } 83 84 @Override onViewDetached()85 protected void onViewDetached() { 86 mNotifDataStore.getHasActiveNotifs().removeObserver(mObserver); 87 mCommandQueue.removeCallback(mCallback); 88 } 89 90 @Override onViewAttached()91 protected void onViewAttached() { 92 mView.setVisibility(View.GONE); 93 mView.setAlpha(0f); 94 95 mDisplayId = mWindowManager.getDefaultDisplay().getDisplayId(); 96 mNotifDataStore.getHasActiveNotifs().addSyncObserver(mObserver); 97 mCommandQueue.addCallback(mCallback); 98 99 updateLightsOutView(); 100 } 101 hasActiveNotifications()102 private boolean hasActiveNotifications() { 103 return mNotifDataStore.getHasActiveNotifs().getValue(); 104 } 105 106 @VisibleForTesting updateLightsOutView()107 void updateLightsOutView() { 108 final boolean showDot = shouldShowDot(); 109 if (showDot != isShowingDot()) { 110 if (showDot) { 111 mView.setAlpha(0f); 112 mView.setVisibility(View.VISIBLE); 113 } 114 115 mView.animate() 116 .alpha(showDot ? 1 : 0) 117 .setDuration(showDot ? 750 : 250) 118 .setInterpolator(new AccelerateInterpolator(2.0f)) 119 .setListener(new AnimatorListenerAdapter() { 120 @Override 121 public void onAnimationEnd(Animator a) { 122 mView.setAlpha(showDot ? 1 : 0); 123 mView.setVisibility(showDot ? View.VISIBLE : View.GONE); 124 // Unset the listener, otherwise this may persist for 125 // another view property animation 126 mView.animate().setListener(null); 127 } 128 }) 129 .start(); 130 } 131 } 132 133 @VisibleForTesting isShowingDot()134 boolean isShowingDot() { 135 return mView.getVisibility() == View.VISIBLE 136 && mView.getAlpha() == 1.0f; 137 } 138 139 @VisibleForTesting shouldShowDot()140 boolean shouldShowDot() { 141 return hasActiveNotifications() && areLightsOut(); 142 } 143 144 @VisibleForTesting areLightsOut()145 boolean areLightsOut() { 146 return 0 != (mAppearance & APPEARANCE_LOW_PROFILE_BARS); 147 } 148 149 private final CommandQueue.Callbacks mCallback = new CommandQueue.Callbacks() { 150 @Override 151 public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance, 152 AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, 153 @Behavior int behavior, @InsetsType int requestedVisibleTypes, 154 String packageName, LetterboxDetails[] letterboxDetails) { 155 if (displayId != mDisplayId) { 156 return; 157 } 158 mAppearance = appearance; 159 updateLightsOutView(); 160 } 161 }; 162 } 163