1 /*
2  * Copyright (C) 2023 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.server.wm;
18 
19 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
20 
21 import android.os.Binder;
22 import android.os.IBinder;
23 import android.os.RemoteException;
24 import android.util.Slog;
25 import android.view.IRotationWatcher;
26 
27 import java.io.PrintWriter;
28 import java.util.ArrayList;
29 
30 /** Manages the registration and event dispatch of {@link IRotationWatcher}. */
31 class RotationWatcherController {
32 
33     private final WindowManagerService mService;
34 
35     /** The watchers that monitor the applied rotation of display. */
36     private final ArrayList<DisplayRotationWatcher> mDisplayRotationWatchers = new ArrayList<>();
37 
38     /** The listeners that monitor the proposed rotation of the corresponding window container. */
39     private final ArrayList<ProposedRotationListener> mProposedRotationListeners =
40             new ArrayList<>();
41 
42     /** A quick look up which can be checked without WM lock. */
43     private volatile boolean mHasProposedRotationListeners;
44 
RotationWatcherController(WindowManagerService wms)45     RotationWatcherController(WindowManagerService wms) {
46         mService = wms;
47     }
48 
registerDisplayRotationWatcher(IRotationWatcher watcher, int displayId)49     void registerDisplayRotationWatcher(IRotationWatcher watcher, int displayId) {
50         final IBinder watcherBinder = watcher.asBinder();
51         for (int i = mDisplayRotationWatchers.size() - 1; i >= 0; i--) {
52             if (watcherBinder == mDisplayRotationWatchers.get(i).mWatcher.asBinder()) {
53                 throw new IllegalArgumentException("Registering existed rotation watcher");
54             }
55         }
56         register(watcherBinder, new DisplayRotationWatcher(mService, watcher, displayId),
57                 mDisplayRotationWatchers);
58     }
59 
registerProposedRotationListener(IRotationWatcher listener, IBinder contextToken)60     void registerProposedRotationListener(IRotationWatcher listener, IBinder contextToken) {
61         final IBinder listenerBinder = listener.asBinder();
62         for (int i = mProposedRotationListeners.size() - 1; i >= 0; i--) {
63             final ProposedRotationListener watcher = mProposedRotationListeners.get(i);
64             if (contextToken == watcher.mToken || listenerBinder == watcher.mWatcher.asBinder()) {
65                 Slog.w(TAG_WM, "Register rotation listener to a registered token, uid="
66                         + Binder.getCallingUid());
67                 return;
68             }
69         }
70         register(listenerBinder, new ProposedRotationListener(mService, listener, contextToken),
71                 mProposedRotationListeners);
72         mHasProposedRotationListeners = !mProposedRotationListeners.isEmpty();
73     }
74 
register(IBinder watcherBinder, T watcher, ArrayList<T> watcherList)75     private static <T extends RotationWatcher> void register(IBinder watcherBinder, T watcher,
76             ArrayList<T> watcherList) {
77         try {
78             watcherBinder.linkToDeath(watcher, 0);
79             watcherList.add(watcher);
80         } catch (RemoteException e) {
81             // Client died, no cleanup needed.
82         }
83     }
84 
unregister(IRotationWatcher watcher, ArrayList<T> watcherList)85     private static <T extends RotationWatcher> boolean unregister(IRotationWatcher watcher,
86             ArrayList<T> watcherList) {
87         final IBinder watcherBinder = watcher.asBinder();
88         for (int i = watcherList.size() - 1; i >= 0; i--) {
89             final RotationWatcher rotationWatcher = watcherList.get(i);
90             if (watcherBinder != rotationWatcher.mWatcher.asBinder()) {
91                 continue;
92             }
93             watcherList.remove(i);
94             rotationWatcher.unlinkToDeath();
95             return true;
96         }
97         return false;
98     }
99 
removeRotationWatcher(IRotationWatcher watcher)100     void removeRotationWatcher(IRotationWatcher watcher) {
101         final boolean removed = unregister(watcher, mProposedRotationListeners);
102         if (removed) {
103             mHasProposedRotationListeners = !mProposedRotationListeners.isEmpty();
104         } else {
105             // The un-registration shares the same interface, so look up the watchers as well.
106             unregister(watcher, mDisplayRotationWatchers);
107         }
108     }
109 
110     /** Called when the new rotation of display is applied. */
dispatchDisplayRotationChange(int displayId, int rotation)111     void dispatchDisplayRotationChange(int displayId, int rotation) {
112         for (int i = mDisplayRotationWatchers.size() - 1; i >= 0; i--) {
113             final DisplayRotationWatcher rotationWatcher = mDisplayRotationWatchers.get(i);
114             if (rotationWatcher.mDisplayId == displayId) {
115                 rotationWatcher.notifyRotation(rotation);
116             }
117         }
118     }
119 
120     /** Called when the window orientation listener has a new proposed rotation. */
dispatchProposedRotation(DisplayContent dc, int rotation)121     void dispatchProposedRotation(DisplayContent dc, int rotation) {
122         for (int i = mProposedRotationListeners.size() - 1; i >= 0; i--) {
123             final ProposedRotationListener listener = mProposedRotationListeners.get(i);
124             final WindowContainer<?> wc = getAssociatedWindowContainer(listener.mToken);
125             if (wc != null) {
126                 if (wc.mDisplayContent == dc) {
127                     listener.notifyRotation(rotation);
128                 }
129             } else {
130                 // Unregister if the associated window container is gone.
131                 mProposedRotationListeners.remove(i);
132                 mHasProposedRotationListeners = !mProposedRotationListeners.isEmpty();
133                 listener.unlinkToDeath();
134             }
135         }
136     }
137 
hasProposedRotationListeners()138     boolean hasProposedRotationListeners() {
139         return mHasProposedRotationListeners;
140     }
141 
getAssociatedWindowContainer(IBinder contextToken)142     WindowContainer<?> getAssociatedWindowContainer(IBinder contextToken) {
143         final WindowContainer<?> wc = ActivityRecord.forTokenLocked(contextToken);
144         if (wc != null) {
145             return wc;
146         }
147         return mService.mWindowContextListenerController.getContainer(contextToken);
148     }
149 
dump(PrintWriter pw)150     void dump(PrintWriter pw) {
151         if (!mDisplayRotationWatchers.isEmpty()) {
152             pw.print("  mDisplayRotationWatchers: [");
153             for (int i = mDisplayRotationWatchers.size() - 1; i >= 0; i--) {
154                 pw.print(' ');
155                 final DisplayRotationWatcher watcher = mDisplayRotationWatchers.get(i);
156                 pw.print(watcher.mOwnerUid);
157                 pw.print("->");
158                 pw.print(watcher.mDisplayId);
159             }
160             pw.println(']');
161         }
162         if (!mProposedRotationListeners.isEmpty()) {
163             pw.print("  mProposedRotationListeners: [");
164             for (int i = mProposedRotationListeners.size() - 1; i >= 0; i--) {
165                 pw.print(' ');
166                 final ProposedRotationListener listener = mProposedRotationListeners.get(i);
167                 pw.print(listener.mOwnerUid);
168                 pw.print("->");
169                 pw.print(getAssociatedWindowContainer(listener.mToken));
170             }
171             pw.println(']');
172         }
173     }
174 
175     /** Reports new applied rotation of a display. */
176     private static class DisplayRotationWatcher extends RotationWatcher {
177         final int mDisplayId;
178 
DisplayRotationWatcher(WindowManagerService wms, IRotationWatcher watcher, int displayId)179         DisplayRotationWatcher(WindowManagerService wms, IRotationWatcher watcher, int displayId) {
180             super(wms, watcher);
181             mDisplayId = displayId;
182         }
183     }
184 
185     /** Reports proposed rotation of a window token. */
186     private static class ProposedRotationListener extends RotationWatcher {
187         final IBinder mToken;
188 
ProposedRotationListener(WindowManagerService wms, IRotationWatcher watcher, IBinder token)189         ProposedRotationListener(WindowManagerService wms, IRotationWatcher watcher,
190                 IBinder token) {
191             super(wms, watcher);
192             mToken = token;
193         }
194     }
195 
196     private static class RotationWatcher implements IBinder.DeathRecipient {
197         final WindowManagerService mWms;
198         final IRotationWatcher mWatcher;
199         final int mOwnerUid = Binder.getCallingUid();
200 
RotationWatcher(WindowManagerService wms, IRotationWatcher watcher)201         RotationWatcher(WindowManagerService wms, IRotationWatcher watcher) {
202             mWms = wms;
203             mWatcher = watcher;
204         }
205 
notifyRotation(int rotation)206         void notifyRotation(int rotation) {
207             try {
208                 mWatcher.onRotationChanged(rotation);
209             } catch (RemoteException ignored) {
210                 // Let binderDied() remove this.
211             }
212         }
213 
unlinkToDeath()214         void unlinkToDeath() {
215             mWatcher.asBinder().unlinkToDeath(this, 0);
216         }
217 
218         @Override
binderDied()219         public void binderDied() {
220             mWms.removeRotationWatcher(mWatcher);
221         }
222     }
223 }
224