1 /* 2 * Copyright (C) 2019 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.wm; 18 19 import android.annotation.Nullable; 20 import android.content.Context; 21 import android.content.res.Configuration; 22 import android.hardware.display.DisplayManager; 23 import android.os.Handler; 24 import android.os.RemoteException; 25 import android.util.Slog; 26 import android.util.SparseArray; 27 import android.view.Display; 28 import android.view.IDisplayWindowListener; 29 import android.view.IWindowManager; 30 31 import com.android.systemui.dagger.qualifiers.Main; 32 import com.android.systemui.wm.DisplayChangeController.OnDisplayChangingListener; 33 34 import java.util.ArrayList; 35 36 import javax.inject.Inject; 37 import javax.inject.Singleton; 38 39 /** 40 * This module deals with display rotations coming from WM. When WM starts a rotation: after it has 41 * frozen the screen, it will call into this class. This will then call all registered local 42 * controllers and give them a chance to queue up task changes to be applied synchronously with that 43 * rotation. 44 */ 45 @Singleton 46 public class DisplayController { 47 private static final String TAG = "DisplayController"; 48 49 private final Handler mHandler; 50 private final Context mContext; 51 private final IWindowManager mWmService; 52 private final DisplayChangeController mChangeController; 53 54 private final SparseArray<DisplayRecord> mDisplays = new SparseArray<>(); 55 private final ArrayList<OnDisplaysChangedListener> mDisplayChangedListeners = new ArrayList<>(); 56 57 /** 58 * Get's a display by id from DisplayManager. 59 */ getDisplay(int displayId)60 public Display getDisplay(int displayId) { 61 final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); 62 return displayManager.getDisplay(displayId); 63 } 64 65 private final IDisplayWindowListener mDisplayContainerListener = 66 new IDisplayWindowListener.Stub() { 67 @Override 68 public void onDisplayAdded(int displayId) { 69 mHandler.post(() -> { 70 synchronized (mDisplays) { 71 if (mDisplays.get(displayId) != null) { 72 return; 73 } 74 Display display = getDisplay(displayId); 75 if (display == null) { 76 // It's likely that the display is private to some app and thus not 77 // accessible by system-ui. 78 return; 79 } 80 DisplayRecord record = new DisplayRecord(); 81 record.mDisplayId = displayId; 82 record.mContext = (displayId == Display.DEFAULT_DISPLAY) ? mContext 83 : mContext.createDisplayContext(display); 84 record.mDisplayLayout = new DisplayLayout(record.mContext, display); 85 mDisplays.put(displayId, record); 86 for (int i = 0; i < mDisplayChangedListeners.size(); ++i) { 87 mDisplayChangedListeners.get(i).onDisplayAdded(displayId); 88 } 89 } 90 }); 91 } 92 93 @Override 94 public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { 95 mHandler.post(() -> { 96 synchronized (mDisplays) { 97 DisplayRecord dr = mDisplays.get(displayId); 98 if (dr == null) { 99 Slog.w(TAG, "Skipping Display Configuration change on non-added" 100 + " display."); 101 return; 102 } 103 Display display = getDisplay(displayId); 104 if (display == null) { 105 Slog.w(TAG, "Skipping Display Configuration change on invalid" 106 + " display. It may have been removed."); 107 return; 108 } 109 Context perDisplayContext = mContext; 110 if (displayId != Display.DEFAULT_DISPLAY) { 111 perDisplayContext = mContext.createDisplayContext(display); 112 } 113 dr.mContext = perDisplayContext.createConfigurationContext(newConfig); 114 dr.mDisplayLayout = new DisplayLayout(dr.mContext, display); 115 for (int i = 0; i < mDisplayChangedListeners.size(); ++i) { 116 mDisplayChangedListeners.get(i).onDisplayConfigurationChanged( 117 displayId, newConfig); 118 } 119 } 120 }); 121 } 122 123 @Override 124 public void onDisplayRemoved(int displayId) { 125 mHandler.post(() -> { 126 synchronized (mDisplays) { 127 if (mDisplays.get(displayId) == null) { 128 return; 129 } 130 for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) { 131 mDisplayChangedListeners.get(i).onDisplayRemoved(displayId); 132 } 133 mDisplays.remove(displayId); 134 } 135 }); 136 } 137 138 @Override 139 public void onFixedRotationStarted(int displayId, int newRotation) { 140 mHandler.post(() -> { 141 synchronized (mDisplays) { 142 if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) { 143 Slog.w(TAG, "Skipping onFixedRotationStarted on unknown" 144 + " display, displayId=" + displayId); 145 return; 146 } 147 for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) { 148 mDisplayChangedListeners.get(i).onFixedRotationStarted( 149 displayId, newRotation); 150 } 151 } 152 }); 153 } 154 155 @Override 156 public void onFixedRotationFinished(int displayId) { 157 mHandler.post(() -> { 158 synchronized (mDisplays) { 159 if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) { 160 Slog.w(TAG, "Skipping onFixedRotationFinished on unknown" 161 + " display, displayId=" + displayId); 162 return; 163 } 164 for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) { 165 mDisplayChangedListeners.get(i).onFixedRotationFinished(displayId); 166 } 167 } 168 }); 169 } 170 }; 171 172 @Inject DisplayController(Context context, @Main Handler mainHandler, IWindowManager wmService)173 public DisplayController(Context context, @Main Handler mainHandler, 174 IWindowManager wmService) { 175 mHandler = mainHandler; 176 mContext = context; 177 mWmService = wmService; 178 mChangeController = new DisplayChangeController(mHandler, mWmService); 179 try { 180 mWmService.registerDisplayWindowListener(mDisplayContainerListener); 181 } catch (RemoteException e) { 182 throw new RuntimeException("Unable to register hierarchy listener"); 183 } 184 } 185 186 /** 187 * Gets the DisplayLayout associated with a display. 188 */ getDisplayLayout(int displayId)189 public @Nullable DisplayLayout getDisplayLayout(int displayId) { 190 final DisplayRecord r = mDisplays.get(displayId); 191 return r != null ? r.mDisplayLayout : null; 192 } 193 194 /** 195 * Gets a display-specific context for a display. 196 */ getDisplayContext(int displayId)197 public @Nullable Context getDisplayContext(int displayId) { 198 final DisplayRecord r = mDisplays.get(displayId); 199 return r != null ? r.mContext : null; 200 } 201 202 /** 203 * Add a display window-container listener. It will get notified whenever a display's 204 * configuration changes or when displays are added/removed from the WM hierarchy. 205 */ addDisplayWindowListener(OnDisplaysChangedListener listener)206 public void addDisplayWindowListener(OnDisplaysChangedListener listener) { 207 synchronized (mDisplays) { 208 if (mDisplayChangedListeners.contains(listener)) { 209 return; 210 } 211 mDisplayChangedListeners.add(listener); 212 for (int i = 0; i < mDisplays.size(); ++i) { 213 listener.onDisplayAdded(mDisplays.keyAt(i)); 214 } 215 } 216 } 217 218 /** 219 * Remove a display window-container listener. 220 */ removeDisplayWindowListener(OnDisplaysChangedListener listener)221 public void removeDisplayWindowListener(OnDisplaysChangedListener listener) { 222 synchronized (mDisplays) { 223 mDisplayChangedListeners.remove(listener); 224 } 225 } 226 227 /** 228 * Adds a display rotation controller. 229 */ addDisplayChangingController(OnDisplayChangingListener controller)230 public void addDisplayChangingController(OnDisplayChangingListener controller) { 231 mChangeController.addRotationListener(controller); 232 } 233 234 /** 235 * Removes a display rotation controller. 236 */ removeDisplayChangingController(OnDisplayChangingListener controller)237 public void removeDisplayChangingController(OnDisplayChangingListener controller) { 238 mChangeController.removeRotationListener(controller); 239 } 240 241 private static class DisplayRecord { 242 int mDisplayId; 243 Context mContext; 244 DisplayLayout mDisplayLayout; 245 } 246 247 /** 248 * Gets notified when a display is added/removed to the WM hierarchy and when a display's 249 * window-configuration changes. 250 * 251 * @see IDisplayWindowListener 252 */ 253 public interface OnDisplaysChangedListener { 254 /** 255 * Called when a display has been added to the WM hierarchy. 256 */ onDisplayAdded(int displayId)257 default void onDisplayAdded(int displayId) {} 258 259 /** 260 * Called when a display's window-container configuration changes. 261 */ onDisplayConfigurationChanged(int displayId, Configuration newConfig)262 default void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {} 263 264 /** 265 * Called when a display is removed. 266 */ onDisplayRemoved(int displayId)267 default void onDisplayRemoved(int displayId) {} 268 269 /** 270 * Called when fixed rotation on a display is started. 271 */ onFixedRotationStarted(int displayId, int newRotation)272 default void onFixedRotationStarted(int displayId, int newRotation) {} 273 274 /** 275 * Called when fixed rotation on a display is finished. 276 */ onFixedRotationFinished(int displayId)277 default void onFixedRotationFinished(int displayId) {} 278 } 279 } 280