1 /*
2  * Copyright (C) 2015 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.audio;
18 
19 import android.content.Context;
20 import android.hardware.devicestate.DeviceStateManager;
21 import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
22 import android.hardware.display.DisplayManager;
23 import android.hardware.display.DisplayManagerGlobal;
24 import android.os.Handler;
25 import android.os.HandlerExecutor;
26 import android.util.Log;
27 import android.view.Display;
28 import android.view.Surface;
29 
30 import java.util.function.Consumer;
31 
32 /**
33  * Class to handle device rotation events for AudioService, and forward device rotation
34  * and folded state to the audio HALs through AudioSystem.
35  *
36  * The role of this class is to monitor device orientation changes, and upon rotation,
37  * verify the UI orientation. In case of a change, send the new orientation, in increments
38  * of 90deg, through AudioSystem.
39  *
40  * Another role of this class is to track device folded state changes. In case of a
41  * change, send the new folded state through AudioSystem.
42  *
43  * Note that even though we're responding to device orientation events, we always
44  * query the display rotation so audio stays in sync with video/dialogs. This is
45  * done with .getDefaultDisplay().getRotation() from WINDOW_SERVICE.
46  *
47  * We also monitor current display ID and audio is able to know which display is active.
48  */
49 class RotationHelper {
50 
51     private static final String TAG = "AudioService.RotationHelper";
52 
53     private static final boolean DEBUG_ROTATION = false;
54 
55     private static AudioDisplayListener sDisplayListener;
56     private static FoldStateListener sFoldStateListener;
57     /** callback to send rotation updates to AudioSystem */
58     private static Consumer<Integer> sRotationCallback;
59     /** callback to send folded state updates to AudioSystem */
60     private static Consumer<Boolean> sFoldStateCallback;
61 
62     private static final Object sRotationLock = new Object();
63     private static final Object sFoldStateLock = new Object();
64     private static Integer sRotation = null; // R/W synchronized on sRotationLock
65     private static Boolean sFoldState = null; // R/W synchronized on sFoldStateLock
66 
67     private static Context sContext;
68     private static Handler sHandler;
69 
70     /**
71      * post conditions:
72      * - sDisplayListener != null
73      * - sContext != null
74      */
init(Context context, Handler handler, Consumer<Integer> rotationCallback, Consumer<Boolean> foldStateCallback)75     static void init(Context context, Handler handler,
76             Consumer<Integer> rotationCallback, Consumer<Boolean> foldStateCallback) {
77         if (context == null) {
78             throw new IllegalArgumentException("Invalid null context");
79         }
80         sContext = context;
81         sHandler = handler;
82         sDisplayListener = new AudioDisplayListener();
83         sFoldStateListener = new FoldStateListener(sContext, RotationHelper::updateFoldState);
84         sRotationCallback = rotationCallback;
85         sFoldStateCallback = foldStateCallback;
86         enable();
87     }
88 
enable()89     static void enable() {
90         ((DisplayManager) sContext.getSystemService(Context.DISPLAY_SERVICE))
91                 .registerDisplayListener(sDisplayListener, sHandler);
92         updateOrientation();
93 
94         sContext.getSystemService(DeviceStateManager.class)
95                 .registerCallback(new HandlerExecutor(sHandler), sFoldStateListener);
96     }
97 
disable()98     static void disable() {
99         ((DisplayManager) sContext.getSystemService(Context.DISPLAY_SERVICE))
100                 .unregisterDisplayListener(sDisplayListener);
101         sContext.getSystemService(DeviceStateManager.class)
102                 .unregisterCallback(sFoldStateListener);
103     }
104 
105     /**
106      * Query current display rotation and publish the change if any.
107      */
updateOrientation()108     static void updateOrientation() {
109         // Even though we're responding to device orientation events,
110         // use display rotation so audio stays in sync with video/dialogs
111         // TODO(b/148458001): Support multi-display
112         int newRotation = DisplayManagerGlobal.getInstance()
113                 .getDisplayInfo(Display.DEFAULT_DISPLAY).rotation;
114         synchronized(sRotationLock) {
115             if (sRotation == null || sRotation != newRotation) {
116                 sRotation = newRotation;
117                 publishRotation(sRotation);
118             }
119         }
120     }
121 
publishRotation(int rotation)122     private static void publishRotation(int rotation) {
123         if (DEBUG_ROTATION) {
124             Log.i(TAG, "publishing device rotation =" + rotation + " (x90deg)");
125         }
126         int rotationDegrees;
127         switch (rotation) {
128             case Surface.ROTATION_0:
129                 rotationDegrees = 0;
130                 break;
131             case Surface.ROTATION_90:
132                 rotationDegrees = 90;
133                 break;
134             case Surface.ROTATION_180:
135                 rotationDegrees = 180;
136                 break;
137             case Surface.ROTATION_270:
138                 rotationDegrees = 270;
139                 break;
140             default:
141                 Log.e(TAG, "Unknown device rotation");
142                 rotationDegrees = -1;
143         }
144         if (rotationDegrees != -1) {
145             sRotationCallback.accept(rotationDegrees);
146         }
147     }
148 
149     /**
150      * publish the change of device folded state if any.
151      */
updateFoldState(boolean foldState)152     static void updateFoldState(boolean foldState) {
153         synchronized (sFoldStateLock) {
154             if (sFoldState == null || sFoldState != foldState) {
155                 sFoldState = foldState;
156                 sFoldStateCallback.accept(foldState);
157             }
158         }
159     }
160 
161     /**
162      *  forceUpdate is called when audioserver restarts.
163      */
forceUpdate()164     static void forceUpdate() {
165         synchronized (sRotationLock) {
166             sRotation = null;
167         }
168         updateOrientation(); // We will get at least one orientation update now.
169         synchronized (sFoldStateLock) {
170             if (sFoldState  != null) {
171                 sFoldStateCallback.accept(sFoldState);
172             }
173         }
174     }
175 
176     /**
177      * Uses android.hardware.display.DisplayManager.DisplayListener
178      */
179     final static class AudioDisplayListener implements DisplayManager.DisplayListener {
180 
181         @Override
onDisplayAdded(int displayId)182         public void onDisplayAdded(int displayId) {
183         }
184 
185         @Override
onDisplayRemoved(int displayId)186         public void onDisplayRemoved(int displayId) {
187         }
188 
189         @Override
onDisplayChanged(int displayId)190         public void onDisplayChanged(int displayId) {
191             if (DEBUG_ROTATION) {
192                 Log.i(TAG, "onDisplayChanged diplayId:" + displayId);
193             }
194             updateOrientation();
195         }
196     }
197 }
198