1 /* 2 * Copyright 2016, 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.Manifest; 20 import android.content.Context; 21 import android.content.pm.PackageManager; 22 import android.os.UserHandle; 23 import android.telecom.Log; 24 import android.telecom.PhoneAccountHandle; 25 26 import com.android.internal.annotations.VisibleForTesting; 27 28 /** 29 * Helps with emergency calls by: 30 * 1. granting temporary location permission to the system dialer service during emergency calls 31 * 2. keeping track of the time of the last emergency call 32 */ 33 @VisibleForTesting 34 public class EmergencyCallHelper { 35 private final Context mContext; 36 private final DefaultDialerCache mDefaultDialerCache; 37 private final Timeouts.Adapter mTimeoutsAdapter; 38 private UserHandle mLocationPermissionGrantedToUser; 39 private PhoneAccountHandle mLastOutgoingEmergencyCallPAH; 40 41 //stores the original state of permissions that dialer had 42 private boolean mHadFineLocation = false; 43 private boolean mHadBackgroundLocation = false; 44 45 //stores whether we successfully granted the runtime permission 46 //This is stored so we don't unnecessarily revoke if the grant had failed with an exception. 47 //Else we will get an exception 48 private boolean mFineLocationGranted= false; 49 private boolean mBackgroundLocationGranted = false; 50 51 private long mLastEmergencyCallTimestampMillis; 52 private long mLastOutgoingEmergencyCallTimestampMillis; 53 54 @VisibleForTesting EmergencyCallHelper( Context context, DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter)55 public EmergencyCallHelper( 56 Context context, 57 DefaultDialerCache defaultDialerCache, 58 Timeouts.Adapter timeoutsAdapter) { 59 mContext = context; 60 mDefaultDialerCache = defaultDialerCache; 61 mTimeoutsAdapter = timeoutsAdapter; 62 } 63 64 @VisibleForTesting maybeGrantTemporaryLocationPermission(Call call, UserHandle userHandle)65 public void maybeGrantTemporaryLocationPermission(Call call, UserHandle userHandle) { 66 if (shouldGrantTemporaryLocationPermission(call)) { 67 grantLocationPermission(userHandle); 68 } 69 if (call != null && call.isEmergencyCall()) { 70 recordEmergencyCall(call); 71 } 72 } 73 74 @VisibleForTesting maybeRevokeTemporaryLocationPermission()75 public void maybeRevokeTemporaryLocationPermission() { 76 if (wasGrantedTemporaryLocationPermission()) { 77 revokeLocationPermission(); 78 } 79 } 80 getLastEmergencyCallTimeMillis()81 long getLastEmergencyCallTimeMillis() { 82 return mLastEmergencyCallTimestampMillis; 83 } 84 85 @VisibleForTesting setLastOutgoingEmergencyCallTimestampMillis(long timestampMillis)86 public void setLastOutgoingEmergencyCallTimestampMillis(long timestampMillis) { 87 mLastOutgoingEmergencyCallTimestampMillis = timestampMillis; 88 } 89 90 @VisibleForTesting setLastOutgoingEmergencyCallPAH(PhoneAccountHandle accountHandle)91 public void setLastOutgoingEmergencyCallPAH(PhoneAccountHandle accountHandle) { 92 mLastOutgoingEmergencyCallPAH = accountHandle; 93 } 94 95 @VisibleForTesting isLastOutgoingEmergencyCallPAH(PhoneAccountHandle currentCallHandle)96 public boolean isLastOutgoingEmergencyCallPAH(PhoneAccountHandle currentCallHandle) { 97 boolean ecbmActive = mLastOutgoingEmergencyCallPAH != null 98 && isInEmergencyCallbackWindow(mLastOutgoingEmergencyCallTimestampMillis) 99 && currentCallHandle != null 100 && currentCallHandle.equals(mLastOutgoingEmergencyCallPAH); 101 if (ecbmActive) { 102 Log.i(this, "ECBM is enabled for %s. The last recorded call timestamp was at %s", 103 currentCallHandle, mLastOutgoingEmergencyCallTimestampMillis); 104 } 105 106 return ecbmActive; 107 } 108 isInEmergencyCallbackWindow(long lastEmergencyCallTimestampMillis)109 boolean isInEmergencyCallbackWindow(long lastEmergencyCallTimestampMillis) { 110 return System.currentTimeMillis() - lastEmergencyCallTimestampMillis 111 < mTimeoutsAdapter.getEmergencyCallbackWindowMillis(mContext.getContentResolver()); 112 } 113 recordEmergencyCall(Call call)114 private void recordEmergencyCall(Call call) { 115 mLastEmergencyCallTimestampMillis = System.currentTimeMillis(); 116 if (!call.isIncoming()) { 117 // ECBM is applicable to MO emergency calls 118 mLastOutgoingEmergencyCallTimestampMillis = mLastEmergencyCallTimestampMillis; 119 mLastOutgoingEmergencyCallPAH = call.getTargetPhoneAccount(); 120 } 121 } 122 shouldGrantTemporaryLocationPermission(Call call)123 private boolean shouldGrantTemporaryLocationPermission(Call call) { 124 if (!mContext.getResources().getBoolean(R.bool.grant_location_permission_enabled)) { 125 Log.i(this, "ShouldGrantTemporaryLocationPermission, disabled by config"); 126 return false; 127 } 128 if (call == null) { 129 Log.i(this, "ShouldGrantTemporaryLocationPermission, no call"); 130 return false; 131 } 132 if (!call.isEmergencyCall() && !isInEmergencyCallbackWindow( 133 getLastEmergencyCallTimeMillis())) { 134 Log.i(this, "ShouldGrantTemporaryLocationPermission, not emergency"); 135 return false; 136 } 137 Log.i(this, "ShouldGrantTemporaryLocationPermission, returning true"); 138 return true; 139 } 140 grantLocationPermission(UserHandle userHandle)141 private void grantLocationPermission(UserHandle userHandle) { 142 String systemDialerPackage = mDefaultDialerCache.getSystemDialerApplication(); 143 Log.i(this, "Attempting to grant temporary location permission to " + systemDialerPackage 144 + ", user: " + userHandle); 145 146 boolean hadBackgroundLocation = hasBackgroundLocationPermission(); 147 boolean hadFineLocation = hasFineLocationPermission(); 148 if (hadBackgroundLocation && hadFineLocation) { 149 Log.i(this, "Skipping location grant because the system dialer already" 150 + " holds sufficient permissions"); 151 return; 152 } 153 mHadFineLocation = hadFineLocation; 154 mHadBackgroundLocation = hadBackgroundLocation; 155 156 if (!hadFineLocation) { 157 try { 158 mContext.getPackageManager().grantRuntimePermission(systemDialerPackage, 159 Manifest.permission.ACCESS_FINE_LOCATION, userHandle); 160 recordFineLocationPermissionGrant(userHandle); 161 } catch (Exception e) { 162 Log.i(this, "Failed to grant ACCESS_FINE_LOCATION"); 163 } 164 } 165 if (!hadBackgroundLocation) { 166 try { 167 mContext.getPackageManager().grantRuntimePermission(systemDialerPackage, 168 Manifest.permission.ACCESS_BACKGROUND_LOCATION, userHandle); 169 recordBackgroundLocationPermissionGrant(userHandle); 170 } catch (Exception e) { 171 Log.i(this, "Failed to grant ACCESS_BACKGROUND_LOCATION"); 172 } 173 } 174 } 175 revokeLocationPermission()176 private void revokeLocationPermission() { 177 String systemDialerPackage = mDefaultDialerCache.getSystemDialerApplication(); 178 Log.i(this, "Revoking temporary location permission from " + systemDialerPackage 179 + ", user: " + mLocationPermissionGrantedToUser); 180 UserHandle userHandle = mLocationPermissionGrantedToUser; 181 182 try { 183 if (!mHadFineLocation && mFineLocationGranted) { 184 mContext.getPackageManager().revokeRuntimePermission(systemDialerPackage, 185 Manifest.permission.ACCESS_FINE_LOCATION, userHandle); 186 } 187 } catch (Exception e) { 188 Log.e(this, e, "Failed to revoke location permission from " + systemDialerPackage 189 + ", user: " + userHandle); 190 } 191 192 try { 193 if (!mHadBackgroundLocation && mBackgroundLocationGranted) { 194 mContext.getPackageManager().revokeRuntimePermission(systemDialerPackage, 195 Manifest.permission.ACCESS_BACKGROUND_LOCATION, userHandle); 196 } 197 } catch (Exception e) { 198 Log.e(this, e, "Failed to revoke location permission from " + systemDialerPackage 199 + ", user: " + userHandle); 200 } 201 202 clearPermissionGrant(); 203 } 204 hasBackgroundLocationPermission()205 private boolean hasBackgroundLocationPermission() { 206 return mContext.getPackageManager().checkPermission( 207 Manifest.permission.ACCESS_BACKGROUND_LOCATION, 208 mDefaultDialerCache.getSystemDialerApplication()) 209 == PackageManager.PERMISSION_GRANTED; 210 } 211 hasFineLocationPermission()212 private boolean hasFineLocationPermission() { 213 return mContext.getPackageManager().checkPermission( 214 Manifest.permission.ACCESS_FINE_LOCATION, 215 mDefaultDialerCache.getSystemDialerApplication()) 216 == PackageManager.PERMISSION_GRANTED; 217 } 218 recordBackgroundLocationPermissionGrant(UserHandle userHandle)219 private void recordBackgroundLocationPermissionGrant(UserHandle userHandle) { 220 mLocationPermissionGrantedToUser = userHandle; 221 mBackgroundLocationGranted = true; 222 } 223 recordFineLocationPermissionGrant(UserHandle userHandle)224 private void recordFineLocationPermissionGrant(UserHandle userHandle) { 225 mLocationPermissionGrantedToUser = userHandle; 226 mFineLocationGranted = true; 227 } 228 wasGrantedTemporaryLocationPermission()229 private boolean wasGrantedTemporaryLocationPermission() { 230 return mLocationPermissionGrantedToUser != null; 231 } 232 clearPermissionGrant()233 private void clearPermissionGrant() { 234 mLocationPermissionGrantedToUser = null; 235 mHadBackgroundLocation = false; 236 mHadFineLocation = false; 237 mBackgroundLocationGranted = false; 238 mFineLocationGranted = false; 239 } 240 } 241