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.ComplicationHostViewModule.SCOPED_COMPLICATIONS_LAYOUT;
20 import static com.android.systemui.complication.dagger.ComplicationModule.SCOPED_COMPLICATIONS_MODEL;
21 
22 import android.graphics.Rect;
23 import android.graphics.Region;
24 import android.os.Debug;
25 import android.os.UserHandle;
26 import android.provider.Settings;
27 import android.util.Log;
28 import android.view.View;
29 
30 import androidx.constraintlayout.widget.ConstraintLayout;
31 import androidx.lifecycle.LifecycleOwner;
32 
33 import com.android.internal.annotations.VisibleForTesting;
34 import com.android.systemui.dreams.DreamOverlayStateController;
35 import com.android.systemui.util.ViewController;
36 import com.android.systemui.util.settings.SecureSettings;
37 
38 import java.util.Collection;
39 import java.util.HashMap;
40 import java.util.List;
41 import java.util.stream.Collectors;
42 
43 import javax.inject.Inject;
44 import javax.inject.Named;
45 
46 /**
47  * The {@link ComplicationHostViewController} is responsible for displaying complications within
48  * a given container. It monitors the available {@link Complication} instances from
49  * {@link com.android.systemui.dreams.DreamOverlayStateController} and inserts/removes them through
50  * a {@link ComplicationLayoutEngine}.
51  */
52 public class ComplicationHostViewController extends ViewController<ConstraintLayout> {
53     private static final String TAG = "ComplicationHostVwCtrl";
54     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
55 
56     private final ComplicationLayoutEngine mLayoutEngine;
57     private final DreamOverlayStateController mDreamOverlayStateController;
58     private final LifecycleOwner mLifecycleOwner;
59     private final ComplicationCollectionViewModel mComplicationCollectionViewModel;
60     private final HashMap<ComplicationId, Complication.ViewHolder> mComplications = new HashMap<>();
61     @VisibleForTesting
62     boolean mIsAnimationEnabled;
63 
64     @Inject
ComplicationHostViewController( @amedSCOPED_COMPLICATIONS_LAYOUT) ConstraintLayout view, ComplicationLayoutEngine layoutEngine, DreamOverlayStateController dreamOverlayStateController, LifecycleOwner lifecycleOwner, @Named(SCOPED_COMPLICATIONS_MODEL) ComplicationCollectionViewModel viewModel, SecureSettings secureSettings)65     protected ComplicationHostViewController(
66             @Named(SCOPED_COMPLICATIONS_LAYOUT) ConstraintLayout view,
67             ComplicationLayoutEngine layoutEngine,
68             DreamOverlayStateController dreamOverlayStateController,
69             LifecycleOwner lifecycleOwner,
70             @Named(SCOPED_COMPLICATIONS_MODEL) ComplicationCollectionViewModel viewModel,
71             SecureSettings secureSettings) {
72         super(view);
73         mLayoutEngine = layoutEngine;
74         mLifecycleOwner = lifecycleOwner;
75         mComplicationCollectionViewModel = viewModel;
76         mDreamOverlayStateController = dreamOverlayStateController;
77 
78         // Whether animations are enabled.
79         mIsAnimationEnabled = secureSettings.getFloatForUser(
80                 Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f, UserHandle.USER_CURRENT) != 0.0f;
81     }
82 
83     @Override
onInit()84     protected void onInit() {
85         super.onInit();
86         mComplicationCollectionViewModel.getComplications().observe(mLifecycleOwner,
87                 complicationViewModels -> updateComplications(complicationViewModels));
88     }
89 
90     /**
91      * Returns the region in display space occupied by complications. Touches in this region
92      * (composed of a collection of individual rectangular regions) should be directed to the
93      * complications rather than the region underneath.
94      */
getTouchRegions()95     public Region getTouchRegions() {
96         final Region region = new Region();
97         final Rect rect = new Rect();
98         final int childCount = mView.getChildCount();
99         for (int i = 0; i < childCount; i++) {
100             View child = mView.getChildAt(i);
101             if (child.getGlobalVisibleRect(rect)) {
102                 region.op(rect, Region.Op.UNION);
103             }
104         }
105 
106         return region;
107     }
108 
updateComplications(Collection<ComplicationViewModel> complications)109     private void updateComplications(Collection<ComplicationViewModel> complications) {
110         if (DEBUG) {
111             Log.d(TAG, "updateComplications called. Callers = " + Debug.getCallers(25));
112             Log.d(TAG, "    mComplications = " + mComplications.toString());
113             Log.d(TAG, "    complications = " + complications.toString());
114         }
115         final Collection<ComplicationId> ids = complications.stream()
116                 .map(complicationViewModel -> complicationViewModel.getId())
117                 .collect(Collectors.toSet());
118 
119         final Collection<ComplicationId> removedComplicationIds =
120                 mComplications.keySet().stream()
121                         .filter(complicationId -> !ids.contains(complicationId))
122                         .collect(Collectors.toSet());
123 
124         // Trim removed complications
125         removedComplicationIds.forEach(complicationId -> {
126             mLayoutEngine.removeComplication(complicationId);
127             mComplications.remove(complicationId);
128         });
129 
130         // Add new complications
131         final Collection<ComplicationViewModel> newComplications = complications
132                 .stream()
133                 .filter(complication -> !mComplications.containsKey(complication.getId()))
134                 .collect(Collectors.toSet());
135 
136         newComplications
137                 .forEach(complication -> {
138                     final ComplicationId id = complication.getId();
139                     final Complication.ViewHolder viewHolder = complication.getComplication()
140                             .createView(complication);
141 
142                     final View view = viewHolder.getView();
143 
144                     if (view == null) {
145                         Log.e(TAG, "invalid complication view. null view supplied by ViewHolder");
146                         return;
147                     }
148 
149                     // Complications to be added before dream entry animations are finished are set
150                     // to invisible and are animated in.
151                     if (!mDreamOverlayStateController.areEntryAnimationsFinished()
152                             && mIsAnimationEnabled) {
153                         view.setVisibility(View.INVISIBLE);
154                     }
155                     mComplications.put(id, viewHolder);
156                     if (view.getParent() != null) {
157                         Log.e(TAG, "View for complication "
158                                 + complication.getComplication().getClass()
159                                 + " already has a parent. Make sure not to reuse complication "
160                                 + "views!");
161                     }
162                     mLayoutEngine.addComplication(id, view,
163                             viewHolder.getLayoutParams(), viewHolder.getCategory());
164                 });
165     }
166 
167     @Override
onViewAttached()168     protected void onViewAttached() {
169     }
170 
171     @Override
onViewDetached()172     protected void onViewDetached() {
173     }
174 
175     /**
176      * Exposes the associated {@link View}. Since this {@link View} is instantiated through dagger
177      * in the {@link ComplicationHostViewController} constructor, the
178      * {@link ComplicationHostViewController} is responsible for surfacing it so that it can be
179      * included in the parent view hierarchy.
180      */
getView()181     public View getView() {
182         return mView;
183     }
184 
185     /**
186      * Gets an unordered list of all the views at a particular position.
187      */
getViewsAtPosition(@omplicationLayoutParams.Position int position)188     public List<View> getViewsAtPosition(@ComplicationLayoutParams.Position int position) {
189         return mLayoutEngine.getViewsAtPosition(position);
190     }
191 }
192