1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 
15 package com.android.systemui.util;
16 
17 import static android.view.Display.DEFAULT_DISPLAY;
18 
19 import android.Manifest;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.pm.PackageManager;
23 import android.content.res.Resources;
24 import android.content.res.TypedArray;
25 import android.provider.Settings;
26 import android.view.ContextThemeWrapper;
27 import android.view.DisplayCutout;
28 import android.view.View;
29 
30 import com.android.internal.policy.SystemBarUtils;
31 import com.android.systemui.R;
32 import com.android.systemui.shared.system.QuickStepContract;
33 import com.android.systemui.statusbar.CommandQueue;
34 
35 import java.util.List;
36 import java.util.function.Consumer;
37 
38 public class Utils {
39 
40     /**
41      * Allows lambda iteration over a list. It is done in reverse order so it is safe
42      * to add or remove items during the iteration.  Skips over null items.
43      */
safeForeach(List<T> list, Consumer<T> c)44     public static <T> void safeForeach(List<T> list, Consumer<T> c) {
45         for (int i = list.size() - 1; i >= 0; i--) {
46             T item = list.get(i);
47             if (item != null) {
48                 c.accept(item);
49             }
50         }
51     }
52 
53     /**
54      * Sets the visibility of an UI element according to the DISABLE_* flags in
55      * {@link android.app.StatusBarManager}.
56      */
57     public static class DisableStateTracker implements CommandQueue.Callbacks,
58             View.OnAttachStateChangeListener {
59         private final int mMask1;
60         private final int mMask2;
61         private final CommandQueue mCommandQueue;
62         private View mView;
63         private boolean mDisabled;
64 
DisableStateTracker(int disableMask, int disable2Mask, CommandQueue commandQueue)65         public DisableStateTracker(int disableMask, int disable2Mask, CommandQueue commandQueue) {
66             mMask1 = disableMask;
67             mMask2 = disable2Mask;
68             mCommandQueue = commandQueue;
69         }
70 
71         @Override
onViewAttachedToWindow(View v)72         public void onViewAttachedToWindow(View v) {
73             mView = v;
74             mCommandQueue.addCallback(this);
75         }
76 
77         @Override
onViewDetachedFromWindow(View v)78         public void onViewDetachedFromWindow(View v) {
79             mCommandQueue.removeCallback(this);
80             mView = null;
81         }
82 
83         /**
84          * Sets visibility of this {@link View} given the states passed from
85          * {@link com.android.systemui.statusbar.CommandQueue.Callbacks#disable(int, int, int)}.
86          */
87         @Override
disable(int displayId, int state1, int state2, boolean animate)88         public void disable(int displayId, int state1, int state2, boolean animate) {
89             if (displayId != mView.getDisplay().getDisplayId()) {
90                 return;
91             }
92             final boolean disabled = ((state1 & mMask1) != 0) || ((state2 & mMask2) != 0);
93             if (disabled == mDisabled) return;
94             mDisabled = disabled;
95             mView.setVisibility(disabled ? View.GONE : View.VISIBLE);
96         }
97 
98         /** @return {@code true} if and only if this {@link View} is currently disabled */
isDisabled()99         public boolean isDisabled() {
100             return mDisabled;
101         }
102     }
103 
104 
105     /**
106      * Returns {@code true} iff the package {@code packageName} is a headless remote display
107      * provider, i.e, that the package holds the privileged {@code REMOTE_DISPLAY_PROVIDER}
108      * permission and that it doesn't host a launcher icon.
109      */
isHeadlessRemoteDisplayProvider(PackageManager pm, String packageName)110     public static boolean isHeadlessRemoteDisplayProvider(PackageManager pm, String packageName) {
111         if (pm.checkPermission(Manifest.permission.REMOTE_DISPLAY_PROVIDER, packageName)
112                 != PackageManager.PERMISSION_GRANTED) {
113             return false;
114         }
115 
116         Intent homeIntent = new Intent(Intent.ACTION_MAIN);
117         homeIntent.addCategory(Intent.CATEGORY_LAUNCHER);
118         homeIntent.setPackage(packageName);
119 
120         return pm.queryIntentActivities(homeIntent, 0).isEmpty();
121     }
122 
123     /**
124      * Returns {@code true} if the navMode is that of
125      * {@link android.view.WindowManagerPolicyConstants#NAV_BAR_MODE_GESTURAL} AND
126      * the context is that of the default display
127      */
isGesturalModeOnDefaultDisplay(Context context, int navMode)128     public static boolean isGesturalModeOnDefaultDisplay(Context context, int navMode) {
129         return context.getDisplayId() == DEFAULT_DISPLAY
130                 && QuickStepContract.isGesturalMode(navMode);
131     }
132 
133     /**
134      * Allow the media player to be shown in the QS area, controlled by 2 flags.
135      * Off by default, but can be disabled by setting to 0
136      */
useQsMediaPlayer(Context context)137     public static boolean useQsMediaPlayer(Context context) {
138         int flag = Settings.Global.getInt(context.getContentResolver(),
139                 Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1);
140         return flag > 0;
141     }
142 
143     /**
144      * Allow media resumption controls. Requires {@link #useQsMediaPlayer(Context)} to be enabled.
145      * On by default, but can be disabled by setting to 0
146      */
useMediaResumption(Context context)147     public static boolean useMediaResumption(Context context) {
148         int flag = Settings.Secure.getInt(context.getContentResolver(),
149                 Settings.Secure.MEDIA_CONTROLS_RESUME, 1);
150         return useQsMediaPlayer(context) && flag > 0;
151     }
152 
153     /**
154      * Allow recommendations from smartspace to show in media controls.
155      * Requires {@link #useQsMediaPlayer(Context)} to be enabled.
156      * On by default, but can be disabled by setting to 0
157      */
allowMediaRecommendations(Context context)158     public static boolean allowMediaRecommendations(Context context) {
159         int flag = Settings.Secure.getInt(context.getContentResolver(),
160                 Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 1);
161         return useQsMediaPlayer(context) && flag > 0;
162     }
163 
164     /**
165      * Returns true if the device should use the split notification shade, based on orientation and
166      * screen width.
167      */
shouldUseSplitNotificationShade(Resources resources)168     public static boolean shouldUseSplitNotificationShade(Resources resources) {
169         return resources.getBoolean(R.bool.config_use_split_notification_shade);
170     }
171 
172     /**
173      * Returns the color provided at the specified {@param attrIndex} in {@param a} if it exists,
174      * otherwise, returns the color from the private attribute {@param privAttrId}.
175      */
getPrivateAttrColorIfUnset(ContextThemeWrapper ctw, TypedArray a, int attrIndex, int defColor, int privAttrId)176     public static int getPrivateAttrColorIfUnset(ContextThemeWrapper ctw, TypedArray a,
177             int attrIndex, int defColor, int privAttrId) {
178         // If the index is specified, use that value
179         if (a.hasValue(attrIndex)) {
180             return a.getColor(attrIndex, defColor);
181         }
182 
183         // Otherwise fallback to the value of the private attribute
184         int[] customAttrs = { privAttrId };
185         a = ctw.obtainStyledAttributes(customAttrs);
186         int color = a.getColor(0, defColor);
187         a.recycle();
188         return color;
189     }
190 
191     /**
192      * Gets the {@link R.dimen#split_shade_header_height}.
193      *
194      * Currently, it's the same as {@link com.android.internal.R.dimen#quick_qs_offset_height}.
195      */
getSplitShadeStatusBarHeight(Context context)196     public static int getSplitShadeStatusBarHeight(Context context) {
197         return SystemBarUtils.getQuickQsOffsetHeight(context);
198     }
199 
200     /**
201      * Gets the {@link R.dimen#qs_header_system_icons_area_height}.
202      *
203      * It's the same as {@link com.android.internal.R.dimen#quick_qs_offset_height} except for
204      * sw600dp-land.
205      */
getQsHeaderSystemIconsAreaHeight(Context context)206     public static int getQsHeaderSystemIconsAreaHeight(Context context) {
207         final Resources res = context.getResources();
208         if (Utils.shouldUseSplitNotificationShade(res)) {
209             return res.getDimensionPixelSize(R.dimen.qs_header_system_icons_area_height);
210         } else {
211             return SystemBarUtils.getQuickQsOffsetHeight(context);
212         }
213     }
214 
215     /**
216      * Gets the {@link R.dimen#status_bar_header_height_keyguard}.
217      */
getStatusBarHeaderHeightKeyguard(Context context)218     public static int getStatusBarHeaderHeightKeyguard(Context context) {
219         final int statusBarHeight = SystemBarUtils.getStatusBarHeight(context);
220         final DisplayCutout cutout = context.getDisplay().getCutout();
221         final int waterfallInsetTop = cutout == null ? 0 : cutout.getWaterfallInsets().top;
222         final int statusBarHeaderHeightKeyguard = context.getResources()
223                 .getDimensionPixelSize(R.dimen.status_bar_header_height_keyguard);
224         return Math.max(statusBarHeight, statusBarHeaderHeightKeyguard + waterfallInsetTop);
225     }
226 }
227