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