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