Home | History | Annotate | Download | only in phone
      1 /*
      2  * Copyright (C) 2017 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.phone;
     18 
     19 import android.Manifest;
     20 import android.annotation.NonNull;
     21 import android.annotation.UserIdInt;
     22 import android.app.ActivityManager;
     23 import android.app.AppOpsManager;
     24 import android.content.Context;
     25 import android.content.pm.PackageManager;
     26 import android.content.pm.UserInfo;
     27 import android.os.Build;
     28 import android.os.UserHandle;
     29 import android.os.UserManager;
     30 import android.provider.Settings;
     31 
     32 import java.util.List;
     33 
     34 /**
     35  * Helper for performing location access checks.
     36  */
     37 final class LocationAccessPolicy {
     38 
     39     private LocationAccessPolicy() {
     40         /* do nothing - hide ctor */
     41     }
     42 
     43     /**
     44      * API to determine if the caller has permissions to get cell location.
     45      *
     46      * @param pkgName Package name of the application requesting access
     47      * @param uid The uid of the package
     48      * @param message Message to add to the exception if no location permission
     49      * @return boolean true or false if permissions is granted
     50      */
     51     static boolean canAccessCellLocation(@NonNull Context context, @NonNull String pkgName,
     52             int uid, String message) throws SecurityException {
     53         context.getSystemService(AppOpsManager.class).checkPackage(uid, pkgName);
     54         // We always require the location permission and also require the
     55         // location mode to be on for non-legacy apps. Legacy apps are
     56         // required to be in the foreground to at least mitigate the case
     57         // where a legacy app the user is not using tracks their location.
     58 
     59         // Grating ACCESS_FINE_LOCATION to an app automatically grants it ACCESS_COARSE_LOCATION.
     60         context.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION, message);
     61         final int opCode = AppOpsManager.permissionToOpCode(
     62                 Manifest.permission.ACCESS_COARSE_LOCATION);
     63         if (opCode != AppOpsManager.OP_NONE && context.getSystemService(AppOpsManager.class)
     64                 .noteOp(opCode, uid, pkgName) != AppOpsManager.MODE_ALLOWED) {
     65             return false;
     66         }
     67         if (!isLocationModeEnabled(context, UserHandle.getUserId(uid))
     68                 && !isLegacyForeground(context, pkgName)) {
     69             return false;
     70         }
     71         // If the user or profile is current, permission is granted.
     72         // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission.
     73         return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context);
     74     }
     75 
     76     private static boolean isLocationModeEnabled(@NonNull Context context, @UserIdInt int userId) {
     77         return Settings.Secure.getIntForUser(context.getContentResolver(),
     78                 Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF, userId)
     79                 != Settings.Secure.LOCATION_MODE_OFF;
     80     }
     81 
     82     private static boolean isLegacyForeground(@NonNull Context context, @NonNull String pkgName) {
     83         return isLegacyVersion(context, pkgName) && isForegroundApp(context, pkgName);
     84     }
     85 
     86     private static boolean isLegacyVersion(@NonNull Context context, @NonNull String pkgName) {
     87         try {
     88             if (context.getPackageManager().getApplicationInfo(pkgName, 0)
     89                     .targetSdkVersion <= Build.VERSION_CODES.O) {
     90                 return true;
     91             }
     92         } catch (PackageManager.NameNotFoundException e) {
     93             // In case of exception, assume known app (more strict checking)
     94             // Note: This case will never happen since checkPackage is
     95             // called to verify validity before checking app's version.
     96         }
     97         return false;
     98     }
     99 
    100     private static boolean isForegroundApp(@NonNull Context context, @NonNull String pkgName) {
    101         final ActivityManager am = context.getSystemService(ActivityManager.class);
    102         final List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1);
    103         if (!tasks.isEmpty()) {
    104             return pkgName.equals(tasks.get(0).topActivity.getPackageName());
    105         }
    106         return false;
    107     }
    108 
    109     private static boolean checkInteractAcrossUsersFull(@NonNull Context context) {
    110         return context.checkCallingOrSelfPermission(
    111                 android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
    112                 == PackageManager.PERMISSION_GRANTED;
    113     }
    114 
    115     private static boolean isCurrentProfile(@NonNull Context context, int uid) {
    116         final int currentUser = ActivityManager.getCurrentUser();
    117         final int callingUserId = UserHandle.getUserId(uid);
    118         if (callingUserId == currentUser) {
    119             return true;
    120         } else {
    121             List<UserInfo> userProfiles = context.getSystemService(
    122                     UserManager.class).getProfiles(currentUser);
    123             for (UserInfo user: userProfiles) {
    124                 if (user.id == callingUserId) {
    125                     return true;
    126                 }
    127             }
    128         }
    129         return false;
    130     }
    131 }
    132