/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.complication; import static com.android.systemui.complication.dagger.ComplicationHostViewModule.SCOPED_COMPLICATIONS_LAYOUT; import static com.android.systemui.complication.dagger.ComplicationModule.SCOPED_COMPLICATIONS_MODEL; import android.graphics.Rect; import android.graphics.Region; import android.os.Debug; import android.os.UserHandle; import android.provider.Settings; import android.util.Log; import android.view.View; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.lifecycle.LifecycleOwner; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.util.ViewController; import com.android.systemui.util.settings.SecureSettings; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Named; /** * The {@link ComplicationHostViewController} is responsible for displaying complications within * a given container. It monitors the available {@link Complication} instances from * {@link com.android.systemui.dreams.DreamOverlayStateController} and inserts/removes them through * a {@link ComplicationLayoutEngine}. */ public class ComplicationHostViewController extends ViewController { private static final String TAG = "ComplicationHostVwCtrl"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final ComplicationLayoutEngine mLayoutEngine; private final DreamOverlayStateController mDreamOverlayStateController; private final LifecycleOwner mLifecycleOwner; private final ComplicationCollectionViewModel mComplicationCollectionViewModel; private final HashMap mComplications = new HashMap<>(); @VisibleForTesting boolean mIsAnimationEnabled; @Inject protected ComplicationHostViewController( @Named(SCOPED_COMPLICATIONS_LAYOUT) ConstraintLayout view, ComplicationLayoutEngine layoutEngine, DreamOverlayStateController dreamOverlayStateController, LifecycleOwner lifecycleOwner, @Named(SCOPED_COMPLICATIONS_MODEL) ComplicationCollectionViewModel viewModel, SecureSettings secureSettings) { super(view); mLayoutEngine = layoutEngine; mLifecycleOwner = lifecycleOwner; mComplicationCollectionViewModel = viewModel; mDreamOverlayStateController = dreamOverlayStateController; // Whether animations are enabled. mIsAnimationEnabled = secureSettings.getFloatForUser( Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f, UserHandle.USER_CURRENT) != 0.0f; } @Override protected void onInit() { super.onInit(); mComplicationCollectionViewModel.getComplications().observe(mLifecycleOwner, complicationViewModels -> updateComplications(complicationViewModels)); } /** * Returns the region in display space occupied by complications. Touches in this region * (composed of a collection of individual rectangular regions) should be directed to the * complications rather than the region underneath. */ public Region getTouchRegions() { final Region region = new Region(); final Rect rect = new Rect(); final int childCount = mView.getChildCount(); for (int i = 0; i < childCount; i++) { View child = mView.getChildAt(i); if (child.getGlobalVisibleRect(rect)) { region.op(rect, Region.Op.UNION); } } return region; } private void updateComplications(Collection complications) { if (DEBUG) { Log.d(TAG, "updateComplications called. Callers = " + Debug.getCallers(25)); Log.d(TAG, " mComplications = " + mComplications.toString()); Log.d(TAG, " complications = " + complications.toString()); } final Collection ids = complications.stream() .map(complicationViewModel -> complicationViewModel.getId()) .collect(Collectors.toSet()); final Collection removedComplicationIds = mComplications.keySet().stream() .filter(complicationId -> !ids.contains(complicationId)) .collect(Collectors.toSet()); // Trim removed complications removedComplicationIds.forEach(complicationId -> { mLayoutEngine.removeComplication(complicationId); mComplications.remove(complicationId); }); // Add new complications final Collection newComplications = complications .stream() .filter(complication -> !mComplications.containsKey(complication.getId())) .collect(Collectors.toSet()); newComplications .forEach(complication -> { final ComplicationId id = complication.getId(); final Complication.ViewHolder viewHolder = complication.getComplication() .createView(complication); final View view = viewHolder.getView(); if (view == null) { Log.e(TAG, "invalid complication view. null view supplied by ViewHolder"); return; } // Complications to be added before dream entry animations are finished are set // to invisible and are animated in. if (!mDreamOverlayStateController.areEntryAnimationsFinished() && mIsAnimationEnabled) { view.setVisibility(View.INVISIBLE); } mComplications.put(id, viewHolder); if (view.getParent() != null) { Log.e(TAG, "View for complication " + complication.getComplication().getClass() + " already has a parent. Make sure not to reuse complication " + "views!"); } mLayoutEngine.addComplication(id, view, viewHolder.getLayoutParams(), viewHolder.getCategory()); }); } @Override protected void onViewAttached() { } @Override protected void onViewDetached() { } /** * Exposes the associated {@link View}. Since this {@link View} is instantiated through dagger * in the {@link ComplicationHostViewController} constructor, the * {@link ComplicationHostViewController} is responsible for surfacing it so that it can be * included in the parent view hierarchy. */ public View getView() { return mView; } /** * Gets an unordered list of all the views at a particular position. */ public List getViewsAtPosition(@ComplicationLayoutParams.Position int position) { return mLayoutEngine.getViewsAtPosition(position); } }