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