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