1 /* 2 * Copyright (C) 2022 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.complication; 18 19 import static com.android.systemui.complication.dagger.DreamHomeControlsComplicationComponent.DreamHomeControlsModule.DREAM_HOME_CONTROLS_CHIP_VIEW; 20 import static com.android.systemui.complication.dagger.RegisteredComplicationsModule.DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS; 21 import static com.android.systemui.complication.dagger.RegisteredComplicationsModule.OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS; 22 import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE; 23 import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK; 24 import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.UNAVAILABLE; 25 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.res.Resources; 29 import android.graphics.drawable.Drawable; 30 import android.util.Log; 31 import android.view.View; 32 import android.widget.ImageView; 33 34 import androidx.annotation.Nullable; 35 36 import com.android.internal.logging.UiEventLogger; 37 import com.android.settingslib.Utils; 38 import com.android.systemui.CoreStartable; 39 import com.android.systemui.animation.ActivityTransitionAnimator; 40 import com.android.systemui.complication.dagger.DreamHomeControlsComplicationComponent; 41 import com.android.systemui.controls.ControlsServiceInfo; 42 import com.android.systemui.controls.dagger.ControlsComponent; 43 import com.android.systemui.controls.management.ControlsListingController; 44 import com.android.systemui.controls.ui.ControlsActivity; 45 import com.android.systemui.controls.ui.ControlsUiController; 46 import com.android.systemui.dagger.qualifiers.Main; 47 import com.android.systemui.dagger.qualifiers.SystemUser; 48 import com.android.systemui.dreams.DreamOverlayStateController; 49 import com.android.systemui.plugins.ActivityStarter; 50 import com.android.systemui.shared.condition.Monitor; 51 import com.android.systemui.statusbar.policy.ConfigurationController; 52 import com.android.systemui.util.ViewController; 53 import com.android.systemui.util.condition.ConditionalCoreStartable; 54 55 import java.util.List; 56 57 import javax.inject.Inject; 58 import javax.inject.Named; 59 60 /** 61 * A dream complication that shows a home controls chip to launch device controls (to control 62 * devices at home like lights and thermostats). 63 */ 64 public class DreamHomeControlsComplication implements Complication { 65 private final Resources mResources; 66 private final DreamHomeControlsComplicationComponent.Factory mComponentFactory; 67 68 @Inject DreamHomeControlsComplication( @ain Resources resources, DreamHomeControlsComplicationComponent.Factory componentFactory)69 public DreamHomeControlsComplication( 70 @Main Resources resources, 71 DreamHomeControlsComplicationComponent.Factory componentFactory) { 72 mResources = resources; 73 mComponentFactory = componentFactory; 74 } 75 76 @Override createView(ComplicationViewModel model)77 public ViewHolder createView(ComplicationViewModel model) { 78 return mComponentFactory.create(mResources).getViewHolder(); 79 } 80 81 @Override getRequiredTypeAvailability()82 public int getRequiredTypeAvailability() { 83 return COMPLICATION_TYPE_HOME_CONTROLS; 84 } 85 86 /** 87 * {@link CoreStartable} for registering the complication with SystemUI on startup. 88 */ 89 public static class Registrant extends ConditionalCoreStartable { 90 private final DreamHomeControlsComplication mComplication; 91 private final DreamOverlayStateController mDreamOverlayStateController; 92 private final ControlsComponent mControlsComponent; 93 private final boolean mReplacedByOpenHub; 94 95 private boolean mOverlayActive = false; 96 97 // Callback for when the home controls service availability changes. 98 private final ControlsListingController.ControlsListingCallback mControlsCallback = 99 services -> updateHomeControlsComplication(); 100 101 private final DreamOverlayStateController.Callback mOverlayStateCallback = 102 new DreamOverlayStateController.Callback() { 103 @Override 104 public void onStateChanged() { 105 if (mOverlayActive == mDreamOverlayStateController.isOverlayActive()) { 106 return; 107 } 108 109 mOverlayActive = !mOverlayActive; 110 111 if (mOverlayActive) { 112 updateHomeControlsComplication(); 113 } 114 } 115 }; 116 117 @Inject Registrant(DreamHomeControlsComplication complication, DreamOverlayStateController dreamOverlayStateController, ControlsComponent controlsComponent, @SystemUser Monitor monitor, @Named(OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS) boolean replacedByOpenHub)118 public Registrant(DreamHomeControlsComplication complication, 119 DreamOverlayStateController dreamOverlayStateController, 120 ControlsComponent controlsComponent, 121 @SystemUser Monitor monitor, 122 @Named(OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS) boolean replacedByOpenHub) { 123 super(monitor); 124 mComplication = complication; 125 mControlsComponent = controlsComponent; 126 mDreamOverlayStateController = dreamOverlayStateController; 127 mReplacedByOpenHub = replacedByOpenHub; 128 } 129 130 @Override onStart()131 public void onStart() { 132 mControlsComponent.getControlsListingController().ifPresent( 133 c -> c.addCallback(mControlsCallback)); 134 mDreamOverlayStateController.addCallback(mOverlayStateCallback); 135 } 136 updateHomeControlsComplication()137 private void updateHomeControlsComplication() { 138 mControlsComponent.getControlsListingController().ifPresent(c -> { 139 if (isHomeControlsAvailable(c.getCurrentServices())) { 140 mDreamOverlayStateController.addComplication(mComplication); 141 } else { 142 mDreamOverlayStateController.removeComplication(mComplication); 143 } 144 }); 145 } 146 isHomeControlsAvailable(List<ControlsServiceInfo> controlsServices)147 private boolean isHomeControlsAvailable(List<ControlsServiceInfo> controlsServices) { 148 if (controlsServices.isEmpty()) { 149 return false; 150 } 151 152 final boolean hasFavorites = mControlsComponent.getControlsController() 153 .map(c -> !c.getFavorites().isEmpty()) 154 .orElse(false); 155 boolean hasPanels = false; 156 for (int i = 0; i < controlsServices.size(); i++) { 157 if (controlsServices.get(i).getPanelActivity() != null) { 158 hasPanels = true; 159 break; 160 } 161 } 162 final ControlsComponent.Visibility visibility = mControlsComponent.getVisibility(); 163 return (hasFavorites || hasPanels) && visibility != UNAVAILABLE; 164 } 165 } 166 167 /** 168 * Contains values/logic associated with the dream complication view. 169 */ 170 public static class DreamHomeControlsChipViewHolder implements ViewHolder { 171 private final ImageView mView; 172 private final ComplicationLayoutParams mLayoutParams; 173 private final DreamHomeControlsChipViewController mViewController; 174 175 @Inject DreamHomeControlsChipViewHolder( DreamHomeControlsChipViewController dreamHomeControlsChipViewController, @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view, @Named(DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS) ComplicationLayoutParams layoutParams )176 DreamHomeControlsChipViewHolder( 177 DreamHomeControlsChipViewController dreamHomeControlsChipViewController, 178 @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view, 179 @Named(DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS) ComplicationLayoutParams layoutParams 180 ) { 181 mView = view; 182 mLayoutParams = layoutParams; 183 mViewController = dreamHomeControlsChipViewController; 184 mViewController.init(); 185 } 186 187 @Override getView()188 public ImageView getView() { 189 return mView; 190 } 191 192 @Override getLayoutParams()193 public ComplicationLayoutParams getLayoutParams() { 194 return mLayoutParams; 195 } 196 } 197 198 /** 199 * Controls behavior of the dream complication. 200 */ 201 static class DreamHomeControlsChipViewController extends ViewController<ImageView> { 202 private static final String TAG = "DreamHomeControlsCtrl"; 203 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 204 205 private final ActivityStarter mActivityStarter; 206 private final Context mContext; 207 private final ConfigurationController mConfigurationController; 208 private final ControlsComponent mControlsComponent; 209 210 private final UiEventLogger mUiEventLogger; 211 212 private final ConfigurationController.ConfigurationListener mConfigurationListener = 213 new ConfigurationController.ConfigurationListener() { 214 @Override 215 public void onUiModeChanged() { 216 reloadResources(); 217 } 218 }; 219 220 @Inject DreamHomeControlsChipViewController( @amedDREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view, ActivityStarter activityStarter, Context context, ConfigurationController configurationController, ControlsComponent controlsComponent, UiEventLogger uiEventLogger)221 DreamHomeControlsChipViewController( 222 @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view, 223 ActivityStarter activityStarter, 224 Context context, 225 ConfigurationController configurationController, 226 ControlsComponent controlsComponent, 227 UiEventLogger uiEventLogger) { 228 super(view); 229 230 mActivityStarter = activityStarter; 231 mContext = context; 232 mConfigurationController = configurationController; 233 mControlsComponent = controlsComponent; 234 mUiEventLogger = uiEventLogger; 235 } 236 237 @Override onViewAttached()238 protected void onViewAttached() { 239 reloadResources(); 240 mView.setOnClickListener(this::onClickHomeControls); 241 mConfigurationController.addCallback(mConfigurationListener); 242 } 243 244 @Override onViewDetached()245 protected void onViewDetached() { 246 mConfigurationController.removeCallback(mConfigurationListener); 247 } 248 reloadResources()249 private void reloadResources() { 250 final String title = getControlsTitle(); 251 if (title != null) { 252 mView.setContentDescription(title); 253 } 254 mView.setImageResource(mControlsComponent.getTileImageId()); 255 mView.setImageTintList(Utils.getColorAttr(mContext, android.R.attr.textColorPrimary)); 256 final Drawable background = mView.getBackground(); 257 if (background != null) { 258 background.setTintList( 259 Utils.getColorAttr(mContext, com.android.internal.R.attr.colorSurface)); 260 } 261 } 262 263 @Nullable getControlsTitle()264 private String getControlsTitle() { 265 try { 266 return mContext.getString(mControlsComponent.getTileTitleId()); 267 } catch (Resources.NotFoundException e) { 268 return null; 269 } 270 } 271 onClickHomeControls(View v)272 private void onClickHomeControls(View v) { 273 if (DEBUG) Log.d(TAG, "home controls complication tapped"); 274 275 mUiEventLogger.log(DreamOverlayUiEvent.DREAM_HOME_CONTROLS_TAPPED); 276 277 final Intent intent = new Intent(mContext, ControlsActivity.class) 278 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK) 279 .putExtra(ControlsUiController.EXTRA_ANIMATE, true) 280 .putExtra(ControlsUiController.EXIT_TO_DREAM, true); 281 282 final ActivityTransitionAnimator.Controller controller = 283 v != null 284 ? ActivityTransitionAnimator.Controller.fromView(v, null /* cujType */) 285 : null; 286 if (mControlsComponent.getVisibility() == AVAILABLE) { 287 // Controls can be made visible. 288 mActivityStarter.startActivity(intent, true /* dismissShade */, controller, 289 true /* showOverLockscreenWhenLocked */); 290 } else if (mControlsComponent.getVisibility() == AVAILABLE_AFTER_UNLOCK) { 291 // Controls can be made visible only after device unlock. 292 mActivityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */, 293 controller); 294 } 295 } 296 } 297 } 298