• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.telecom;
18 
19 import android.app.UiModeManager;
20 import android.content.BroadcastReceiver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.content.res.Configuration;
25 import android.hardware.Sensor;
26 import android.hardware.SensorEvent;
27 import android.hardware.SensorEventListener;
28 import android.hardware.SensorManager;
29 import android.telecom.Log;
30 
31 import java.util.Set;
32 import java.util.concurrent.CopyOnWriteArraySet;
33 import java.util.concurrent.CountDownLatch;
34 import java.util.concurrent.TimeUnit;
35 import java.util.concurrent.atomic.AtomicBoolean;
36 
37 /**
38  * Provides various system states to the rest of the telecom codebase.
39  */
40 public class SystemStateHelper {
41     public static interface SystemStateListener {
onCarModeChanged(boolean isCarMode)42         public void onCarModeChanged(boolean isCarMode);
43     }
44 
45     private final Context mContext;
46     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
47         @Override
48         public void onReceive(Context context, Intent intent) {
49             Log.startSession("SSP.oR");
50             try {
51                 String action = intent.getAction();
52                 if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(action)) {
53                     onEnterCarMode();
54                 } else if (UiModeManager.ACTION_EXIT_CAR_MODE.equals(action)) {
55                     onExitCarMode();
56                 } else {
57                     Log.w(this, "Unexpected intent received: %s", intent.getAction());
58                 }
59             } finally {
60                 Log.endSession();
61             }
62         }
63     };
64 
65     private Set<SystemStateListener> mListeners = new CopyOnWriteArraySet<>();
66     private boolean mIsCarMode;
67 
SystemStateHelper(Context context)68     public SystemStateHelper(Context context) {
69         mContext = context;
70 
71         IntentFilter intentFilter = new IntentFilter(UiModeManager.ACTION_ENTER_CAR_MODE);
72         intentFilter.addAction(UiModeManager.ACTION_EXIT_CAR_MODE);
73         mContext.registerReceiver(mBroadcastReceiver, intentFilter);
74         Log.i(this, "Registering car mode receiver: %s", intentFilter);
75 
76         mIsCarMode = getSystemCarMode();
77     }
78 
addListener(SystemStateListener listener)79     public void addListener(SystemStateListener listener) {
80         if (listener != null) {
81             mListeners.add(listener);
82         }
83     }
84 
removeListener(SystemStateListener listener)85     public boolean removeListener(SystemStateListener listener) {
86         return mListeners.remove(listener);
87     }
88 
isCarMode()89     public boolean isCarMode() {
90         return mIsCarMode;
91     }
92 
isDeviceAtEar()93     public boolean isDeviceAtEar() {
94         return isDeviceAtEar(mContext);
95     }
96 
97     /**
98      * Returns a guess whether the phone is up to the user's ear. Use the proximity sensor and
99      * the gravity sensor to make a guess
100      * @return true if the proximity sensor is activated, the magnitude of gravity in directions
101      *         parallel to the screen is greater than some configurable threshold, and the
102      *         y-component of gravity isn't less than some other configurable threshold.
103      */
isDeviceAtEar(Context context)104     public static boolean isDeviceAtEar(Context context) {
105         SensorManager sm = context.getSystemService(SensorManager.class);
106         if (sm == null) {
107             return false;
108         }
109         Sensor grav = sm.getDefaultSensor(Sensor.TYPE_GRAVITY);
110         Sensor proximity = sm.getDefaultSensor(Sensor.TYPE_PROXIMITY);
111         if (grav == null || proximity == null) {
112             return false;
113         }
114 
115         AtomicBoolean result = new AtomicBoolean(true);
116         CountDownLatch gravLatch = new CountDownLatch(1);
117         CountDownLatch proxLatch = new CountDownLatch(1);
118 
119         final double xyGravityThreshold = context.getResources().getFloat(
120                 R.dimen.device_on_ear_xy_gravity_threshold);
121         final double yGravityNegativeThreshold = context.getResources().getFloat(
122                 R.dimen.device_on_ear_y_gravity_negative_threshold);
123 
124         SensorEventListener listener = new SensorEventListener() {
125             @Override
126             public void onSensorChanged(SensorEvent event) {
127                 if (event.sensor.getType() == Sensor.TYPE_GRAVITY) {
128                     if (gravLatch.getCount() == 0) {
129                         return;
130                     }
131                     double xyMag = Math.sqrt(event.values[0] * event.values[0]
132                             + event.values[1] * event.values[1]);
133                     if (xyMag < xyGravityThreshold
134                             || event.values[1] < yGravityNegativeThreshold) {
135                         result.set(false);
136                     }
137                     gravLatch.countDown();
138                 } else if (event.sensor.getType() == Sensor.TYPE_PROXIMITY) {
139                     if (proxLatch.getCount() == 0) {
140                         return;
141                     }
142                     if (event.values[0] >= proximity.getMaximumRange()) {
143                         result.set(false);
144                     }
145                     proxLatch.countDown();
146                 }
147             }
148 
149             @Override
150             public void onAccuracyChanged(Sensor sensor, int accuracy) {
151             }
152         };
153 
154         try {
155             sm.registerListener(listener, grav, SensorManager.SENSOR_DELAY_FASTEST);
156             sm.registerListener(listener, proximity, SensorManager.SENSOR_DELAY_FASTEST);
157             boolean accelValid = gravLatch.await(100, TimeUnit.MILLISECONDS);
158             boolean proxValid = proxLatch.await(100, TimeUnit.MILLISECONDS);
159             if (accelValid && proxValid) {
160                 return result.get();
161             } else {
162                 Log.w(SystemStateHelper.class.getSimpleName(),
163                         "Timed out waiting for sensors: %b %b", accelValid, proxValid);
164                 return false;
165             }
166         } catch (InterruptedException e) {
167             return false;
168         } finally {
169             sm.unregisterListener(listener);
170         }
171     }
172 
onEnterCarMode()173     private void onEnterCarMode() {
174         if (!mIsCarMode) {
175             Log.i(this, "Entering carmode");
176             mIsCarMode = true;
177             notifyCarMode();
178         }
179     }
180 
onExitCarMode()181     private void onExitCarMode() {
182         if (mIsCarMode) {
183             Log.i(this, "Exiting carmode");
184             mIsCarMode = false;
185             notifyCarMode();
186         }
187     }
188 
notifyCarMode()189     private void notifyCarMode() {
190         for (SystemStateListener listener : mListeners) {
191             listener.onCarModeChanged(mIsCarMode);
192         }
193     }
194 
195     /**
196      * Checks the system for the current car mode.
197      *
198      * @return True if in car mode, false otherwise.
199      */
getSystemCarMode()200     private boolean getSystemCarMode() {
201         UiModeManager uiModeManager =
202                 (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
203 
204         if (uiModeManager != null) {
205             return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR;
206         }
207 
208         return false;
209     }
210 }
211