Grant default permissions to preinstalled VrListenerServices.

- While explicitly bound, the package for a single pre-installed
  VrListenerService will be granted permission to access
  notification policy, be bound as a notification listener service,
  and draw system overlays.

Bug: 22855417
Change-Id: I568d5d9c032e0926e47c8ef4b46e3910b6bdf766
diff --git a/services/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
similarity index 94%
rename from services/core/java/com/android/server/SystemConfig.java
rename to core/java/com/android/server/SystemConfig.java
index 30e0ceb..07912cd 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -19,6 +19,7 @@
 import static com.android.internal.util.ArrayUtils.appendInt;
 
 import android.app.ActivityManager;
+import android.content.ComponentName;
 import android.content.pm.FeatureInfo;
 import android.content.pm.PackageManager;
 import android.os.Environment;
@@ -115,6 +116,9 @@
     // These are the packages that should not run under system user
     final ArraySet<String> mSystemUserBlacklistedApps = new ArraySet<>();
 
+    // These are the components that are enabled by default as VR mode listener services.
+    final ArraySet<ComponentName> mDefaultVrComponents = new ArraySet<>();
+
     public static SystemConfig getInstance() {
         synchronized (SystemConfig.class) {
             if (sInstance == null) {
@@ -168,6 +172,10 @@
         return mSystemUserBlacklistedApps;
     }
 
+    public ArraySet<ComponentName> getDefaultVrComponents() {
+        return mDefaultVrComponents;
+    }
+
     SystemConfig() {
         // Read configuration from system
         readPermissions(Environment.buildPath(
@@ -431,6 +439,19 @@
                         mSystemUserBlacklistedApps.add(pkgname);
                     }
                     XmlUtils.skipCurrentTag(parser);
+                } else if ("default-enabled-vr-app".equals(name) && allowAppConfigs) {
+                    String pkgname = parser.getAttributeValue(null, "package");
+                    String clsname = parser.getAttributeValue(null, "class");
+                    if (pkgname == null) {
+                        Slog.w(TAG, "<default-enabled-vr-app without package in " + permFile
+                                + " at " + parser.getPositionDescription());
+                    } else if (clsname == null) {
+                        Slog.w(TAG, "<default-enabled-vr-app without class in " + permFile
+                                + " at " + parser.getPositionDescription());
+                    } else {
+                        mDefaultVrComponents.add(new ComponentName(pkgname, clsname));
+                    }
+                    XmlUtils.skipCurrentTag(parser);
                 } else {
                     XmlUtils.skipCurrentTag(parser);
                     continue;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 987b5ea..61c9f2b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -21,6 +21,7 @@
 import android.app.AppGlobals;
 import android.app.backup.BackupManager;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.ContentProvider;
 import android.content.ContentValues;
 import android.content.Context;
@@ -63,6 +64,7 @@
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.os.BackgroundThread;
 import com.android.providers.settings.SettingsState.Setting;
+import com.android.server.SystemConfig;
 
 import java.io.File;
 import java.io.FileDescriptor;
@@ -1940,7 +1942,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 125;
+            private static final int SETTINGS_VERSION = 126;
 
             private final int mUserId;
 
@@ -2136,6 +2138,35 @@
                     currentVersion = 125;
                 }
 
+                if (currentVersion == 125) {
+                    // Version 125: Allow OEMs to set the default VR service.
+                    final SettingsState secureSettings = getSecureSettingsLocked(userId);
+
+                    Setting currentSetting = secureSettings.getSettingLocked(
+                            Settings.Secure.ENABLED_VR_LISTENERS);
+                    if (currentSetting == null) {
+                        ArraySet<ComponentName> l =
+                                SystemConfig.getInstance().getDefaultVrComponents();
+
+                        if (l != null && !l.isEmpty()) {
+                            StringBuilder b = new StringBuilder();
+                            boolean start = true;
+                            for (ComponentName c : l) {
+                                if (!start) {
+                                    b.append(':');
+                                }
+                                b.append(c.flattenToString());
+                                start = false;
+                            }
+                            secureSettings.insertSettingLocked(
+                                    Settings.Secure.ENABLED_VR_LISTENERS, b.toString(),
+                                    SettingsState.SYSTEM_PACKAGE_NAME);
+                        }
+
+                    }
+                    currentVersion = 126;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 // Return the current version.
diff --git a/services/core/java/com/android/server/vr/EnabledComponentsObserver.java b/services/core/java/com/android/server/vr/EnabledComponentsObserver.java
index 1363fb9..eb926c1 100644
--- a/services/core/java/com/android/server/vr/EnabledComponentsObserver.java
+++ b/services/core/java/com/android/server/vr/EnabledComponentsObserver.java
@@ -227,10 +227,11 @@
         return userIds;
     }
 
-    private ArraySet<ComponentName> loadComponentNamesForUser(int userId) {
+    public static ArraySet<ComponentName> loadComponentNames(PackageManager pm, int userId,
+            String serviceName, String permissionName) {
+
         ArraySet<ComponentName> installed = new ArraySet<>();
-        PackageManager pm = mContext.getPackageManager();
-        Intent queryIntent = new Intent(mServiceName);
+        Intent queryIntent = new Intent(serviceName);
         List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
                 queryIntent,
                 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
@@ -241,10 +242,10 @@
                 ServiceInfo info = resolveInfo.serviceInfo;
 
                 ComponentName component = new ComponentName(info.packageName, info.name);
-                if (!mServicePermission.equals(info.permission)) {
+                if (!permissionName.equals(info.permission)) {
                     Slog.w(TAG, "Skipping service " + info.packageName + "/" + info.name
                             + ": it does not require the permission "
-                            + mServicePermission);
+                            + permissionName);
                     continue;
                 }
                 installed.add(component);
@@ -253,6 +254,11 @@
         return installed;
     }
 
+    private ArraySet<ComponentName> loadComponentNamesForUser(int userId) {
+        return loadComponentNames(mContext.getPackageManager(), userId, mServiceName,
+                mServicePermission);
+    }
+
     private ArraySet<ComponentName> loadComponentNamesFromSetting(String settingName,
             int userId) {
         final ContentResolver cr = mContext.getContentResolver();
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index 6bf949c..5849b10 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -16,16 +16,23 @@
 package com.android.server.vr;
 
 import android.app.AppOpsManager;
+import android.app.NotificationManager;
 import android.annotation.NonNull;
-import android.content.Context;
 import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IInterface;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.provider.Settings;
+import android.service.notification.NotificationListenerService;
 import android.service.vr.IVrListener;
 import android.service.vr.VrListenerService;
 import android.util.ArraySet;
@@ -38,7 +45,9 @@
 import com.android.server.utils.ManagedApplicationService;
 import com.android.server.utils.ManagedApplicationService.BinderChecker;
 
+import java.lang.StringBuilder;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Objects;
 import java.util.Set;
 
@@ -53,8 +62,8 @@
  *  hardware/libhardware/modules/vr
  * <p/>
  * In general applications may enable or disable VR mode by calling
- * {@link android.app.Activity#setVrMode)}.  An application may also implement a service to be run
- * while in VR mode by implementing {@link android.service.vr.VrListenerService}.
+ * {@link android.app.Activity#setVrModeEnabled)}.  An application may also implement a service to
+ * be run while in VR mode by implementing {@link android.service.vr.VrListenerService}.
  *
  * @see {@link android.service.vr.VrListenerService}
  * @see {@link com.android.server.vr.VrManagerInternal}
@@ -74,13 +83,18 @@
     private final IBinder mOverlayToken = new Binder();
 
     // State protected by mLock
-    private boolean mVrModeEnabled = false;
+    private boolean mVrModeEnabled;
     private final Set<VrStateListener> mListeners = new ArraySet<>();
     private EnabledComponentsObserver mComponentObserver;
     private ManagedApplicationService mCurrentVrService;
     private Context mContext;
     private ComponentName mCurrentVrModeComponent;
     private int mCurrentVrModeUser;
+    private boolean mWasDefaultGranted;
+    private boolean mGuard;
+    private final ArraySet<String> mPreviousToggledListenerSettings = new ArraySet<>();
+    private String mPreviousNotificationPolicyAccessPackage;
+    private String mPreviousManageOverlayPackage;
 
     private static final BinderChecker sBinderChecker = new BinderChecker() {
         @Override
@@ -234,62 +248,271 @@
      *
      * @return {@code true} if the component/user combination specified is valid.
      */
-    private boolean updateCurrentVrServiceLocked(boolean enabled,
-            @NonNull ComponentName component, int userId, ComponentName calling) {
+    private boolean updateCurrentVrServiceLocked(boolean enabled, @NonNull ComponentName component,
+            int userId, ComponentName calling) {
 
         boolean sendUpdatedCaller = false;
+        final long identity = Binder.clearCallingIdentity();
+        try {
 
-        boolean validUserComponent = (mComponentObserver.isValid(component, userId) ==
-                EnabledComponentsObserver.NO_ERROR);
+            boolean validUserComponent = (mComponentObserver.isValid(component, userId) ==
+                    EnabledComponentsObserver.NO_ERROR);
 
-        // Always send mode change events.
-        changeVrModeLocked(enabled, (enabled && validUserComponent) ? component : null);
+            // Always send mode change events.
+            changeVrModeLocked(enabled, (enabled && validUserComponent) ? component : null);
 
-        if (!enabled || !validUserComponent) {
-            // Unbind whatever is running
-            if (mCurrentVrService != null) {
-                Slog.i(TAG, "Disconnecting " + mCurrentVrService.getComponent() + " for user " +
-                        mCurrentVrService.getUserId());
-                mCurrentVrService.disconnect();
-                mCurrentVrService = null;
-            }
-        } else {
-            if (mCurrentVrService != null) {
-                // Unbind any running service that doesn't match the component/user selection
-                if (mCurrentVrService.disconnectIfNotMatching(component, userId)) {
+            if (!enabled || !validUserComponent) {
+                // Unbind whatever is running
+                if (mCurrentVrService != null) {
                     Slog.i(TAG, "Disconnecting " + mCurrentVrService.getComponent() + " for user " +
-                        mCurrentVrService.getUserId());
+                            mCurrentVrService.getUserId());
+                    mCurrentVrService.disconnect();
+                    disableImpliedPermissionsLocked(mCurrentVrService.getComponent(),
+                            new UserHandle(mCurrentVrService.getUserId()));
+                    mCurrentVrService = null;
+                }
+            } else {
+                if (mCurrentVrService != null) {
+                    // Unbind any running service that doesn't match the component/user selection
+                    if (mCurrentVrService.disconnectIfNotMatching(component, userId)) {
+                        Slog.i(TAG, "Disconnecting " + mCurrentVrService.getComponent() +
+                                " for user " + mCurrentVrService.getUserId());
+                        disableImpliedPermissionsLocked(mCurrentVrService.getComponent(),
+                                new UserHandle(mCurrentVrService.getUserId()));
+                        createAndConnectService(component, userId);
+                        enableImpliedPermissionsLocked(mCurrentVrService.getComponent(),
+                                new UserHandle(mCurrentVrService.getUserId()));
+                        sendUpdatedCaller = true;
+                    }
+                    // The service with the correct component/user is bound
+                } else {
+                    // Nothing was previously running, bind a new service
                     createAndConnectService(component, userId);
+                    enableImpliedPermissionsLocked(mCurrentVrService.getComponent(),
+                            new UserHandle(mCurrentVrService.getUserId()));
                     sendUpdatedCaller = true;
                 }
-                // The service with the correct component/user is bound
-            } else {
-                // Nothing was previously running, bind a new service
-                createAndConnectService(component, userId);
+            }
+
+            if (calling != null && !Objects.equals(calling, mCurrentVrModeComponent))  {
+                mCurrentVrModeComponent = calling;
+                mCurrentVrModeUser = userId;
                 sendUpdatedCaller = true;
             }
+
+            if (mCurrentVrService != null && sendUpdatedCaller) {
+                final ComponentName c = mCurrentVrModeComponent;
+                mCurrentVrService.sendEvent(new PendingEvent() {
+                    @Override
+                    public void runEvent(IInterface service) throws RemoteException {
+                        IVrListener l = (IVrListener) service;
+                        l.focusedActivityChanged(c);
+                    }
+                });
+            }
+
+            return validUserComponent;
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * Enable the permission given in {@link #IMPLIED_VR_LISTENER_PERMISSIONS} for the given
+     * component package and user.
+     *
+     * @param component the component whose package should be enabled.
+     * @param userId the user that owns the given component.
+     */
+    private void enableImpliedPermissionsLocked(ComponentName component, UserHandle userId) {
+        if (mGuard) {
+            // Impossible
+            throw new IllegalStateException("Enabling permissions without disabling.");
+        }
+        mGuard = true;
+
+        PackageManager pm = mContext.getPackageManager();
+
+        String pName = component.getPackageName();
+        if (pm == null) {
+            Slog.e(TAG, "Couldn't set implied permissions for " + pName +
+                ", PackageManager isn't running");
+            return;
+        }
+
+        ApplicationInfo info = null;
+        try {
+            info = pm.getApplicationInfo(pName, PackageManager.GET_META_DATA);
+        } catch (NameNotFoundException e) {
+        }
+
+        if (info == null) {
+            Slog.e(TAG, "Couldn't set implied permissions for " + pName + ", no such package.");
+            return;
+        }
+
+        if (!(info.isSystemApp() || info.isUpdatedSystemApp())) {
+            return; // Application is not pre-installed, avoid setting implied permissions
+        }
+
+        mWasDefaultGranted = true;
+
+        grantOverlayAccess(pName, userId);
+        grantNotificationPolicyAccess(pName);
+        grantNotificationListenerAccess(pName, userId);
+    }
+
+    /**
+     * Disable the permission given in {@link #IMPLIED_VR_LISTENER_PERMISSIONS} for the given
+     * component package and user.
+     *
+     * @param component the component whose package should be disabled.
+     * @param userId the user that owns the given component.
+     */
+    private void disableImpliedPermissionsLocked(ComponentName component, UserHandle userId) {
+        if (!mGuard) {
+            // Impossible
+            throw new IllegalStateException("Disabling permissions without enabling.");
+        }
+        mGuard = false;
+
+        PackageManager pm = mContext.getPackageManager();
+
+        if (pm == null) {
+            Slog.e(TAG, "Couldn't remove implied permissions for " + component +
+                ", PackageManager isn't running");
+            return;
+        }
+
+        String pName = component.getPackageName();
+        if (mWasDefaultGranted) {
+            revokeOverlayAccess(userId);
+            revokeNotificationPolicyAccess(pName);
+            revokeNotificiationListenerAccess();
+            mWasDefaultGranted = false;
+        }
+
+    }
+
+    private void grantOverlayAccess(String pkg, UserHandle userId) {
+        PackageManager pm = mContext.getPackageManager();
+        boolean prev = (PackageManager.PERMISSION_GRANTED ==
+                pm.checkPermission(android.Manifest.permission.SYSTEM_ALERT_WINDOW, pkg));
+        mPreviousManageOverlayPackage = null;
+        if (!prev) {
+            pm.grantRuntimePermission(pkg, android.Manifest.permission.SYSTEM_ALERT_WINDOW,
+                    userId);
+            mPreviousManageOverlayPackage = pkg;
+        }
+    }
+
+    private void revokeOverlayAccess(UserHandle userId) {
+        PackageManager pm = mContext.getPackageManager();
+        if (mPreviousManageOverlayPackage != null) {
+            pm.revokeRuntimePermission(mPreviousManageOverlayPackage,
+                    android.Manifest.permission.SYSTEM_ALERT_WINDOW, userId);
+            mPreviousManageOverlayPackage = null;
+        }
+    }
+
+
+    private void grantNotificationPolicyAccess(String pkg) {
+        NotificationManager nm = mContext.getSystemService(NotificationManager.class);
+        boolean prev = nm.isNotificationPolicyAccessGrantedForPackage(pkg);
+        mPreviousNotificationPolicyAccessPackage = null;
+        if (!prev) {
+            mPreviousNotificationPolicyAccessPackage = pkg;
+            nm.setNotificationPolicyAccessGranted(pkg, true);
+        }
+    }
+
+    private void revokeNotificationPolicyAccess(String pkg) {
+        NotificationManager nm = mContext.getSystemService(NotificationManager.class);
+        if (mPreviousNotificationPolicyAccessPackage != null) {
+            nm.setNotificationPolicyAccessGranted(mPreviousNotificationPolicyAccessPackage, false);
+            mPreviousNotificationPolicyAccessPackage = null;
+        }
+    }
+
+    private void grantNotificationListenerAccess(String pkg, UserHandle userId) {
+        PackageManager pm = mContext.getPackageManager();
+        ArraySet<ComponentName> possibleServices = EnabledComponentsObserver.loadComponentNames(pm,
+                userId.getIdentifier(), NotificationListenerService.SERVICE_INTERFACE,
+                android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE);
+        ContentResolver resolver = mContext.getContentResolver();
+
+        ArraySet<String> current = getCurrentNotifListeners(resolver);
+
+        mPreviousToggledListenerSettings.clear();
+
+        for (ComponentName c : possibleServices) {
+            String flatName = c.flattenToString();
+            if (Objects.equals(c.getPackageName(), pkg)
+                    && !current.contains(flatName)) {
+                mPreviousToggledListenerSettings.add(flatName);
+                current.add(flatName);
+            }
         }
 
-        if (calling != null && !Objects.equals(calling, mCurrentVrModeComponent))  {
-            mCurrentVrModeComponent = calling;
-            mCurrentVrModeUser = userId;
-            sendUpdatedCaller = true;
+        if (current.size() > 0) {
+            String flatSettings = formatSettings(current);
+            Settings.Secure.putString(resolver, Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
+                    flatSettings);
         }
-
-        if (mCurrentVrService != null && sendUpdatedCaller) {
-            final ComponentName c = mCurrentVrModeComponent;
-            mCurrentVrService.sendEvent(new PendingEvent() {
-                @Override
-                public void runEvent(IInterface service) throws RemoteException {
-                    IVrListener l = (IVrListener) service;
-                    l.focusedActivityChanged(c);
-                }
-            });
-        }
-
-        return validUserComponent;
     }
 
+    private void revokeNotificiationListenerAccess() {
+        if (mPreviousToggledListenerSettings.isEmpty()) {
+            return;
+        }
+
+        ContentResolver resolver = mContext.getContentResolver();
+        ArraySet<String> current = getCurrentNotifListeners(resolver);
+
+        current.removeAll(mPreviousToggledListenerSettings);
+        mPreviousToggledListenerSettings.clear();
+
+        String flatSettings = formatSettings(current);
+        Settings.Secure.putString(resolver, Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
+                flatSettings);
+    }
+
+    private ArraySet<String> getCurrentNotifListeners(ContentResolver resolver) {
+        String flat = Settings.Secure.getString(resolver,
+                Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
+
+        ArraySet<String> current = new ArraySet<>();
+        if (flat != null) {
+            String[] allowed = flat.split(":");
+            for (String s : allowed) {
+                current.add(s);
+            }
+        }
+        return current;
+    }
+
+    private static String formatSettings(Collection<String> c) {
+        if (c == null || c.isEmpty()) {
+            return "";
+        }
+
+        StringBuilder b = new StringBuilder();
+        boolean start = true;
+        for (String s : c) {
+            if ("".equals(s)) {
+                continue;
+            }
+            if (!start) {
+                b.append(':');
+            }
+            b.append(s);
+            start = false;
+        }
+        return b.toString();
+    }
+
+
+
     private void createAndConnectService(@NonNull ComponentName component, int userId) {
         mCurrentVrService = VrManagerService.create(mContext, component, userId);
         mCurrentVrService.connect();