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