1 /*
2  * Copyright (C) 2020 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.car.notification.headsup;
18 
19 import android.content.Context;
20 import android.view.LayoutInflater;
21 import android.view.View;
22 import android.view.ViewGroup;
23 import android.view.WindowManager;
24 
25 import androidx.annotation.VisibleForTesting;
26 
27 import com.android.car.notification.CarNotificationTypeItem;
28 import com.android.car.notification.R;
29 
30 import java.util.LinkedList;
31 
32 /**
33  * Container for displaying Heads Up Notifications.
34  */
35 public abstract class CarHeadsUpNotificationContainer {
36     private static final String TAG = "CarHUNContainer";
37     private final LinkedList<HunImportance> mHunImportanceLinkedList = new LinkedList<>();
38     private final ViewGroup mHunWindow;
39     private final ViewGroup mHunContent;
40     private final boolean mShowHunOnBottom;
41     private final Context mContext;
42 
CarHeadsUpNotificationContainer(Context context, WindowManager windowManager)43     public CarHeadsUpNotificationContainer(Context context, WindowManager windowManager) {
44         mContext = context;
45         mShowHunOnBottom = context.getResources().getBoolean(
46                 R.bool.config_showHeadsUpNotificationOnBottom);
47         mHunWindow = (ViewGroup) LayoutInflater.from(context).inflate(
48                 mShowHunOnBottom ? R.layout.headsup_container_bottom
49                         : R.layout.headsup_container, /* root= */ null, /* attachToRoot= */ false);
50         mHunContent = mHunWindow.findViewById(R.id.headsup_content);
51         mHunWindow.setVisibility(View.INVISIBLE);
52         windowManager.addView(mHunWindow, getWindowManagerLayoutParams());
53     }
54 
55     /**
56      * @return {@link WindowManager.LayoutParams} to be used when adding HUN Window to {@link
57      * WindowManager}.
58      */
getWindowManagerLayoutParams()59     protected abstract WindowManager.LayoutParams getWindowManagerLayoutParams();
60 
getContext()61     protected Context getContext() {
62         return mContext;
63     }
64 
65     /**
66      * Displays a given notification View to the user and inserts the view at Z-index according to
67      * its {@link HunImportance},
68      */
displayNotification(View notificationView, CarNotificationTypeItem notificationTypeItem)69     public void displayNotification(View notificationView,
70             CarNotificationTypeItem notificationTypeItem) {
71         HunImportance hunImportance = getImportanceForCarNotificationTypeItem(notificationTypeItem);
72 
73         displayNotificationInner(notificationView, hunImportance);
74 
75         if (shouldShowHunPanel()) {
76             getHunWindow().setVisibility(View.VISIBLE);
77         }
78     }
79 
displayNotificationInner(View notificationView, HunImportance hunImportance)80     private void displayNotificationInner(View notificationView, HunImportance hunImportance) {
81         if (mHunImportanceLinkedList.isEmpty() || hunImportance.equals(HunImportance.EMERGENCY)) {
82             mHunImportanceLinkedList.add(hunImportance);
83             getHunContent().addView(notificationView);
84             return;
85         }
86 
87         int index = 0;
88         for (; index < mHunImportanceLinkedList.size(); index++) {
89             if (hunImportance.isLessImportantThan(mHunImportanceLinkedList.get(index))) break;
90         }
91         if (index < mHunImportanceLinkedList.size()) {
92             mHunImportanceLinkedList.add(index, hunImportance);
93             getHunContent().addView(notificationView, index);
94             return;
95         }
96 
97         mHunImportanceLinkedList.add(hunImportance);
98         getHunContent().addView(notificationView);
99     }
100 
101     /**
102      * @return {@code true} if Hun panel should be set as visible after displaying HUN.
103      */
shouldShowHunPanel()104     public boolean shouldShowHunPanel() {
105         return !isVisible();
106     }
107 
108     /**
109      * Removes a given notification View from the container.
110      */
removeNotification(View notificationView)111     public void removeNotification(View notificationView) {
112         if (getHunContent().getChildCount() == 0) return;
113 
114         int index = getHunContent().indexOfChild(notificationView);
115         if (index == -1) return;
116 
117         getHunContent().removeViewAt(index);
118         mHunImportanceLinkedList.remove(index);
119 
120         if (shouldHideHunPanel()) {
121             getHunWindow().setVisibility(View.INVISIBLE);
122         }
123     }
124 
125     /**
126      * @return {@code true} if HUN panel should be set as invisible after removing a HUN.
127      */
shouldHideHunPanel()128     public boolean shouldHideHunPanel() {
129         return getHunContent().getChildCount() == 0;
130     }
131 
132     /**
133      * @return Whether or not the container is currently visible.
134      */
isVisible()135     public final boolean isVisible() {
136         return getHunWindow().getVisibility() == View.VISIBLE;
137     }
138 
139     /**
140      * @return HUN window.
141      */
getHunWindow()142     protected final ViewGroup getHunWindow() {
143         return mHunWindow;
144     }
145 
146     /**
147      * @return HUN content inside of window.
148      */
getHunContent()149     protected final ViewGroup getHunContent() {
150         return mHunContent;
151     }
152 
153     /**
154      * @return {@code true} if HUN should be shown on bottom.
155      */
getShowHunOnBottom()156     protected final boolean getShowHunOnBottom() {
157         return mShowHunOnBottom;
158     }
159 
getImportanceForCarNotificationTypeItem( CarNotificationTypeItem notificationTypeItem)160     private HunImportance getImportanceForCarNotificationTypeItem(
161             CarNotificationTypeItem notificationTypeItem) {
162         if (notificationTypeItem == CarNotificationTypeItem.EMERGENCY) {
163             return HunImportance.EMERGENCY;
164         } else if (notificationTypeItem == CarNotificationTypeItem.WARNING) {
165             return HunImportance.WARNING;
166         } else if (notificationTypeItem == CarNotificationTypeItem.NAVIGATION) {
167             return HunImportance.NAVIGATION;
168         } else if (notificationTypeItem == CarNotificationTypeItem.CALL) {
169             return HunImportance.CALL;
170         } else {
171             return HunImportance.OTHER;
172         }
173     }
174 
175     @VisibleForTesting
176     enum HunImportance {
177         OTHER(/* level= */ 0),
178         CALL(/* level= */ 1),
179         NAVIGATION(/* level= */ 2),
180         WARNING(/* level= */ 3),
181         EMERGENCY(/* level= */ 4);
182 
183         private final Integer mLevel;
184 
HunImportance(int level)185         HunImportance(int level) {
186             this.mLevel = level;
187         }
188 
isMoreImportantThan(HunImportance other)189         boolean isMoreImportantThan(HunImportance other) {
190             return this.mLevel > other.mLevel;
191         }
192 
isLessImportantThan(HunImportance other)193         boolean isLessImportantThan(HunImportance other) {
194             return this.mLevel < other.mLevel;
195         }
196     }
197 }
198