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.media.AudioSystem;
21 import android.os.Handler;
22 import android.util.Log;
23 import android.view.OrientationEventListener;
24 import android.view.Surface;
25 import android.view.WindowManager;
26 
27 import com.android.server.policy.WindowOrientationListener;
28 
29 /**
30  * Class to handle device rotation events for AudioService, and forward device rotation
31  * to the audio HALs through AudioSystem.
32  *
33  * The role of this class is to monitor device orientation changes, and upon rotation,
34  * verify the UI orientation. In case of a change, send the new orientation, in increments
35  * of 90deg, through AudioSystem.
36  *
37  * Note that even though we're responding to device orientation events, we always
38  * query the display rotation so audio stays in sync with video/dialogs. This is
39  * done with .getDefaultDisplay().getRotation() from WINDOW_SERVICE.
40  */
41 class RotationHelper {
42 
43     private static final String TAG = "AudioService.RotationHelper";
44 
45     private static AudioOrientationListener sOrientationListener;
46     private static AudioWindowOrientationListener sWindowOrientationListener;
47 
48     private static final Object sRotationLock = new Object();
49     private static int sDeviceRotation = Surface.ROTATION_0; // R/W synchronized on sRotationLock
50 
51     private static Context sContext;
52 
53     /**
54      * post conditions:
55      * - (sWindowOrientationListener != null) xor (sOrientationListener != null)
56      * - sWindowOrientationListener xor sOrientationListener is enabled
57      * - sContext != null
58      */
init(Context context, Handler handler)59     static void init(Context context, Handler handler) {
60         if (context == null) {
61             throw new IllegalArgumentException("Invalid null context");
62         }
63         sContext = context;
64         sWindowOrientationListener = new AudioWindowOrientationListener(context, handler);
65         sWindowOrientationListener.enable();
66         if (!sWindowOrientationListener.canDetectOrientation()) {
67             // cannot use com.android.server.policy.WindowOrientationListener, revert to public
68             // orientation API
69             Log.i(TAG, "Not using WindowOrientationListener, reverting to OrientationListener");
70             sWindowOrientationListener.disable();
71             sWindowOrientationListener = null;
72             sOrientationListener = new AudioOrientationListener(context);
73             sOrientationListener.enable();
74         }
75     }
76 
enable()77     static void enable() {
78         if (sWindowOrientationListener != null) {
79             sWindowOrientationListener.enable();
80         } else {
81             sOrientationListener.enable();
82         }
83         updateOrientation();
84     }
85 
disable()86     static void disable() {
87         if (sWindowOrientationListener != null) {
88             sWindowOrientationListener.disable();
89         } else {
90             sOrientationListener.disable();
91         }
92     }
93 
94     /**
95      * Query current display rotation and publish the change if any.
96      */
updateOrientation()97     static void updateOrientation() {
98         // Even though we're responding to device orientation events,
99         // use display rotation so audio stays in sync with video/dialogs
100         int newRotation = ((WindowManager) sContext.getSystemService(
101                 Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation();
102         synchronized(sRotationLock) {
103             if (newRotation != sDeviceRotation) {
104                 sDeviceRotation = newRotation;
105                 publishRotation(sDeviceRotation);
106             }
107         }
108     }
109 
publishRotation(int rotation)110     private static void publishRotation(int rotation) {
111         Log.v(TAG, "publishing device rotation =" + rotation + " (x90deg)");
112         switch (rotation) {
113             case Surface.ROTATION_0:
114                 AudioSystem.setParameters("rotation=0");
115                 break;
116             case Surface.ROTATION_90:
117                 AudioSystem.setParameters("rotation=90");
118                 break;
119             case Surface.ROTATION_180:
120                 AudioSystem.setParameters("rotation=180");
121                 break;
122             case Surface.ROTATION_270:
123                 AudioSystem.setParameters("rotation=270");
124                 break;
125             default:
126                 Log.e(TAG, "Unknown device rotation");
127         }
128     }
129 
130     /**
131      * Uses android.view.OrientationEventListener
132      */
133     final static class AudioOrientationListener extends OrientationEventListener {
AudioOrientationListener(Context context)134         AudioOrientationListener(Context context) {
135             super(context);
136         }
137 
138         @Override
onOrientationChanged(int orientation)139         public void onOrientationChanged(int orientation) {
140             updateOrientation();
141         }
142     }
143 
144     /**
145      * Uses com.android.server.policy.WindowOrientationListener
146      */
147     final static class AudioWindowOrientationListener extends WindowOrientationListener {
148         private static RotationCheckThread sRotationCheckThread;
149 
AudioWindowOrientationListener(Context context, Handler handler)150         AudioWindowOrientationListener(Context context, Handler handler) {
151             super(context, handler);
152         }
153 
onProposedRotationChanged(int rotation)154         public void onProposedRotationChanged(int rotation) {
155             updateOrientation();
156             if (sRotationCheckThread != null) {
157                 sRotationCheckThread.endCheck();
158             }
159             sRotationCheckThread = new RotationCheckThread();
160             sRotationCheckThread.beginCheck();
161         }
162     }
163 
164     /**
165      * When com.android.server.policy.WindowOrientationListener report an orientation change,
166      * the UI may not have rotated yet. This thread polls with gradually increasing delays
167      * the new orientation.
168      */
169     final static class RotationCheckThread extends Thread {
170         // how long to wait between each rotation check
171         private final int[] WAIT_TIMES_MS = { 10, 20, 50, 100, 100, 200, 200, 500 };
172         private int mWaitCounter;
173         private final Object mCounterLock = new Object();
174 
RotationCheckThread()175         RotationCheckThread() {
176             super("RotationCheck");
177         }
178 
beginCheck()179         void beginCheck() {
180             synchronized(mCounterLock) {
181                 mWaitCounter = 0;
182             }
183             try {
184                 start();
185             } catch (IllegalStateException e) { }
186         }
187 
endCheck()188         void endCheck() {
189             synchronized(mCounterLock) {
190                 mWaitCounter = WAIT_TIMES_MS.length;
191             }
192         }
193 
run()194         public void run() {
195             while (mWaitCounter < WAIT_TIMES_MS.length) {
196                 int waitTimeMs;
197                 synchronized(mCounterLock) {
198                     waitTimeMs = mWaitCounter < WAIT_TIMES_MS.length ?
199                             WAIT_TIMES_MS[mWaitCounter] : 0;
200                     mWaitCounter++;
201                 }
202                 try {
203                     if (waitTimeMs > 0) {
204                         sleep(waitTimeMs);
205                         updateOrientation();
206                     }
207                 } catch (InterruptedException e) { }
208             }
209         }
210     }
211 }