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