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 com.android.internal.annotations.VisibleForTesting;
25 
26 /**
27  * Helps with emergency calls by:
28  * 1. granting temporary location permission to the system dialer service during emergency calls
29  * 2. keeping track of the time of the last emergency call
30  */
31 @VisibleForTesting
32 public class EmergencyCallHelper {
33     private final Context mContext;
34     private final DefaultDialerCache mDefaultDialerCache;
35     private final Timeouts.Adapter mTimeoutsAdapter;
36     private UserHandle mLocationPermissionGrantedToUser;
37     private boolean mHadFineLocation = false;
38     private boolean mHadBackgroundLocation = false;
39     private long mLastEmergencyCallTimestampMillis;
40 
41     @VisibleForTesting
EmergencyCallHelper( Context context, DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter)42     public EmergencyCallHelper(
43             Context context,
44             DefaultDialerCache defaultDialerCache,
45             Timeouts.Adapter timeoutsAdapter) {
46         mContext = context;
47         mDefaultDialerCache = defaultDialerCache;
48         mTimeoutsAdapter = timeoutsAdapter;
49     }
50 
maybeGrantTemporaryLocationPermission(Call call, UserHandle userHandle)51     void maybeGrantTemporaryLocationPermission(Call call, UserHandle userHandle) {
52         if (shouldGrantTemporaryLocationPermission(call)) {
53             grantLocationPermission(userHandle);
54         }
55         if (call != null && call.isEmergencyCall()) {
56             recordEmergencyCallTime();
57         }
58     }
59 
maybeRevokeTemporaryLocationPermission()60     void maybeRevokeTemporaryLocationPermission() {
61         if (wasGrantedTemporaryLocationPermission()) {
62             revokeLocationPermission();
63         }
64     }
65 
getLastEmergencyCallTimeMillis()66     long getLastEmergencyCallTimeMillis() {
67         return mLastEmergencyCallTimestampMillis;
68     }
69 
recordEmergencyCallTime()70     private void recordEmergencyCallTime() {
71         mLastEmergencyCallTimestampMillis = System.currentTimeMillis();
72     }
73 
isInEmergencyCallbackWindow()74     private boolean isInEmergencyCallbackWindow() {
75         return System.currentTimeMillis() - getLastEmergencyCallTimeMillis()
76                 < mTimeoutsAdapter.getEmergencyCallbackWindowMillis(mContext.getContentResolver());
77     }
78 
shouldGrantTemporaryLocationPermission(Call call)79     private boolean shouldGrantTemporaryLocationPermission(Call call) {
80         if (!mContext.getResources().getBoolean(R.bool.grant_location_permission_enabled)) {
81             Log.i(this, "ShouldGrantTemporaryLocationPermission, disabled by config");
82             return false;
83         }
84         if (call == null) {
85             Log.i(this, "ShouldGrantTemporaryLocationPermission, no call");
86             return false;
87         }
88         if (!call.isEmergencyCall() && !isInEmergencyCallbackWindow()) {
89             Log.i(this, "ShouldGrantTemporaryLocationPermission, not emergency");
90             return false;
91         }
92         Log.i(this, "ShouldGrantTemporaryLocationPermission, returning true");
93         return true;
94     }
95 
grantLocationPermission(UserHandle userHandle)96     private void grantLocationPermission(UserHandle userHandle) {
97         String systemDialerPackage = mDefaultDialerCache.getSystemDialerApplication();
98         Log.i(this, "Granting temporary location permission to " + systemDialerPackage
99               + ", user: " + userHandle);
100         try {
101             boolean hadBackgroundLocation = hasBackgroundLocationPermission();
102             boolean hadFineLocation = hasFineLocationPermission();
103             if (hadBackgroundLocation && hadFineLocation) {
104                 Log.i(this, "Skipping location grant because the system dialer already"
105                         + " holds sufficient permissions");
106                 return;
107             }
108             if (!hadFineLocation) {
109                 mContext.getPackageManager().grantRuntimePermission(systemDialerPackage,
110                         Manifest.permission.ACCESS_FINE_LOCATION, userHandle);
111             }
112             if (!hadBackgroundLocation) {
113                 mContext.getPackageManager().grantRuntimePermission(systemDialerPackage,
114                         Manifest.permission.ACCESS_BACKGROUND_LOCATION, userHandle);
115             }
116             mHadFineLocation = hadFineLocation;
117             mHadBackgroundLocation = hadBackgroundLocation;
118             recordPermissionGrant(userHandle);
119         } catch (Exception e) {
120             Log.e(this, e, "Failed to grant location permissions to " + systemDialerPackage
121                   + ", user: " + userHandle);
122         }
123     }
124 
revokeLocationPermission()125     private void revokeLocationPermission() {
126         String systemDialerPackage = mDefaultDialerCache.getSystemDialerApplication();
127         Log.i(this, "Revoking temporary location permission from " + systemDialerPackage
128               + ", user: " + mLocationPermissionGrantedToUser);
129         UserHandle userHandle = mLocationPermissionGrantedToUser;
130         try {
131             if (!mHadFineLocation) {
132                 mContext.getPackageManager().revokeRuntimePermission(systemDialerPackage,
133                         Manifest.permission.ACCESS_FINE_LOCATION, userHandle);
134             }
135             if (!mHadBackgroundLocation) {
136                 mContext.getPackageManager().revokeRuntimePermission(systemDialerPackage,
137                         Manifest.permission.ACCESS_BACKGROUND_LOCATION, userHandle);
138             }
139         } catch (Exception e) {
140             Log.e(this, e, "Failed to revoke location permission from " + systemDialerPackage
141                   + ", user: " + userHandle);
142         }
143         clearPermissionGrant();
144     }
145 
hasBackgroundLocationPermission()146     private boolean hasBackgroundLocationPermission() {
147         return mContext.getPackageManager().checkPermission(
148                 Manifest.permission.ACCESS_BACKGROUND_LOCATION,
149                 mDefaultDialerCache.getSystemDialerApplication())
150                 == PackageManager.PERMISSION_GRANTED;
151     }
152 
hasFineLocationPermission()153     private boolean hasFineLocationPermission() {
154         return mContext.getPackageManager().checkPermission(
155                 Manifest.permission.ACCESS_FINE_LOCATION,
156                 mDefaultDialerCache.getSystemDialerApplication())
157                 == PackageManager.PERMISSION_GRANTED;
158     }
159 
recordPermissionGrant(UserHandle userHandle)160     private void recordPermissionGrant(UserHandle userHandle) {
161         mLocationPermissionGrantedToUser = userHandle;
162     }
163 
wasGrantedTemporaryLocationPermission()164     private boolean wasGrantedTemporaryLocationPermission() {
165         return mLocationPermissionGrantedToUser != null;
166     }
167 
clearPermissionGrant()168     private void clearPermissionGrant() {
169         mLocationPermissionGrantedToUser = null;
170         mHadBackgroundLocation = false;
171         mHadFineLocation = false;
172     }
173 }
174