VrManagerService.java revision 0568f28ac3ba83ec268b54d16a8b19c1fcaf9c89
1/** 2 * Copyright (C) 2015 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 */ 16package com.android.server.vr; 17 18import android.Manifest; 19import android.app.AppOpsManager; 20import android.app.NotificationManager; 21import android.annotation.NonNull; 22import android.content.ComponentName; 23import android.content.ContentResolver; 24import android.content.Context; 25import android.content.pm.ApplicationInfo; 26import android.content.pm.PackageManager; 27import android.content.pm.PackageManager.NameNotFoundException; 28import android.os.Binder; 29import android.os.Handler; 30import android.os.IBinder; 31import android.os.IInterface; 32import android.os.Looper; 33import android.os.Message; 34import android.os.RemoteCallbackList; 35import android.os.RemoteException; 36import android.os.UserHandle; 37import android.provider.Settings; 38import android.service.notification.NotificationListenerService; 39import android.service.vr.IVrListener; 40import android.service.vr.IVrManager; 41import android.service.vr.IVrStateCallbacks; 42import android.service.vr.VrListenerService; 43import android.util.ArraySet; 44import android.util.Slog; 45 46import com.android.internal.R; 47import com.android.server.SystemService; 48import com.android.server.utils.ManagedApplicationService.PendingEvent; 49import com.android.server.vr.EnabledComponentsObserver.EnabledComponentChangeListener; 50import com.android.server.utils.ManagedApplicationService; 51import com.android.server.utils.ManagedApplicationService.BinderChecker; 52 53import java.lang.StringBuilder; 54import java.lang.ref.WeakReference; 55import java.util.ArrayList; 56import java.util.Collection; 57import java.util.Objects; 58import java.util.Set; 59 60/** 61 * Service tracking whether VR mode is active, and notifying listening services of state changes. 62 * <p/> 63 * Services running in system server may modify the state of VrManagerService via the interface in 64 * VrManagerInternal, and may register to receive callbacks when the system VR mode changes via the 65 * interface given in VrStateListener. 66 * <p/> 67 * Device vendors may choose to receive VR state changes by implementing the VR mode HAL, e.g.: 68 * hardware/libhardware/modules/vr 69 * <p/> 70 * In general applications may enable or disable VR mode by calling 71 * {@link android.app.Activity#setVrModeEnabled)}. An application may also implement a service to 72 * be run while in VR mode by implementing {@link android.service.vr.VrListenerService}. 73 * 74 * @see {@link android.service.vr.VrListenerService} 75 * @see {@link com.android.server.vr.VrManagerInternal} 76 * @see {@link com.android.server.vr.VrStateListener} 77 * 78 * @hide 79 */ 80public class VrManagerService extends SystemService implements EnabledComponentChangeListener{ 81 82 public static final String TAG = "VrManagerService"; 83 84 public static final String VR_MANAGER_BINDER_SERVICE = "vrmanager"; 85 86 private static native void initializeNative(); 87 private static native void setVrModeNative(boolean enabled); 88 89 private final Object mLock = new Object(); 90 91 private final IBinder mOverlayToken = new Binder(); 92 93 // State protected by mLock 94 private boolean mVrModeEnabled; 95 private EnabledComponentsObserver mComponentObserver; 96 private ManagedApplicationService mCurrentVrService; 97 private Context mContext; 98 private ComponentName mCurrentVrModeComponent; 99 private int mCurrentVrModeUser; 100 private boolean mWasDefaultGranted; 101 private boolean mGuard; 102 private final RemoteCallbackList<IVrStateCallbacks> mRemoteCallbacks = 103 new RemoteCallbackList<>(); 104 private final ArraySet<String> mPreviousToggledListenerSettings = new ArraySet<>(); 105 private String mPreviousNotificationPolicyAccessPackage; 106 private String mPreviousCoarseLocationPackage; 107 private String mPreviousManageOverlayPackage; 108 109 private static final int MSG_VR_STATE_CHANGE = 0; 110 111 private final Handler mHandler = new Handler() { 112 @Override 113 public void handleMessage(Message msg) { 114 switch(msg.what) { 115 case MSG_VR_STATE_CHANGE : { 116 boolean state = (msg.arg1 == 1); 117 int i = mRemoteCallbacks.beginBroadcast(); 118 while (i > 0) { 119 i--; 120 try { 121 mRemoteCallbacks.getBroadcastItem(i).onVrStateChanged(state); 122 } catch (RemoteException e) { 123 // Noop 124 } 125 } 126 mRemoteCallbacks.finishBroadcast(); 127 } break; 128 default : 129 throw new IllegalStateException("Unknown message type: " + msg.what); 130 } 131 } 132 }; 133 134 private static final BinderChecker sBinderChecker = new BinderChecker() { 135 @Override 136 public IInterface asInterface(IBinder binder) { 137 return IVrListener.Stub.asInterface(binder); 138 } 139 140 @Override 141 public boolean checkType(IInterface service) { 142 return service instanceof IVrListener; 143 } 144 }; 145 146 /** 147 * Called when a user, package, or setting changes that could affect whether or not the 148 * currently bound VrListenerService is changed. 149 */ 150 @Override 151 public void onEnabledComponentChanged() { 152 synchronized (mLock) { 153 if (mCurrentVrService == null) { 154 return; // No active services 155 } 156 157 // There is an active service, update it if needed 158 updateCurrentVrServiceLocked(mVrModeEnabled, mCurrentVrService.getComponent(), 159 mCurrentVrService.getUserId(), null); 160 } 161 } 162 163 private final IVrManager mVrManager = new IVrManager.Stub() { 164 165 @Override 166 public void registerListener(IVrStateCallbacks cb) { 167 enforceCallerPermission(Manifest.permission.ACCESS_VR_MANAGER); 168 if (cb == null) { 169 throw new IllegalArgumentException("Callback binder object is null."); 170 } 171 172 VrManagerService.this.addStateCallback(cb); 173 } 174 175 @Override 176 public void unregisterListener(IVrStateCallbacks cb) { 177 enforceCallerPermission(Manifest.permission.ACCESS_VR_MANAGER); 178 if (cb == null) { 179 throw new IllegalArgumentException("Callback binder object is null."); 180 } 181 182 VrManagerService.this.removeStateCallback(cb); 183 } 184 185 @Override 186 public boolean getVrModeState() { 187 return VrManagerService.this.getVrMode(); 188 } 189 190 }; 191 192 private void enforceCallerPermission(String permission) { 193 if (mContext.checkCallingOrSelfPermission(permission) 194 != PackageManager.PERMISSION_GRANTED) { 195 throw new SecurityException("Caller does not hold the permission " + permission); 196 } 197 } 198 199 /** 200 * Implementation of VrManagerInternal. Callable only from system services. 201 */ 202 private final class LocalService extends VrManagerInternal { 203 @Override 204 public void setVrMode(boolean enabled, ComponentName packageName, int userId, 205 ComponentName callingPackage) { 206 VrManagerService.this.setVrMode(enabled, packageName, userId, callingPackage); 207 } 208 209 @Override 210 public boolean isCurrentVrListener(String packageName, int userId) { 211 return VrManagerService.this.isCurrentVrListener(packageName, userId); 212 } 213 214 @Override 215 public int hasVrPackage(ComponentName packageName, int userId) { 216 return VrManagerService.this.hasVrPackage(packageName, userId); 217 } 218 } 219 220 public VrManagerService(Context context) { 221 super(context); 222 } 223 224 @Override 225 public void onStart() { 226 synchronized(mLock) { 227 initializeNative(); 228 mContext = getContext(); 229 } 230 231 publishLocalService(VrManagerInternal.class, new LocalService()); 232 publishBinderService(VR_MANAGER_BINDER_SERVICE, mVrManager.asBinder()); 233 } 234 235 @Override 236 public void onBootPhase(int phase) { 237 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { 238 synchronized (mLock) { 239 Looper looper = Looper.getMainLooper(); 240 Handler handler = new Handler(looper); 241 ArrayList<EnabledComponentChangeListener> listeners = new ArrayList<>(); 242 listeners.add(this); 243 mComponentObserver = EnabledComponentsObserver.build(mContext, handler, 244 Settings.Secure.ENABLED_VR_LISTENERS, looper, 245 android.Manifest.permission.BIND_VR_LISTENER_SERVICE, 246 VrListenerService.SERVICE_INTERFACE, mLock, listeners); 247 248 mComponentObserver.rebuildAll(); 249 } 250 } 251 } 252 253 @Override 254 public void onStartUser(int userHandle) { 255 synchronized (mLock) { 256 mComponentObserver.onUsersChanged(); 257 } 258 } 259 260 @Override 261 public void onSwitchUser(int userHandle) { 262 synchronized (mLock) { 263 mComponentObserver.onUsersChanged(); 264 } 265 266 } 267 268 @Override 269 public void onStopUser(int userHandle) { 270 synchronized (mLock) { 271 mComponentObserver.onUsersChanged(); 272 } 273 274 } 275 276 @Override 277 public void onCleanupUser(int userHandle) { 278 synchronized (mLock) { 279 mComponentObserver.onUsersChanged(); 280 } 281 } 282 283 private void updateOverlayStateLocked(ComponentName exemptedComponent) { 284 final long identity = Binder.clearCallingIdentity(); 285 try { 286 AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class); 287 if (appOpsManager != null) { 288 String[] exemptions = (exemptedComponent == null) ? new String[0] : 289 new String[] { exemptedComponent.getPackageName() }; 290 291 appOpsManager.setUserRestriction(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, 292 mVrModeEnabled, mOverlayToken, exemptions); 293 } 294 } finally { 295 Binder.restoreCallingIdentity(identity); 296 } 297 } 298 299 /** 300 * Send VR mode changes (if the mode state has changed), and update the bound/unbound state of 301 * the currently selected VR listener service. If the component selected for the VR listener 302 * service has changed, unbind the previous listener and bind the new listener (if enabled). 303 * <p/> 304 * Note: Must be called while holding {@code mLock}. 305 * 306 * @param enabled new state for VR mode. 307 * @param component new component to be bound as a VR listener. 308 * @param userId user owning the component to be bound. 309 * @param calling the component currently using VR mode, or null to leave unchanged. 310 * 311 * @return {@code true} if the component/user combination specified is valid. 312 */ 313 private boolean updateCurrentVrServiceLocked(boolean enabled, @NonNull ComponentName component, 314 int userId, ComponentName calling) { 315 316 boolean sendUpdatedCaller = false; 317 final long identity = Binder.clearCallingIdentity(); 318 try { 319 320 boolean validUserComponent = (mComponentObserver.isValid(component, userId) == 321 EnabledComponentsObserver.NO_ERROR); 322 323 // Always send mode change events. 324 changeVrModeLocked(enabled, (enabled && validUserComponent) ? component : null); 325 326 if (!enabled || !validUserComponent) { 327 // Unbind whatever is running 328 if (mCurrentVrService != null) { 329 Slog.i(TAG, "Disconnecting " + mCurrentVrService.getComponent() + " for user " + 330 mCurrentVrService.getUserId()); 331 mCurrentVrService.disconnect(); 332 disableImpliedPermissionsLocked(mCurrentVrService.getComponent(), 333 new UserHandle(mCurrentVrService.getUserId())); 334 mCurrentVrService = null; 335 } 336 } else { 337 if (mCurrentVrService != null) { 338 // Unbind any running service that doesn't match the component/user selection 339 if (mCurrentVrService.disconnectIfNotMatching(component, userId)) { 340 Slog.i(TAG, "Disconnecting " + mCurrentVrService.getComponent() + 341 " for user " + mCurrentVrService.getUserId()); 342 disableImpliedPermissionsLocked(mCurrentVrService.getComponent(), 343 new UserHandle(mCurrentVrService.getUserId())); 344 createAndConnectService(component, userId); 345 enableImpliedPermissionsLocked(mCurrentVrService.getComponent(), 346 new UserHandle(mCurrentVrService.getUserId())); 347 sendUpdatedCaller = true; 348 } 349 // The service with the correct component/user is bound 350 } else { 351 // Nothing was previously running, bind a new service 352 createAndConnectService(component, userId); 353 enableImpliedPermissionsLocked(mCurrentVrService.getComponent(), 354 new UserHandle(mCurrentVrService.getUserId())); 355 sendUpdatedCaller = true; 356 } 357 } 358 359 if (calling != null && !Objects.equals(calling, mCurrentVrModeComponent)) { 360 mCurrentVrModeComponent = calling; 361 mCurrentVrModeUser = userId; 362 sendUpdatedCaller = true; 363 } 364 365 if (mCurrentVrService != null && sendUpdatedCaller) { 366 final ComponentName c = mCurrentVrModeComponent; 367 mCurrentVrService.sendEvent(new PendingEvent() { 368 @Override 369 public void runEvent(IInterface service) throws RemoteException { 370 IVrListener l = (IVrListener) service; 371 l.focusedActivityChanged(c); 372 } 373 }); 374 } 375 376 return validUserComponent; 377 } finally { 378 Binder.restoreCallingIdentity(identity); 379 } 380 } 381 382 /** 383 * Enable the permission given in {@link #IMPLIED_VR_LISTENER_PERMISSIONS} for the given 384 * component package and user. 385 * 386 * @param component the component whose package should be enabled. 387 * @param userId the user that owns the given component. 388 */ 389 private void enableImpliedPermissionsLocked(ComponentName component, UserHandle userId) { 390 if (mGuard) { 391 // Impossible 392 throw new IllegalStateException("Enabling permissions without disabling."); 393 } 394 mGuard = true; 395 396 PackageManager pm = mContext.getPackageManager(); 397 398 String pName = component.getPackageName(); 399 if (pm == null) { 400 Slog.e(TAG, "Couldn't set implied permissions for " + pName + 401 ", PackageManager isn't running"); 402 return; 403 } 404 405 ApplicationInfo info = null; 406 try { 407 info = pm.getApplicationInfo(pName, PackageManager.GET_META_DATA); 408 } catch (NameNotFoundException e) { 409 } 410 411 if (info == null) { 412 Slog.e(TAG, "Couldn't set implied permissions for " + pName + ", no such package."); 413 return; 414 } 415 416 if (!(info.isSystemApp() || info.isUpdatedSystemApp())) { 417 return; // Application is not pre-installed, avoid setting implied permissions 418 } 419 420 mWasDefaultGranted = true; 421 422 grantCoarseLocationAccess(pName, userId); 423 grantOverlayAccess(pName, userId); 424 grantNotificationPolicyAccess(pName); 425 grantNotificationListenerAccess(pName, userId); 426 } 427 428 /** 429 * Disable the permission given in {@link #IMPLIED_VR_LISTENER_PERMISSIONS} for the given 430 * component package and user. 431 * 432 * @param component the component whose package should be disabled. 433 * @param userId the user that owns the given component. 434 */ 435 private void disableImpliedPermissionsLocked(ComponentName component, UserHandle userId) { 436 if (!mGuard) { 437 // Impossible 438 throw new IllegalStateException("Disabling permissions without enabling."); 439 } 440 mGuard = false; 441 442 PackageManager pm = mContext.getPackageManager(); 443 444 if (pm == null) { 445 Slog.e(TAG, "Couldn't remove implied permissions for " + component + 446 ", PackageManager isn't running"); 447 return; 448 } 449 450 String pName = component.getPackageName(); 451 if (mWasDefaultGranted) { 452 revokeCoarseLocationAccess(userId); 453 revokeOverlayAccess(userId); 454 revokeNotificationPolicyAccess(pName); 455 revokeNotificiationListenerAccess(); 456 mWasDefaultGranted = false; 457 } 458 459 } 460 461 private void grantCoarseLocationAccess(String pkg, UserHandle userId) { 462 PackageManager pm = mContext.getPackageManager(); 463 boolean prev = (PackageManager.PERMISSION_GRANTED == 464 pm.checkPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION, pkg)); 465 mPreviousCoarseLocationPackage = null; 466 if (!prev) { 467 pm.grantRuntimePermission(pkg, android.Manifest.permission.ACCESS_COARSE_LOCATION, 468 userId); 469 mPreviousCoarseLocationPackage = pkg; 470 } 471 } 472 473 private void revokeCoarseLocationAccess(UserHandle userId) { 474 PackageManager pm = mContext.getPackageManager(); 475 if (mPreviousCoarseLocationPackage != null) { 476 pm.revokeRuntimePermission(mPreviousCoarseLocationPackage, 477 android.Manifest.permission.ACCESS_COARSE_LOCATION, userId); 478 mPreviousCoarseLocationPackage = null; 479 } 480 } 481 482 private void grantOverlayAccess(String pkg, UserHandle userId) { 483 PackageManager pm = mContext.getPackageManager(); 484 boolean prev = (PackageManager.PERMISSION_GRANTED == 485 pm.checkPermission(android.Manifest.permission.SYSTEM_ALERT_WINDOW, pkg)); 486 mPreviousManageOverlayPackage = null; 487 if (!prev) { 488 pm.grantRuntimePermission(pkg, android.Manifest.permission.SYSTEM_ALERT_WINDOW, 489 userId); 490 mPreviousManageOverlayPackage = pkg; 491 } 492 } 493 494 private void revokeOverlayAccess(UserHandle userId) { 495 PackageManager pm = mContext.getPackageManager(); 496 if (mPreviousManageOverlayPackage != null) { 497 pm.revokeRuntimePermission(mPreviousManageOverlayPackage, 498 android.Manifest.permission.SYSTEM_ALERT_WINDOW, userId); 499 mPreviousManageOverlayPackage = null; 500 } 501 } 502 503 private void grantNotificationPolicyAccess(String pkg) { 504 NotificationManager nm = mContext.getSystemService(NotificationManager.class); 505 boolean prev = nm.isNotificationPolicyAccessGrantedForPackage(pkg); 506 mPreviousNotificationPolicyAccessPackage = null; 507 if (!prev) { 508 mPreviousNotificationPolicyAccessPackage = pkg; 509 nm.setNotificationPolicyAccessGranted(pkg, true); 510 } 511 } 512 513 private void revokeNotificationPolicyAccess(String pkg) { 514 NotificationManager nm = mContext.getSystemService(NotificationManager.class); 515 if (mPreviousNotificationPolicyAccessPackage != null) { 516 if (mPreviousNotificationPolicyAccessPackage.equals(pkg)) { 517 // Remove any DND zen rules possibly created by the package. 518 nm.removeAutomaticZenRules(mPreviousNotificationPolicyAccessPackage); 519 // Remove Notification Policy Access. 520 nm.setNotificationPolicyAccessGranted(mPreviousNotificationPolicyAccessPackage, false); 521 mPreviousNotificationPolicyAccessPackage = null; 522 } else { 523 Slog.e(TAG, "Couldn't remove Notification Policy Access for package: " + pkg); 524 } 525 } 526 } 527 528 private void grantNotificationListenerAccess(String pkg, UserHandle userId) { 529 PackageManager pm = mContext.getPackageManager(); 530 ArraySet<ComponentName> possibleServices = EnabledComponentsObserver.loadComponentNames(pm, 531 userId.getIdentifier(), NotificationListenerService.SERVICE_INTERFACE, 532 android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE); 533 ContentResolver resolver = mContext.getContentResolver(); 534 535 ArraySet<String> current = getCurrentNotifListeners(resolver); 536 537 mPreviousToggledListenerSettings.clear(); 538 539 for (ComponentName c : possibleServices) { 540 String flatName = c.flattenToString(); 541 if (Objects.equals(c.getPackageName(), pkg) 542 && !current.contains(flatName)) { 543 mPreviousToggledListenerSettings.add(flatName); 544 current.add(flatName); 545 } 546 } 547 548 if (current.size() > 0) { 549 String flatSettings = formatSettings(current); 550 Settings.Secure.putString(resolver, Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, 551 flatSettings); 552 } 553 } 554 555 private void revokeNotificiationListenerAccess() { 556 if (mPreviousToggledListenerSettings.isEmpty()) { 557 return; 558 } 559 560 ContentResolver resolver = mContext.getContentResolver(); 561 ArraySet<String> current = getCurrentNotifListeners(resolver); 562 563 current.removeAll(mPreviousToggledListenerSettings); 564 mPreviousToggledListenerSettings.clear(); 565 566 String flatSettings = formatSettings(current); 567 Settings.Secure.putString(resolver, Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, 568 flatSettings); 569 } 570 571 private ArraySet<String> getCurrentNotifListeners(ContentResolver resolver) { 572 String flat = Settings.Secure.getString(resolver, 573 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS); 574 575 ArraySet<String> current = new ArraySet<>(); 576 if (flat != null) { 577 String[] allowed = flat.split(":"); 578 for (String s : allowed) { 579 current.add(s); 580 } 581 } 582 return current; 583 } 584 585 private static String formatSettings(Collection<String> c) { 586 if (c == null || c.isEmpty()) { 587 return ""; 588 } 589 590 StringBuilder b = new StringBuilder(); 591 boolean start = true; 592 for (String s : c) { 593 if ("".equals(s)) { 594 continue; 595 } 596 if (!start) { 597 b.append(':'); 598 } 599 b.append(s); 600 start = false; 601 } 602 return b.toString(); 603 } 604 605 606 607 private void createAndConnectService(@NonNull ComponentName component, int userId) { 608 mCurrentVrService = VrManagerService.create(mContext, component, userId); 609 mCurrentVrService.connect(); 610 Slog.i(TAG, "Connecting " + component + " for user " + userId); 611 } 612 613 /** 614 * Send VR mode change callbacks to HAL and system services if mode has actually changed. 615 * <p/> 616 * Note: Must be called while holding {@code mLock}. 617 * 618 * @param enabled new state of the VR mode. 619 * @param exemptedComponent a component to exempt from AppOps restrictions for overlays. 620 */ 621 private void changeVrModeLocked(boolean enabled, ComponentName exemptedComponent) { 622 if (mVrModeEnabled != enabled) { 623 mVrModeEnabled = enabled; 624 625 // Log mode change event. 626 Slog.i(TAG, "VR mode " + ((mVrModeEnabled) ? "enabled" : "disabled")); 627 setVrModeNative(mVrModeEnabled); 628 629 updateOverlayStateLocked(exemptedComponent); 630 onVrModeChangedLocked(); 631 } 632 } 633 634 /** 635 * Notify system services of VR mode change. 636 * <p/> 637 * Note: Must be called while holding {@code mLock}. 638 */ 639 private void onVrModeChangedLocked() { 640 mHandler.sendMessage(mHandler.obtainMessage(MSG_VR_STATE_CHANGE, 641 (mVrModeEnabled) ? 1 : 0, 0)); 642 } 643 644 /** 645 * Helper function for making ManagedApplicationService instances. 646 */ 647 private static ManagedApplicationService create(@NonNull Context context, 648 @NonNull ComponentName component, int userId) { 649 return ManagedApplicationService.build(context, component, userId, 650 R.string.vr_listener_binding_label, Settings.ACTION_VR_LISTENER_SETTINGS, 651 sBinderChecker); 652 } 653 654 /* 655 * Implementation of VrManagerInternal calls. These are callable from system services. 656 */ 657 658 private boolean setVrMode(boolean enabled, @NonNull ComponentName targetPackageName, 659 int userId, @NonNull ComponentName callingPackage) { 660 synchronized (mLock) { 661 return updateCurrentVrServiceLocked(enabled, targetPackageName, userId, callingPackage); 662 } 663 } 664 665 private int hasVrPackage(@NonNull ComponentName targetPackageName, int userId) { 666 synchronized (mLock) { 667 return mComponentObserver.isValid(targetPackageName, userId); 668 } 669 } 670 671 private boolean isCurrentVrListener(String packageName, int userId) { 672 synchronized (mLock) { 673 if (mCurrentVrService == null) { 674 return false; 675 } 676 return mCurrentVrService.getComponent().getPackageName().equals(packageName) && 677 userId == mCurrentVrService.getUserId(); 678 } 679 } 680 681 /* 682 * Implementation of IVrManager calls. 683 */ 684 685 private void addStateCallback(IVrStateCallbacks cb) { 686 mRemoteCallbacks.register(cb); 687 } 688 689 private void removeStateCallback(IVrStateCallbacks cb) { 690 mRemoteCallbacks.unregister(cb); 691 } 692 693 private boolean getVrMode() { 694 synchronized (mLock) { 695 return mVrModeEnabled; 696 } 697 } 698} 699