1 /* 2 * Copyright (C) 2021 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.car.cluster; 18 19 import static android.car.cluster.ClusterHomeManager.CONFIG_DISPLAY_BOUNDS; 20 import static android.car.cluster.ClusterHomeManager.CONFIG_DISPLAY_ID; 21 22 import android.car.Car; 23 import android.car.cluster.ClusterHomeManager; 24 import android.car.cluster.ClusterState; 25 import android.content.Context; 26 import android.graphics.Rect; 27 import android.util.Slog; 28 import android.view.Display; 29 import android.view.SurfaceControl; 30 import android.window.DisplayAreaInfo; 31 import android.window.WindowContainerToken; 32 import android.window.WindowContainerTransaction; 33 34 import com.android.systemui.SystemUI; 35 import com.android.systemui.car.CarServiceProvider; 36 import com.android.systemui.dagger.SysUISingleton; 37 import com.android.systemui.dagger.qualifiers.Main; 38 import com.android.wm.shell.RootTaskDisplayAreaOrganizer; 39 40 import java.util.Optional; 41 import java.util.concurrent.Executor; 42 43 import javax.inject.Inject; 44 45 /*** 46 * Controls the RootTDA of cluster display per CLUSTER_DISPLAY_STATE message. 47 */ 48 @SysUISingleton 49 public class ClusterDisplayController extends SystemUI { 50 private static final String TAG = ClusterDisplayController.class.getSimpleName(); 51 private static final boolean DBG = false; 52 53 private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer; 54 private final CarServiceProvider mCarServiceProvider; 55 private final Executor mMainExecutor; 56 57 private ClusterHomeManager mClusterHomeManager; 58 private WindowContainerToken mRootTDAToken; 59 private ClusterState mClusterState; 60 61 @Inject ClusterDisplayController(Context context, Optional<RootTaskDisplayAreaOrganizer> rootTDAOrganizer, CarServiceProvider carServiceProvider, @Main Executor mainExecutor)62 public ClusterDisplayController(Context context, 63 Optional<RootTaskDisplayAreaOrganizer> rootTDAOrganizer, 64 CarServiceProvider carServiceProvider, @Main Executor mainExecutor) { 65 super(context); 66 mRootTDAOrganizer = rootTDAOrganizer.orElse(null); 67 mCarServiceProvider = carServiceProvider; 68 mMainExecutor = mainExecutor; 69 } 70 71 @Override start()72 public void start() { 73 if (mRootTDAOrganizer == null) { 74 Slog.w(TAG, "ClusterDisplayController is disabled because of no " 75 + "RootTaskDisplayAreaOrganizer"); 76 return; 77 } 78 mCarServiceProvider.addListener(mCarServiceOnConnectedListener); 79 } 80 81 private final CarServiceProvider.CarServiceOnConnectedListener mCarServiceOnConnectedListener = 82 new CarServiceProvider.CarServiceOnConnectedListener() { 83 @Override 84 public void onConnected(Car car) { 85 mClusterHomeManager = (ClusterHomeManager) car.getCarManager(Car.CLUSTER_HOME_SERVICE); 86 if (mClusterHomeManager == null) { 87 Slog.w(TAG, "ClusterHomeManager is disabled"); 88 return; 89 } 90 mClusterHomeManager.registerClusterStateListener(mMainExecutor, mClusterStateListener); 91 92 mClusterState = mClusterHomeManager.getClusterState(); 93 if (mClusterState.displayId != Display.INVALID_DISPLAY) { 94 mRootTDAOrganizer.registerListener(mClusterState.displayId, mRootTDAListener); 95 } 96 } 97 }; 98 99 private final ClusterHomeManager.ClusterStateListener mClusterStateListener = 100 new ClusterHomeManager.ClusterStateListener() { 101 @Override 102 public void onClusterStateChanged(ClusterState state, int changes) { 103 // Need to update mClusterState first, since mClusterState will be referenced during 104 // registerListener() -> onDisplayAreaAppeared() -> resizeTDA(). 105 mClusterState = state; 106 if (DBG) { 107 Slog.d(TAG, "onClusterStateChanged: changes=" + changes 108 + ", displayId=" + state.displayId); 109 } 110 if ((changes & CONFIG_DISPLAY_ID) != 0) { 111 if (state.displayId != Display.INVALID_DISPLAY) { 112 mRootTDAOrganizer.registerListener(state.displayId, mRootTDAListener); 113 } else { 114 mRootTDAOrganizer.unregisterListener(mRootTDAListener); 115 } 116 } 117 if ((changes & CONFIG_DISPLAY_BOUNDS) != 0 && mRootTDAToken != null) { 118 resizeTDA(mRootTDAToken, state.bounds, state.displayId); 119 } 120 } 121 }; 122 123 private final RootTaskDisplayAreaOrganizer.RootTaskDisplayAreaListener mRootTDAListener = 124 new RootTaskDisplayAreaOrganizer.RootTaskDisplayAreaListener() { 125 @Override 126 public void onDisplayAreaAppeared(DisplayAreaInfo displayAreaInfo) { 127 if (DBG) Slog.d(TAG, "onDisplayAreaAppeared: " + displayAreaInfo); 128 if (mClusterState != null && mClusterState.displayId != Display.INVALID_DISPLAY) { 129 resizeTDA(displayAreaInfo.token, mClusterState.bounds, mClusterState.displayId); 130 } 131 mRootTDAToken = displayAreaInfo.token; 132 } 133 134 @Override 135 public void onDisplayAreaVanished(DisplayAreaInfo displayAreaInfo) { 136 if (DBG) Slog.d(TAG, "onDisplayAreaVanished: " + displayAreaInfo); 137 mRootTDAToken = null; 138 } 139 140 @Override 141 public void onDisplayAreaInfoChanged(DisplayAreaInfo displayAreaInfo) { 142 if (DBG) Slog.d(TAG, "onDisplayAreaInfoChanged: " + displayAreaInfo); 143 mRootTDAToken = displayAreaInfo.token; 144 } 145 }; 146 resizeTDA(WindowContainerToken token, Rect bounds, int displayId)147 private void resizeTDA(WindowContainerToken token, Rect bounds, int displayId) { 148 if (DBG) { 149 Slog.d(TAG, "resizeTDA: token=" + token + ", bounds=" + bounds 150 + ", displayId=" + displayId); 151 } 152 WindowContainerTransaction wct = new WindowContainerTransaction(); 153 wct.setBounds(token, bounds); 154 wct.setAppBounds(token, bounds); 155 mRootTDAOrganizer.applyTransaction(wct); 156 157 SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); 158 mRootTDAOrganizer.setPosition(tx, displayId, bounds.left, bounds.top); 159 tx.apply(); 160 } 161 } 162