1 /* 2 * Copyright (C) 2020 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.location.injector; 18 19 import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION; 20 import static android.app.AppOpsManager.OP_MONITOR_LOCATION; 21 22 import static com.android.server.location.LocationManagerService.D; 23 import static com.android.server.location.LocationManagerService.TAG; 24 25 import android.location.util.identity.CallerIdentity; 26 import android.util.ArrayMap; 27 import android.util.ArraySet; 28 import android.util.Log; 29 30 import com.android.internal.annotations.GuardedBy; 31 32 import java.util.Map; 33 import java.util.Objects; 34 import java.util.Set; 35 36 /** 37 * Helps manage appop monitoring for multiple location clients. 38 */ 39 public class LocationAttributionHelper { 40 41 private static class BucketKey { 42 private final String mBucket; 43 private final Object mKey; 44 BucketKey(String bucket, Object key)45 private BucketKey(String bucket, Object key) { 46 mBucket = Objects.requireNonNull(bucket); 47 mKey = Objects.requireNonNull(key); 48 } 49 50 @Override equals(Object o)51 public boolean equals(Object o) { 52 if (this == o) { 53 return true; 54 } 55 if (o == null || getClass() != o.getClass()) { 56 return false; 57 } 58 59 BucketKey that = (BucketKey) o; 60 return mBucket.equals(that.mBucket) 61 && mKey.equals(that.mKey); 62 } 63 64 @Override hashCode()65 public int hashCode() { 66 return Objects.hash(mBucket, mKey); 67 } 68 } 69 70 private final AppOpsHelper mAppOpsHelper; 71 72 @GuardedBy("this") 73 private final Map<CallerIdentity, Set<BucketKey>> mAttributions; 74 @GuardedBy("this") 75 private final Map<CallerIdentity, Set<BucketKey>> mHighPowerAttributions; 76 LocationAttributionHelper(AppOpsHelper appOpsHelper)77 public LocationAttributionHelper(AppOpsHelper appOpsHelper) { 78 mAppOpsHelper = appOpsHelper; 79 80 mAttributions = new ArrayMap<>(); 81 mHighPowerAttributions = new ArrayMap<>(); 82 } 83 84 /** 85 * Report normal location usage for the given caller in the given bucket, with a unique key. 86 */ reportLocationStart(CallerIdentity identity, String bucket, Object key)87 public synchronized void reportLocationStart(CallerIdentity identity, String bucket, 88 Object key) { 89 Set<BucketKey> keySet = mAttributions.computeIfAbsent(identity, 90 i -> new ArraySet<>()); 91 boolean empty = keySet.isEmpty(); 92 if (keySet.add(new BucketKey(bucket, key)) && empty) { 93 if (!mAppOpsHelper.startOpNoThrow(OP_MONITOR_LOCATION, identity)) { 94 mAttributions.remove(identity); 95 } 96 } 97 } 98 99 /** 100 * Report normal location usage has stopped for the given caller in the given bucket, with a 101 * unique key. 102 */ reportLocationStop(CallerIdentity identity, String bucket, Object key)103 public synchronized void reportLocationStop(CallerIdentity identity, String bucket, 104 Object key) { 105 Set<BucketKey> keySet = mAttributions.get(identity); 106 if (keySet != null && keySet.remove(new BucketKey(bucket, key)) 107 && keySet.isEmpty()) { 108 mAttributions.remove(identity); 109 mAppOpsHelper.finishOp(OP_MONITOR_LOCATION, identity); 110 } 111 } 112 113 /** 114 * Report high power location usage for the given caller in the given bucket, with a unique 115 * key. 116 */ reportHighPowerLocationStart(CallerIdentity identity, String bucket, Object key)117 public synchronized void reportHighPowerLocationStart(CallerIdentity identity, String bucket, 118 Object key) { 119 Set<BucketKey> keySet = mHighPowerAttributions.computeIfAbsent(identity, 120 i -> new ArraySet<>()); 121 boolean empty = keySet.isEmpty(); 122 if (keySet.add(new BucketKey(bucket, key)) && empty) { 123 if (mAppOpsHelper.startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, identity)) { 124 if (D) { 125 Log.v(TAG, "starting high power location attribution for " + identity); 126 } 127 } else { 128 mHighPowerAttributions.remove(identity); 129 } 130 } 131 } 132 133 /** 134 * Report high power location usage has stopped for the given caller in the given bucket, 135 * with a unique key. 136 */ reportHighPowerLocationStop(CallerIdentity identity, String bucket, Object key)137 public synchronized void reportHighPowerLocationStop(CallerIdentity identity, String bucket, 138 Object key) { 139 Set<BucketKey> keySet = mHighPowerAttributions.get(identity); 140 if (keySet != null && keySet.remove(new BucketKey(bucket, key)) 141 && keySet.isEmpty()) { 142 if (D) { 143 Log.v(TAG, "stopping high power location attribution for " + identity); 144 } 145 mHighPowerAttributions.remove(identity); 146 mAppOpsHelper.finishOp(OP_MONITOR_HIGH_POWER_LOCATION, identity); 147 } 148 } 149 } 150