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.phone.fragment.dagger.StatusBarFragmentScope; 40 import com.android.systemui.util.ViewController; 41 42 import javax.inject.Inject; 43 import javax.inject.Named; 44 45 /** 46 * Apps can request a low profile mode {@link View#SYSTEM_UI_FLAG_LOW_PROFILE} 47 * where status bar and navigation icons dim. In this mode, a notification dot appears 48 * where the notification icons would appear if they would be shown outside of this mode. 49 * 50 * This controller shows and hides the notification dot in the status bar to indicate 51 * whether there are notifications when the device is in {@link View#SYSTEM_UI_FLAG_LOW_PROFILE}. 52 */ 53 @StatusBarFragmentScope 54 public class LightsOutNotifController extends ViewController<View> { 55 private final CommandQueue mCommandQueue; 56 private final NotifLiveDataStore mNotifDataStore; 57 private final WindowManager mWindowManager; 58 private final Observer<Boolean> mObserver = hasNotifs -> updateLightsOutView(); 59 60 /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int, int) */ 61 @VisibleForTesting @Appearance int mAppearance; 62 63 private int mDisplayId; 64 65 @Inject LightsOutNotifController( @amedLIGHTS_OUT_NOTIF_VIEW) View lightsOutNotifView, WindowManager windowManager, NotifLiveDataStore notifDataStore, CommandQueue commandQueue)66 LightsOutNotifController( 67 @Named(LIGHTS_OUT_NOTIF_VIEW) View lightsOutNotifView, 68 WindowManager windowManager, 69 NotifLiveDataStore notifDataStore, 70 CommandQueue commandQueue) { 71 super(lightsOutNotifView); 72 mWindowManager = windowManager; 73 mNotifDataStore = notifDataStore; 74 mCommandQueue = commandQueue; 75 76 } 77 78 @Override onViewDetached()79 protected void onViewDetached() { 80 mNotifDataStore.getHasActiveNotifs().removeObserver(mObserver); 81 mCommandQueue.removeCallback(mCallback); 82 } 83 84 @Override onViewAttached()85 protected void onViewAttached() { 86 mView.setVisibility(View.GONE); 87 mView.setAlpha(0f); 88 89 mDisplayId = mWindowManager.getDefaultDisplay().getDisplayId(); 90 mNotifDataStore.getHasActiveNotifs().addSyncObserver(mObserver); 91 mCommandQueue.addCallback(mCallback); 92 93 updateLightsOutView(); 94 } 95 hasActiveNotifications()96 private boolean hasActiveNotifications() { 97 return mNotifDataStore.getHasActiveNotifs().getValue(); 98 } 99 100 @VisibleForTesting updateLightsOutView()101 void updateLightsOutView() { 102 final boolean showDot = shouldShowDot(); 103 if (showDot != isShowingDot()) { 104 if (showDot) { 105 mView.setAlpha(0f); 106 mView.setVisibility(View.VISIBLE); 107 } 108 109 mView.animate() 110 .alpha(showDot ? 1 : 0) 111 .setDuration(showDot ? 750 : 250) 112 .setInterpolator(new AccelerateInterpolator(2.0f)) 113 .setListener(new AnimatorListenerAdapter() { 114 @Override 115 public void onAnimationEnd(Animator a) { 116 mView.setAlpha(showDot ? 1 : 0); 117 mView.setVisibility(showDot ? View.VISIBLE : View.GONE); 118 // Unset the listener, otherwise this may persist for 119 // another view property animation 120 mView.animate().setListener(null); 121 } 122 }) 123 .start(); 124 } 125 } 126 127 @VisibleForTesting isShowingDot()128 boolean isShowingDot() { 129 return mView.getVisibility() == View.VISIBLE 130 && mView.getAlpha() == 1.0f; 131 } 132 133 @VisibleForTesting shouldShowDot()134 boolean shouldShowDot() { 135 return hasActiveNotifications() && areLightsOut(); 136 } 137 138 @VisibleForTesting areLightsOut()139 boolean areLightsOut() { 140 return 0 != (mAppearance & APPEARANCE_LOW_PROFILE_BARS); 141 } 142 143 private final CommandQueue.Callbacks mCallback = new CommandQueue.Callbacks() { 144 @Override 145 public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance, 146 AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, 147 @Behavior int behavior, @InsetsType int requestedVisibleTypes, 148 String packageName, LetterboxDetails[] letterboxDetails) { 149 if (displayId != mDisplayId) { 150 return; 151 } 152 mAppearance = appearance; 153 updateLightsOutView(); 154 } 155 }; 156 } 157