1 /* 2 * Copyright (C) 2013 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 android.content.Context; 20 import android.graphics.Rect; 21 import android.graphics.drawable.Icon; 22 import android.os.Bundle; 23 import android.os.UserHandle; 24 import android.util.Log; 25 import android.view.Gravity; 26 import android.view.View; 27 import android.view.ViewGroup; 28 import android.widget.LinearLayout; 29 30 import androidx.collection.MutableIntObjectMap; 31 32 import com.android.internal.statusbar.StatusBarIcon; 33 import com.android.systemui.Flags; 34 import com.android.systemui.demomode.DemoMode; 35 import com.android.systemui.kairos.ExperimentalKairosApi; 36 import com.android.systemui.kairos.KairosNetwork; 37 import com.android.systemui.plugins.DarkIconDispatcher; 38 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; 39 import com.android.systemui.res.R; 40 import com.android.systemui.statusbar.StatusBarIconView; 41 import com.android.systemui.statusbar.StatusIconDisplayable; 42 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapterKairos; 43 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger; 44 import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView; 45 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel; 46 import com.android.systemui.statusbar.pipeline.shared.ui.view.ModernStatusBarView; 47 import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView; 48 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel; 49 50 import dagger.Lazy; 51 52 import kotlin.OptIn; 53 import kotlin.Pair; 54 55 import kotlinx.coroutines.CoroutineScope; 56 import kotlinx.coroutines.Job; 57 58 import java.util.ArrayList; 59 import java.util.List; 60 import java.util.concurrent.CancellationException; 61 62 //TODO: This should be a controller, not its own view 63 @OptIn(markerClass = ExperimentalKairosApi.class) 64 public class DemoStatusIcons extends StatusIconContainer implements DemoMode, DarkReceiver { 65 private static final String TAG = "DemoStatusIcons"; 66 67 private final LinearLayout mStatusIcons; 68 private final ArrayList<ModernStatusBarMobileView> mModernMobileViews = new ArrayList<>(); 69 private final int mIconSize; 70 71 private ModernStatusBarWifiView mModernWifiView; 72 private boolean mDemoMode; 73 private int mColor; 74 private int mContrastColor; 75 76 private final MobileIconsViewModel mMobileIconsViewModel; 77 private final StatusBarLocation mLocation; 78 79 private final Lazy<MobileUiAdapterKairos> mMobileUiAdapterKairos; 80 private final KairosNetwork mKairosNetwork; 81 private final CoroutineScope mAppScope; 82 83 private final MutableIntObjectMap<Job> mBindingJobs = new MutableIntObjectMap<>(); 84 DemoStatusIcons( LinearLayout statusIcons, MobileIconsViewModel mobileIconsViewModel, StatusBarLocation location, int iconSize, Lazy<MobileUiAdapterKairos> mobileUiAdapterKairos, KairosNetwork kairosNetwork, CoroutineScope appScope )85 public DemoStatusIcons( 86 LinearLayout statusIcons, 87 MobileIconsViewModel mobileIconsViewModel, 88 StatusBarLocation location, 89 int iconSize, 90 Lazy<MobileUiAdapterKairos> mobileUiAdapterKairos, 91 KairosNetwork kairosNetwork, 92 CoroutineScope appScope 93 ) { 94 super(statusIcons.getContext()); 95 mStatusIcons = statusIcons; 96 mIconSize = iconSize; 97 mMobileUiAdapterKairos = mobileUiAdapterKairos; 98 mKairosNetwork = kairosNetwork; 99 mAppScope = appScope; 100 mColor = DarkIconDispatcher.DEFAULT_ICON_TINT; 101 mContrastColor = DarkIconDispatcher.DEFAULT_INVERSE_ICON_TINT; 102 mMobileIconsViewModel = mobileIconsViewModel; 103 mLocation = location; 104 105 if (statusIcons instanceof StatusIconContainer) { 106 setShouldRestrictIcons(((StatusIconContainer) statusIcons).isRestrictingIcons()); 107 } else { 108 setShouldRestrictIcons(false); 109 } 110 setLayoutParams(mStatusIcons.getLayoutParams()); 111 setPadding(mStatusIcons.getPaddingLeft(), mStatusIcons.getPaddingTop(), 112 mStatusIcons.getPaddingRight(), mStatusIcons.getPaddingBottom()); 113 setOrientation(mStatusIcons.getOrientation()); 114 setGravity(Gravity.CENTER_VERTICAL); // no LL.getGravity() 115 ViewGroup p = (ViewGroup) mStatusIcons.getParent(); 116 p.addView(this, p.indexOfChild(mStatusIcons)); 117 } 118 remove()119 public void remove() { 120 ((ViewGroup) getParent()).removeView(this); 121 } 122 123 /** Set the tint colors */ setColor(int color, int contrastColor)124 public void setColor(int color, int contrastColor) { 125 mColor = color; 126 mContrastColor = contrastColor; 127 updateColors(); 128 } 129 updateColors()130 private void updateColors() { 131 for (int i = 0; i < getChildCount(); i++) { 132 StatusIconDisplayable child = (StatusIconDisplayable) getChildAt(i); 133 child.setStaticDrawableColor(mColor, mContrastColor); 134 child.setDecorColor(mColor); 135 } 136 } 137 138 @Override demoCommands()139 public List<String> demoCommands() { 140 List<String> commands = new ArrayList<>(); 141 commands.add(COMMAND_STATUS); 142 return commands; 143 } 144 145 @Override onDemoModeStarted()146 public void onDemoModeStarted() { 147 mDemoMode = true; 148 mStatusIcons.setVisibility(View.GONE); 149 setVisibility(View.VISIBLE); 150 } 151 152 @Override onDemoModeFinished()153 public void onDemoModeFinished() { 154 mDemoMode = false; 155 mStatusIcons.setVisibility(View.VISIBLE); 156 mModernMobileViews.clear(); 157 setVisibility(View.GONE); 158 } 159 160 @Override dispatchDemoCommand(String command, Bundle args)161 public void dispatchDemoCommand(String command, Bundle args) { 162 String volume = args.getString("volume"); 163 if (volume != null) { 164 int iconId = volume.equals("vibrate") ? R.drawable.stat_sys_ringer_vibrate 165 : 0; 166 updateSlot("volume", null, iconId); 167 } 168 String zen = args.getString("zen"); 169 if (zen != null) { 170 int iconId = zen.equals("dnd") ? R.drawable.stat_sys_dnd : 0; 171 updateSlot("zen", null, iconId); 172 } 173 String bt = args.getString("bluetooth"); 174 if (bt != null) { 175 int iconId = bt.equals("connected") 176 ? R.drawable.stat_sys_data_bluetooth_connected : 0; 177 updateSlot("bluetooth", null, iconId); 178 } 179 String location = args.getString("location"); 180 if (location != null) { 181 int iconId = location.equals("show") ? PhoneStatusBarPolicy.LOCATION_STATUS_ICON_ID 182 : 0; 183 updateSlot("location", null, iconId); 184 } 185 String alarm = args.getString("alarm"); 186 if (alarm != null) { 187 int iconId = alarm.equals("show") ? R.drawable.stat_sys_alarm 188 : 0; 189 updateSlot("alarm_clock", null, iconId); 190 } 191 String tty = args.getString("tty"); 192 if (tty != null) { 193 int iconId = tty.equals("show") ? R.drawable.stat_sys_tty_mode 194 : 0; 195 updateSlot("tty", null, iconId); 196 } 197 String mute = args.getString("mute"); 198 if (mute != null) { 199 int iconId = mute.equals("show") ? android.R.drawable.stat_notify_call_mute 200 : 0; 201 updateSlot("mute", null, iconId); 202 } 203 String speakerphone = args.getString("speakerphone"); 204 if (speakerphone != null) { 205 int iconId = speakerphone.equals("show") ? android.R.drawable.stat_sys_speakerphone 206 : 0; 207 updateSlot("speakerphone", null, iconId); 208 } 209 String cast = args.getString("cast"); 210 if (cast != null) { 211 int iconId = cast.equals("show") ? R.drawable.stat_sys_cast : 0; 212 updateSlot("cast", null, iconId); 213 } 214 String hotspot = args.getString("hotspot"); 215 if (hotspot != null) { 216 int iconId = hotspot.equals("show") ? R.drawable.stat_sys_hotspot : 0; 217 updateSlot("hotspot", null, iconId); 218 } 219 } 220 221 /// Can only be used to update non-signal related slots updateSlot(String slot, String iconPkg, int iconId)222 private void updateSlot(String slot, String iconPkg, int iconId) { 223 if (!mDemoMode) return; 224 if (iconPkg == null) { 225 iconPkg = mContext.getPackageName(); 226 } 227 int removeIndex = -1; 228 for (int i = 0; i < getChildCount(); i++) { 229 View child = getChildAt(i); 230 if (!(child instanceof StatusBarIconView)) { 231 continue; 232 } 233 StatusBarIconView v = (StatusBarIconView) child; 234 if (slot.equals(v.getTag())) { 235 if (iconId == 0) { 236 removeIndex = i; 237 break; 238 } else { 239 StatusBarIcon icon = v.getStatusBarIcon(); 240 icon.visible = true; 241 icon.icon = Icon.createWithResource(icon.icon.getResPackage(), iconId); 242 v.set(icon); 243 v.updateDrawable(); 244 return; 245 } 246 } 247 } 248 if (iconId == 0) { 249 if (removeIndex != -1) { 250 removeViewAt(removeIndex); 251 } 252 return; 253 } 254 StatusBarIcon icon = new StatusBarIcon(iconPkg, UserHandle.SYSTEM, iconId, 0, 0, "Demo", 255 StatusBarIcon.Type.SystemIcon); 256 icon.visible = true; 257 StatusBarIconView v = new StatusBarIconView(getContext(), slot, null, false); 258 v.setTag(slot); 259 v.set(icon); 260 v.setStaticDrawableColor(mColor, mContrastColor); 261 v.setDecorColor(mColor); 262 addView(v, 0, createLayoutParams()); 263 } 264 265 /** 266 * Add a {@link ModernStatusBarMobileView} 267 * 268 * @param mobileContext possibly mcc/mnc overridden mobile context 269 * @param subId the subscriptionId for this mobile view 270 */ addModernMobileView( Context mobileContext, MobileViewLogger mobileViewLogger, int subId)271 public void addModernMobileView( 272 Context mobileContext, 273 MobileViewLogger mobileViewLogger, 274 int subId) { 275 Log.d(TAG, "addModernMobileView (subId=" + subId + ")"); 276 if (Flags.statusBarMobileIconKairos()) { 277 Pair<ModernStatusBarMobileView, Job> viewAndJob = 278 ModernStatusBarMobileView.constructAndBind( 279 mobileContext, 280 mobileViewLogger, 281 "mobile", 282 mMobileUiAdapterKairos.get().getMobileIconsViewModel().viewModelForSub( 283 subId, mLocation), 284 mAppScope, 285 subId, 286 mLocation, 287 mKairosNetwork 288 ); 289 ModernStatusBarMobileView view = viewAndJob.getFirst(); 290 mBindingJobs.put(subId, viewAndJob.getSecond()); 291 // mobile always goes at the end 292 mModernMobileViews.add(view); 293 addView(view, getChildCount(), createLayoutParams()); 294 } else { 295 ModernStatusBarMobileView view = 296 ModernStatusBarMobileView.constructAndBind( 297 mobileContext, 298 mobileViewLogger, 299 "mobile", 300 mMobileIconsViewModel.viewModelForSub(subId, mLocation) 301 ); 302 303 // mobile always goes at the end 304 mModernMobileViews.add(view); 305 addView(view, getChildCount(), createLayoutParams()); 306 } 307 } 308 309 /** 310 * Add a {@link ModernStatusBarWifiView} 311 */ addModernWifiView(LocationBasedWifiViewModel viewModel)312 public void addModernWifiView(LocationBasedWifiViewModel viewModel) { 313 Log.d(TAG, "addModernDemoWifiView: "); 314 ModernStatusBarWifiView view = ModernStatusBarWifiView 315 .constructAndBind(mContext, "wifi", viewModel); 316 317 int viewIndex = getChildCount(); 318 // If we have mobile views, put wifi before them 319 for (int i = 0; i < getChildCount(); i++) { 320 View child = getChildAt(i); 321 if (child instanceof ModernStatusBarMobileView) { 322 viewIndex = i; 323 break; 324 } 325 } 326 327 mModernWifiView = view; 328 mModernWifiView.setStaticDrawableColor(mColor, mContrastColor); 329 addView(view, viewIndex, createLayoutParams()); 330 } 331 332 /** Adds a bindable icon to the demo mode view. */ addBindableIcon(StatusBarIconHolder.BindableIconHolder holder)333 public void addBindableIcon(StatusBarIconHolder.BindableIconHolder holder) { 334 // This doesn't do any correct ordering, and also doesn't check if we already have an 335 // existing icon for the slot. But since we hope to remove this class soon, we won't spend 336 // the time adding that logic. 337 ModernStatusBarView view = holder.getInitializer().createAndBind(mContext); 338 addView(view, createLayoutParams()); 339 } 340 onRemoveIcon(StatusIconDisplayable view)341 public void onRemoveIcon(StatusIconDisplayable view) { 342 if (view.getSlot().equals("wifi")) { 343 if (view instanceof ModernStatusBarWifiView) { 344 Log.d(TAG, "onRemoveIcon: removing modern wifi view"); 345 removeView(mModernWifiView); 346 mModernWifiView = null; 347 } 348 } else if (view instanceof ModernStatusBarMobileView) { 349 ModernStatusBarMobileView mobileView = matchingModernMobileView( 350 (ModernStatusBarMobileView) view); 351 if (mobileView != null) { 352 if (Flags.statusBarMobileIconKairos()) { 353 Job job = mBindingJobs.remove(mobileView.getSubId()); 354 if (job != null) { 355 job.cancel(new CancellationException()); 356 } 357 } 358 removeView(mobileView); 359 mModernMobileViews.remove(mobileView); 360 } 361 } 362 } 363 matchingModernMobileView(ModernStatusBarMobileView other)364 private ModernStatusBarMobileView matchingModernMobileView(ModernStatusBarMobileView other) { 365 for (ModernStatusBarMobileView v : mModernMobileViews) { 366 if (v.getSubId() == other.getSubId()) { 367 return v; 368 } 369 } 370 371 return null; 372 } 373 createLayoutParams()374 private LayoutParams createLayoutParams() { 375 return new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize); 376 } 377 378 @Override onDarkChangedWithContrast(ArrayList<Rect> areas, int tint, int contrastTint)379 public void onDarkChangedWithContrast(ArrayList<Rect> areas, int tint, int contrastTint) { 380 setColor(tint, contrastTint); 381 382 if (mModernWifiView != null) { 383 mModernWifiView.onDarkChangedWithContrast(areas, tint, contrastTint); 384 } 385 386 for (ModernStatusBarMobileView view : mModernMobileViews) { 387 view.onDarkChangedWithContrast(areas, tint, contrastTint); 388 } 389 } 390 391 @Override onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint)392 public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) { 393 // not needed 394 } 395 } 396