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 { 42 /** 43 * Listener method to inform interested parties when a package name requests to enter or 44 * exit car mode. 45 * @param priority the priority of the enter/exit request. 46 * @param packageName the package name of the requester. 47 * @param isCarMode {@code true} if the package is entering car mode, {@code false} 48 * otherwise. 49 */ onCarModeChanged(int priority, String packageName, boolean isCarMode)50 void onCarModeChanged(int priority, String packageName, boolean isCarMode); 51 } 52 53 private final Context mContext; 54 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 55 @Override 56 public void onReceive(Context context, Intent intent) { 57 Log.startSession("SSP.oR"); 58 try { 59 String action = intent.getAction(); 60 if (UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED.equals(action)) { 61 int priority = intent.getIntExtra(UiModeManager.EXTRA_PRIORITY, 62 UiModeManager.DEFAULT_PRIORITY); 63 String callingPackage = intent.getStringExtra( 64 UiModeManager.EXTRA_CALLING_PACKAGE); 65 Log.i(SystemStateHelper.this, "ENTER_CAR_MODE_PRIVILEGED; priority=%d, pkg=%s", 66 priority, callingPackage); 67 onEnterCarMode(priority, callingPackage); 68 } else if (UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED.equals(action)) { 69 int priority = intent.getIntExtra(UiModeManager.EXTRA_PRIORITY, 70 UiModeManager.DEFAULT_PRIORITY); 71 String callingPackage = intent.getStringExtra( 72 UiModeManager.EXTRA_CALLING_PACKAGE); 73 Log.i(SystemStateHelper.this, "EXIT_CAR_MODE_PRIVILEGED; priority=%d, pkg=%s", 74 priority, callingPackage); 75 onExitCarMode(priority, callingPackage); 76 } else { 77 Log.w(this, "Unexpected intent received: %s", intent.getAction()); 78 } 79 } finally { 80 Log.endSession(); 81 } 82 } 83 }; 84 85 private Set<SystemStateListener> mListeners = new CopyOnWriteArraySet<>(); 86 private boolean mIsCarMode; 87 SystemStateHelper(Context context)88 public SystemStateHelper(Context context) { 89 mContext = context; 90 91 IntentFilter intentFilter = new IntentFilter( 92 UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED); 93 intentFilter.addAction(UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED); 94 mContext.registerReceiver(mBroadcastReceiver, intentFilter); 95 Log.i(this, "Registering car mode receiver: %s", intentFilter); 96 97 mIsCarMode = getSystemCarMode(); 98 } 99 addListener(SystemStateListener listener)100 public void addListener(SystemStateListener listener) { 101 if (listener != null) { 102 mListeners.add(listener); 103 } 104 } 105 removeListener(SystemStateListener listener)106 public boolean removeListener(SystemStateListener listener) { 107 return mListeners.remove(listener); 108 } 109 isCarMode()110 public boolean isCarMode() { 111 return mIsCarMode; 112 } 113 isDeviceAtEar()114 public boolean isDeviceAtEar() { 115 return isDeviceAtEar(mContext); 116 } 117 118 /** 119 * Returns a guess whether the phone is up to the user's ear. Use the proximity sensor and 120 * the gravity sensor to make a guess 121 * @return true if the proximity sensor is activated, the magnitude of gravity in directions 122 * parallel to the screen is greater than some configurable threshold, and the 123 * y-component of gravity isn't less than some other configurable threshold. 124 */ isDeviceAtEar(Context context)125 public static boolean isDeviceAtEar(Context context) { 126 SensorManager sm = context.getSystemService(SensorManager.class); 127 if (sm == null) { 128 return false; 129 } 130 Sensor grav = sm.getDefaultSensor(Sensor.TYPE_GRAVITY); 131 Sensor proximity = sm.getDefaultSensor(Sensor.TYPE_PROXIMITY); 132 if (grav == null || proximity == null) { 133 return false; 134 } 135 136 AtomicBoolean result = new AtomicBoolean(true); 137 CountDownLatch gravLatch = new CountDownLatch(1); 138 CountDownLatch proxLatch = new CountDownLatch(1); 139 140 final double xyGravityThreshold = context.getResources().getFloat( 141 R.dimen.device_on_ear_xy_gravity_threshold); 142 final double yGravityNegativeThreshold = context.getResources().getFloat( 143 R.dimen.device_on_ear_y_gravity_negative_threshold); 144 145 SensorEventListener listener = new SensorEventListener() { 146 @Override 147 public void onSensorChanged(SensorEvent event) { 148 if (event.sensor.getType() == Sensor.TYPE_GRAVITY) { 149 if (gravLatch.getCount() == 0) { 150 return; 151 } 152 double xyMag = Math.sqrt(event.values[0] * event.values[0] 153 + event.values[1] * event.values[1]); 154 if (xyMag < xyGravityThreshold 155 || event.values[1] < yGravityNegativeThreshold) { 156 result.set(false); 157 } 158 gravLatch.countDown(); 159 } else if (event.sensor.getType() == Sensor.TYPE_PROXIMITY) { 160 if (proxLatch.getCount() == 0) { 161 return; 162 } 163 if (event.values[0] >= proximity.getMaximumRange()) { 164 result.set(false); 165 } 166 proxLatch.countDown(); 167 } 168 } 169 170 @Override 171 public void onAccuracyChanged(Sensor sensor, int accuracy) { 172 } 173 }; 174 175 try { 176 sm.registerListener(listener, grav, SensorManager.SENSOR_DELAY_FASTEST); 177 sm.registerListener(listener, proximity, SensorManager.SENSOR_DELAY_FASTEST); 178 boolean accelValid = gravLatch.await(100, TimeUnit.MILLISECONDS); 179 boolean proxValid = proxLatch.await(100, TimeUnit.MILLISECONDS); 180 if (accelValid && proxValid) { 181 return result.get(); 182 } else { 183 Log.w(SystemStateHelper.class.getSimpleName(), 184 "Timed out waiting for sensors: %b %b", accelValid, proxValid); 185 return false; 186 } 187 } catch (InterruptedException e) { 188 return false; 189 } finally { 190 sm.unregisterListener(listener); 191 } 192 } 193 onEnterCarMode(int priority, String packageName)194 private void onEnterCarMode(int priority, String packageName) { 195 Log.i(this, "Entering carmode"); 196 mIsCarMode = getSystemCarMode(); 197 for (SystemStateListener listener : mListeners) { 198 listener.onCarModeChanged(priority, packageName, true /* isCarMode */); 199 } 200 } 201 onExitCarMode(int priority, String packageName)202 private void onExitCarMode(int priority, String packageName) { 203 Log.i(this, "Exiting carmode"); 204 mIsCarMode = getSystemCarMode(); 205 for (SystemStateListener listener : mListeners) { 206 listener.onCarModeChanged(priority, packageName, false /* isCarMode */); 207 } 208 } 209 210 /** 211 * Checks the system for the current car mode. 212 * 213 * @return True if in car mode, false otherwise. 214 */ getSystemCarMode()215 private boolean getSystemCarMode() { 216 UiModeManager uiModeManager = 217 (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE); 218 219 if (uiModeManager != null) { 220 return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR; 221 } 222 223 return false; 224 } 225 } 226