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
17package com.android.server.audio;
18
19import android.content.Context;
20import android.media.AudioSystem;
21import android.os.Handler;
22import android.util.Log;
23import android.view.OrientationEventListener;
24import android.view.Surface;
25import android.view.WindowManager;
26
27import 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 */
41class 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     */
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
77    static void enable() {
78        if (sWindowOrientationListener != null) {
79            sWindowOrientationListener.enable();
80        } else {
81            sOrientationListener.enable();
82        }
83        updateOrientation();
84    }
85
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     */
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
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 {
134        AudioOrientationListener(Context context) {
135            super(context);
136        }
137
138        @Override
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
150        AudioWindowOrientationListener(Context context, Handler handler) {
151            super(context, handler);
152        }
153
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
175        RotationCheckThread() {
176            super("RotationCheck");
177        }
178
179        void beginCheck() {
180            synchronized(mCounterLock) {
181                mWaitCounter = 0;
182            }
183            try {
184                start();
185            } catch (IllegalStateException e) { }
186        }
187
188        void endCheck() {
189            synchronized(mCounterLock) {
190                mWaitCounter = WAIT_TIMES_MS.length;
191            }
192        }
193
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}