• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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