1 /* 2 * Copyright (C) 2018 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.systemui.appops; 18 19 import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; 20 import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; 21 import static android.media.AudioManager.ACTION_MICROPHONE_MUTE_CHANGED; 22 23 import android.annotation.Nullable; 24 import android.app.AppOpsManager; 25 import android.content.BroadcastReceiver; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.media.AudioManager; 30 import android.media.AudioRecordingConfiguration; 31 import android.os.Handler; 32 import android.os.Looper; 33 import android.os.UserHandle; 34 import android.permission.PermissionManager; 35 import android.util.ArraySet; 36 import android.util.Log; 37 import android.util.SparseArray; 38 39 import androidx.annotation.WorkerThread; 40 41 import com.android.internal.annotations.GuardedBy; 42 import com.android.internal.annotations.VisibleForTesting; 43 import com.android.systemui.Dumpable; 44 import com.android.systemui.broadcast.BroadcastDispatcher; 45 import com.android.systemui.dagger.SysUISingleton; 46 import com.android.systemui.dagger.qualifiers.Background; 47 import com.android.systemui.dump.DumpManager; 48 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; 49 import com.android.systemui.util.Assert; 50 import com.android.systemui.util.time.SystemClock; 51 52 import java.io.PrintWriter; 53 import java.util.ArrayList; 54 import java.util.List; 55 import java.util.Map; 56 import java.util.Set; 57 import java.util.concurrent.Executor; 58 59 import javax.inject.Inject; 60 61 /** 62 * Controller to keep track of applications that have requested access to given App Ops 63 * 64 * It can be subscribed to with callbacks. Additionally, it passes on the information to 65 * NotificationPresenter to be displayed to the user. 66 */ 67 @SysUISingleton 68 public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsController, 69 AppOpsManager.OnOpActiveChangedListener, 70 AppOpsManager.OnOpNotedInternalListener, IndividualSensorPrivacyController.Callback, 71 Dumpable { 72 73 // This is the minimum time that we will keep AppOps that are noted on record. If multiple 74 // occurrences of the same (op, package, uid) happen in a shorter interval, they will not be 75 // notified to listeners. 76 private static final long NOTED_OP_TIME_DELAY_MS = 5000; 77 private static final String TAG = "AppOpsControllerImpl"; 78 private static final boolean DEBUG = false; 79 80 private final BroadcastDispatcher mDispatcher; 81 private final Context mContext; 82 private final AppOpsManager mAppOps; 83 private final AudioManager mAudioManager; 84 private final IndividualSensorPrivacyController mSensorPrivacyController; 85 private final SystemClock mClock; 86 87 private H mBGHandler; 88 private final Executor mBgExecutor; 89 private final List<AppOpsController.Callback> mCallbacks = new ArrayList<>(); 90 private final SparseArray<Set<Callback>> mCallbacksByCode = new SparseArray<>(); 91 private boolean mListening; 92 private boolean mMicMuted; 93 private boolean mCameraDisabled; 94 95 @GuardedBy("mActiveItems") 96 private final List<AppOpItem> mActiveItems = new ArrayList<>(); 97 @GuardedBy("mNotedItems") 98 private final List<AppOpItem> mNotedItems = new ArrayList<>(); 99 @GuardedBy("mActiveItems") 100 private final SparseArray<ArrayList<AudioRecordingConfiguration>> mRecordingsByUid = 101 new SparseArray<>(); 102 103 @VisibleForTesting 104 protected static final int[] OPS_MIC = new int[] { 105 AppOpsManager.OP_RECORD_AUDIO, 106 AppOpsManager.OP_PHONE_CALL_MICROPHONE, 107 AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, 108 AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, 109 AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO 110 }; 111 112 protected static final int[] OPS_CAMERA = new int[] { 113 AppOpsManager.OP_CAMERA, 114 AppOpsManager.OP_PHONE_CALL_CAMERA 115 }; 116 117 protected static final int[] OPS_LOC = new int[] { 118 AppOpsManager.OP_FINE_LOCATION, 119 AppOpsManager.OP_COARSE_LOCATION, 120 AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION 121 }; 122 123 protected static final int[] OPS_OTHERS = new int[] { 124 AppOpsManager.OP_SYSTEM_ALERT_WINDOW 125 }; 126 127 protected static final int[] OPS = concatOps(OPS_MIC, OPS_CAMERA, OPS_LOC, OPS_OTHERS); 128 129 /** 130 * @param opArrays the given op arrays. 131 * @return the concatenations of the given op arrays. Null arrays are treated as empty. 132 */ concatOps(@ullable int[]...opArrays)133 private static int[] concatOps(@Nullable int[]...opArrays) { 134 if (opArrays == null) { 135 return new int[0]; 136 } 137 int totalLength = 0; 138 for (int[] opArray : opArrays) { 139 if (opArray == null || opArray.length == 0) { 140 continue; 141 } 142 totalLength += opArray.length; 143 } 144 final int[] concatOps = new int[totalLength]; 145 int index = 0; 146 for (int[] opArray : opArrays) { 147 if (opArray == null || opArray.length == 0) continue; 148 System.arraycopy(opArray, 0, concatOps, index, opArray.length); 149 index += opArray.length; 150 } 151 return concatOps; 152 } 153 154 @Inject AppOpsControllerImpl( Context context, @Background Looper bgLooper, @Background Executor bgExecutor, DumpManager dumpManager, AudioManager audioManager, IndividualSensorPrivacyController sensorPrivacyController, BroadcastDispatcher dispatcher, SystemClock clock )155 public AppOpsControllerImpl( 156 Context context, 157 @Background Looper bgLooper, 158 @Background Executor bgExecutor, 159 DumpManager dumpManager, 160 AudioManager audioManager, 161 IndividualSensorPrivacyController sensorPrivacyController, 162 BroadcastDispatcher dispatcher, 163 SystemClock clock 164 ) { 165 mDispatcher = dispatcher; 166 mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); 167 mBGHandler = new H(bgLooper); 168 mBgExecutor = bgExecutor; 169 final int numOps = OPS.length; 170 for (int i = 0; i < numOps; i++) { 171 mCallbacksByCode.put(OPS[i], new ArraySet<>()); 172 } 173 mAudioManager = audioManager; 174 mSensorPrivacyController = sensorPrivacyController; 175 mMicMuted = audioManager.isMicrophoneMute() 176 || mSensorPrivacyController.isSensorBlocked(MICROPHONE); 177 mCameraDisabled = mSensorPrivacyController.isSensorBlocked(CAMERA); 178 mContext = context; 179 mClock = clock; 180 dumpManager.registerDumpable(TAG, this); 181 } 182 183 @VisibleForTesting setBGHandler(H handler)184 protected void setBGHandler(H handler) { 185 mBGHandler = handler; 186 } 187 188 @VisibleForTesting setListening(boolean listening)189 protected void setListening(boolean listening) { 190 mListening = listening; 191 // Move IPCs to the background. 192 mBgExecutor.execute(() -> { 193 if (listening) { 194 // System UI could be restarted while ops are active, so fetch the currently active 195 // ops once System UI starts listening again -- see b/294104969. 196 fetchCurrentActiveOps(); 197 198 mAppOps.startWatchingActive(OPS, this); 199 mAppOps.startWatchingNoted(OPS, this); 200 mAudioManager.registerAudioRecordingCallback(mAudioRecordingCallback, mBGHandler); 201 mSensorPrivacyController.addCallback(this); 202 203 mMicMuted = mAudioManager.isMicrophoneMute() 204 || mSensorPrivacyController.isSensorBlocked(MICROPHONE); 205 mCameraDisabled = mSensorPrivacyController.isSensorBlocked(CAMERA); 206 207 mBGHandler.post(() -> mAudioRecordingCallback.onRecordingConfigChanged( 208 mAudioManager.getActiveRecordingConfigurations())); 209 mDispatcher.registerReceiverWithHandler(this, 210 new IntentFilter(ACTION_MICROPHONE_MUTE_CHANGED), mBGHandler); 211 } else { 212 mAppOps.stopWatchingActive(this); 213 mAppOps.stopWatchingNoted(this); 214 mAudioManager.unregisterAudioRecordingCallback(mAudioRecordingCallback); 215 mSensorPrivacyController.removeCallback(this); 216 217 mBGHandler.removeCallbacksAndMessages(null); // null removes all 218 mDispatcher.unregisterReceiver(this); 219 synchronized (mActiveItems) { 220 mActiveItems.clear(); 221 mRecordingsByUid.clear(); 222 } 223 synchronized (mNotedItems) { 224 mNotedItems.clear(); 225 } 226 } 227 }); 228 } 229 fetchCurrentActiveOps()230 private void fetchCurrentActiveOps() { 231 List<AppOpsManager.PackageOps> packageOps = mAppOps.getPackagesForOps(OPS); 232 if (packageOps == null) { 233 return; 234 } 235 for (AppOpsManager.PackageOps op : packageOps) { 236 for (AppOpsManager.OpEntry entry : op.getOps()) { 237 for (Map.Entry<String, AppOpsManager.AttributedOpEntry> attributedOpEntry : 238 entry.getAttributedOpEntries().entrySet()) { 239 if (attributedOpEntry.getValue().isRunning()) { 240 onOpActiveChanged( 241 entry.getOpStr(), 242 op.getUid(), 243 op.getPackageName(), 244 /* attributionTag= */ attributedOpEntry.getKey(), 245 /* active= */ true, 246 // AppOpsManager doesn't have a way to fetch attribution flags or 247 // chain ID given an op entry, so default them to none. 248 AppOpsManager.ATTRIBUTION_FLAGS_NONE, 249 AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE); 250 } 251 } 252 } 253 } 254 } 255 256 /** 257 * Adds a callback that will get notifified when an AppOp of the type the controller tracks 258 * changes 259 * 260 * @param callback Callback to report changes 261 * @param opsCodes App Ops the callback is interested in checking 262 * 263 * @see #removeCallback(int[], Callback) 264 */ 265 @Override addCallback(int[] opsCodes, AppOpsController.Callback callback)266 public void addCallback(int[] opsCodes, AppOpsController.Callback callback) { 267 boolean added = false; 268 final int numCodes = opsCodes.length; 269 for (int i = 0; i < numCodes; i++) { 270 if (mCallbacksByCode.contains(opsCodes[i])) { 271 mCallbacksByCode.get(opsCodes[i]).add(callback); 272 added = true; 273 } else { 274 if (DEBUG) Log.wtf(TAG, "APP_OP " + opsCodes[i] + " not supported"); 275 } 276 } 277 if (added) mCallbacks.add(callback); 278 if (!mCallbacks.isEmpty()) setListening(true); 279 } 280 281 /** 282 * Removes a callback from those notified when an AppOp of the type the controller tracks 283 * changes 284 * 285 * @param callback Callback to stop reporting changes 286 * @param opsCodes App Ops the callback was interested in checking 287 * 288 * @see #addCallback(int[], Callback) 289 */ 290 @Override removeCallback(int[] opsCodes, AppOpsController.Callback callback)291 public void removeCallback(int[] opsCodes, AppOpsController.Callback callback) { 292 final int numCodes = opsCodes.length; 293 for (int i = 0; i < numCodes; i++) { 294 if (mCallbacksByCode.contains(opsCodes[i])) { 295 mCallbacksByCode.get(opsCodes[i]).remove(callback); 296 } 297 } 298 mCallbacks.remove(callback); 299 if (mCallbacks.isEmpty()) setListening(false); 300 } 301 302 // Find item number in list, only call if the list passed is locked getAppOpItemLocked(List<AppOpItem> appOpList, int code, int uid, String packageName)303 private AppOpItem getAppOpItemLocked(List<AppOpItem> appOpList, int code, int uid, 304 String packageName) { 305 final int itemsQ = appOpList.size(); 306 for (int i = 0; i < itemsQ; i++) { 307 AppOpItem item = appOpList.get(i); 308 if (item.getCode() == code && item.getUid() == uid 309 && item.getPackageName().equals(packageName)) { 310 return item; 311 } 312 } 313 return null; 314 } 315 updateActives(int code, int uid, String packageName, boolean active)316 private boolean updateActives(int code, int uid, String packageName, boolean active) { 317 synchronized (mActiveItems) { 318 AppOpItem item = getAppOpItemLocked(mActiveItems, code, uid, packageName); 319 if (item == null && active) { 320 item = new AppOpItem(code, uid, packageName, mClock.elapsedRealtime()); 321 if (isOpMicrophone(code)) { 322 item.setDisabled(isAnyRecordingPausedLocked(uid)); 323 } else if (isOpCamera(code)) { 324 item.setDisabled(mCameraDisabled); 325 } 326 mActiveItems.add(item); 327 if (DEBUG) Log.w(TAG, "Added item: " + item.toString()); 328 return !item.isDisabled(); 329 } else if (item != null && !active) { 330 mActiveItems.remove(item); 331 if (DEBUG) Log.w(TAG, "Removed item: " + item.toString()); 332 return true; 333 } 334 return false; 335 } 336 } 337 removeNoted(int code, int uid, String packageName)338 private void removeNoted(int code, int uid, String packageName) { 339 AppOpItem item; 340 synchronized (mNotedItems) { 341 item = getAppOpItemLocked(mNotedItems, code, uid, packageName); 342 if (item == null) return; 343 mNotedItems.remove(item); 344 if (DEBUG) Log.w(TAG, "Removed item: " + item.toString()); 345 } 346 boolean active; 347 // Check if the item is also active 348 synchronized (mActiveItems) { 349 active = getAppOpItemLocked(mActiveItems, code, uid, packageName) != null; 350 } 351 if (!active) { 352 notifySuscribersWorker(code, uid, packageName, false); 353 } 354 } 355 addNoted(int code, int uid, String packageName)356 private boolean addNoted(int code, int uid, String packageName) { 357 AppOpItem item; 358 boolean createdNew = false; 359 synchronized (mNotedItems) { 360 item = getAppOpItemLocked(mNotedItems, code, uid, packageName); 361 if (item == null) { 362 item = new AppOpItem(code, uid, packageName, mClock.elapsedRealtime()); 363 mNotedItems.add(item); 364 if (DEBUG) Log.w(TAG, "Added item: " + item.toString()); 365 createdNew = true; 366 } 367 } 368 // We should keep this so we make sure it cannot time out. 369 mBGHandler.removeCallbacksAndMessages(item); 370 mBGHandler.scheduleRemoval(item, NOTED_OP_TIME_DELAY_MS); 371 return createdNew; 372 } 373 isUserVisible(String packageName)374 private boolean isUserVisible(String packageName) { 375 return PermissionManager.shouldShowPackageForIndicatorCached(mContext, packageName); 376 } 377 378 @WorkerThread getActiveAppOps()379 public List<AppOpItem> getActiveAppOps() { 380 return getActiveAppOps(false); 381 } 382 383 /** 384 * Returns a copy of the list containing all the active AppOps that the controller tracks. 385 * 386 * Call from a worker thread as it may perform long operations. 387 * 388 * @return List of active AppOps information 389 */ 390 @WorkerThread getActiveAppOps(boolean showPaused)391 public List<AppOpItem> getActiveAppOps(boolean showPaused) { 392 return getActiveAppOpsForUser(UserHandle.USER_ALL, showPaused); 393 } 394 395 /** 396 * Returns a copy of the list containing all the active AppOps that the controller tracks, for 397 * a given user id. 398 * 399 * Call from a worker thread as it may perform long operations. 400 * 401 * @param userId User id to track, can be {@link UserHandle#USER_ALL} 402 * 403 * @return List of active AppOps information for that user id 404 */ 405 @WorkerThread getActiveAppOpsForUser(int userId, boolean showPaused)406 public List<AppOpItem> getActiveAppOpsForUser(int userId, boolean showPaused) { 407 Assert.isNotMainThread(); 408 List<AppOpItem> list = new ArrayList<>(); 409 synchronized (mActiveItems) { 410 final int numActiveItems = mActiveItems.size(); 411 for (int i = 0; i < numActiveItems; i++) { 412 AppOpItem item = mActiveItems.get(i); 413 if ((userId == UserHandle.USER_ALL 414 || UserHandle.getUserId(item.getUid()) == userId) 415 && isUserVisible(item.getPackageName()) 416 && (showPaused || !item.isDisabled())) { 417 list.add(item); 418 } 419 } 420 } 421 synchronized (mNotedItems) { 422 final int numNotedItems = mNotedItems.size(); 423 for (int i = 0; i < numNotedItems; i++) { 424 AppOpItem item = mNotedItems.get(i); 425 if ((userId == UserHandle.USER_ALL 426 || UserHandle.getUserId(item.getUid()) == userId) 427 && isUserVisible(item.getPackageName())) { 428 list.add(item); 429 } 430 } 431 } 432 return list; 433 } 434 notifySuscribers(int code, int uid, String packageName, boolean active)435 private void notifySuscribers(int code, int uid, String packageName, boolean active) { 436 mBGHandler.post(() -> notifySuscribersWorker(code, uid, packageName, active)); 437 } 438 439 /** 440 * Required to override, delegate to other. Should not be called. 441 */ onOpActiveChanged(String op, int uid, String packageName, boolean active)442 public void onOpActiveChanged(String op, int uid, String packageName, boolean active) { 443 onOpActiveChanged(op, uid, packageName, null, active, 444 AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE); 445 } 446 447 // Get active app ops, and check if their attributions are trusted 448 @Override onOpActiveChanged(String op, int uid, String packageName, String attributionTag, boolean active, int attributionFlags, int attributionChainId)449 public void onOpActiveChanged(String op, int uid, String packageName, String attributionTag, 450 boolean active, int attributionFlags, int attributionChainId) { 451 int code = AppOpsManager.strOpToOp(op); 452 if (DEBUG) { 453 Log.w(TAG, String.format("onActiveChanged(%d,%d,%s,%s,%d,%d)", code, uid, packageName, 454 Boolean.toString(active), attributionChainId, attributionFlags)); 455 } 456 if (active && attributionChainId != AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE 457 && attributionFlags != AppOpsManager.ATTRIBUTION_FLAGS_NONE 458 && (attributionFlags & AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR) == 0 459 && (attributionFlags & AppOpsManager.ATTRIBUTION_FLAG_TRUSTED) == 0) { 460 // if this attribution chain isn't trusted, and this isn't the accessor, do not show it. 461 return; 462 } 463 boolean activeChanged = updateActives(code, uid, packageName, active); 464 if (!activeChanged) return; // early return 465 // Check if the item is also noted, in that case, there's no update. 466 boolean alsoNoted; 467 synchronized (mNotedItems) { 468 alsoNoted = getAppOpItemLocked(mNotedItems, code, uid, packageName) != null; 469 } 470 // If active is true, we only send the update if the op is not actively noted (already true) 471 // If active is false, we only send the update if the op is not actively noted (prevent 472 // early removal) 473 if (!alsoNoted) { 474 notifySuscribers(code, uid, packageName, active); 475 } 476 } 477 478 @Override onOpNoted(int code, int uid, String packageName, String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.Mode int result)479 public void onOpNoted(int code, int uid, String packageName, 480 String attributionTag, @AppOpsManager.OpFlags int flags, 481 @AppOpsManager.Mode int result) { 482 if (DEBUG) { 483 Log.w(TAG, "Noted op: " + code + " with result " 484 + AppOpsManager.MODE_NAMES[result] + " for package " + packageName); 485 } 486 if (result != AppOpsManager.MODE_ALLOWED) return; 487 boolean notedAdded = addNoted(code, uid, packageName); 488 if (!notedAdded) return; // early return 489 boolean alsoActive; 490 synchronized (mActiveItems) { 491 alsoActive = getAppOpItemLocked(mActiveItems, code, uid, packageName) != null; 492 } 493 if (!alsoActive) { 494 notifySuscribers(code, uid, packageName, true); 495 } 496 } 497 notifySuscribersWorker(int code, int uid, String packageName, boolean active)498 private void notifySuscribersWorker(int code, int uid, String packageName, boolean active) { 499 if (mCallbacksByCode.contains(code) && isUserVisible(packageName)) { 500 if (DEBUG) Log.d(TAG, "Notifying of change in package " + packageName); 501 for (Callback cb: mCallbacksByCode.get(code)) { 502 cb.onActiveStateChanged(code, uid, packageName, active); 503 } 504 } 505 } 506 507 @Override dump(PrintWriter pw, String[] args)508 public void dump(PrintWriter pw, String[] args) { 509 pw.println("AppOpsController state:"); 510 pw.println(" Listening: " + mListening); 511 pw.println(" Active Items:"); 512 for (int i = 0; i < mActiveItems.size(); i++) { 513 final AppOpItem item = mActiveItems.get(i); 514 pw.print(" "); pw.println(item.toString()); 515 } 516 pw.println(" Noted Items:"); 517 for (int i = 0; i < mNotedItems.size(); i++) { 518 final AppOpItem item = mNotedItems.get(i); 519 pw.print(" "); pw.println(item.toString()); 520 } 521 522 } 523 isAnyRecordingPausedLocked(int uid)524 private boolean isAnyRecordingPausedLocked(int uid) { 525 if (mMicMuted) { 526 return true; 527 } 528 List<AudioRecordingConfiguration> configs = mRecordingsByUid.get(uid); 529 if (configs == null) return false; 530 int configsNum = configs.size(); 531 for (int i = 0; i < configsNum; i++) { 532 AudioRecordingConfiguration config = configs.get(i); 533 if (config.isClientSilenced()) return true; 534 } 535 return false; 536 } 537 updateSensorDisabledStatus()538 private void updateSensorDisabledStatus() { 539 synchronized (mActiveItems) { 540 int size = mActiveItems.size(); 541 for (int i = 0; i < size; i++) { 542 AppOpItem item = mActiveItems.get(i); 543 544 boolean paused = false; 545 if (isOpMicrophone(item.getCode())) { 546 paused = isAnyRecordingPausedLocked(item.getUid()); 547 } else if (isOpCamera(item.getCode())) { 548 paused = mCameraDisabled; 549 } 550 551 if (item.isDisabled() != paused) { 552 item.setDisabled(paused); 553 notifySuscribers( 554 item.getCode(), 555 item.getUid(), 556 item.getPackageName(), 557 !item.isDisabled() 558 ); 559 } 560 } 561 } 562 } 563 564 private AudioManager.AudioRecordingCallback mAudioRecordingCallback = 565 new AudioManager.AudioRecordingCallback() { 566 @Override 567 public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) { 568 synchronized (mActiveItems) { 569 mRecordingsByUid.clear(); 570 final int recordingsCount = configs.size(); 571 for (int i = 0; i < recordingsCount; i++) { 572 AudioRecordingConfiguration recording = configs.get(i); 573 574 ArrayList<AudioRecordingConfiguration> recordings = mRecordingsByUid.get( 575 recording.getClientUid()); 576 if (recordings == null) { 577 recordings = new ArrayList<>(); 578 mRecordingsByUid.put(recording.getClientUid(), recordings); 579 } 580 recordings.add(recording); 581 } 582 } 583 updateSensorDisabledStatus(); 584 } 585 }; 586 587 @Override onReceive(Context context, Intent intent)588 public void onReceive(Context context, Intent intent) { 589 mMicMuted = mAudioManager.isMicrophoneMute() 590 || mSensorPrivacyController.isSensorBlocked(MICROPHONE); 591 updateSensorDisabledStatus(); 592 } 593 594 @Override onSensorBlockedChanged(int sensor, boolean blocked)595 public void onSensorBlockedChanged(int sensor, boolean blocked) { 596 mBGHandler.post(() -> { 597 if (sensor == CAMERA) { 598 mCameraDisabled = blocked; 599 } else if (sensor == MICROPHONE) { 600 mMicMuted = mAudioManager.isMicrophoneMute() || blocked; 601 } 602 updateSensorDisabledStatus(); 603 }); 604 } 605 606 @Override isMicMuted()607 public boolean isMicMuted() { 608 return mMicMuted; 609 } 610 isOpCamera(int op)611 private boolean isOpCamera(int op) { 612 for (int i = 0; i < OPS_CAMERA.length; i++) { 613 if (op == OPS_CAMERA[i]) return true; 614 } 615 return false; 616 } 617 isOpMicrophone(int op)618 private boolean isOpMicrophone(int op) { 619 for (int i = 0; i < OPS_MIC.length; i++) { 620 if (op == OPS_MIC[i]) return true; 621 } 622 return false; 623 } 624 625 protected class H extends Handler { H(Looper looper)626 H(Looper looper) { 627 super(looper); 628 } 629 scheduleRemoval(AppOpItem item, long timeToRemoval)630 public void scheduleRemoval(AppOpItem item, long timeToRemoval) { 631 removeCallbacksAndMessages(item); 632 postDelayed(new Runnable() { 633 @Override 634 public void run() { 635 removeNoted(item.getCode(), item.getUid(), item.getPackageName()); 636 } 637 }, item, timeToRemoval); 638 } 639 } 640 } 641