Enable system service to notify device owners about pending update

Create a DevicePolicyManager API which can be used by OTA subsystem
to tell device owners about pending updates. Device owners will get
a callback from its DeviceAdminReceiver when the update service sends
out such notifications.

Bug: 20213644
Change-Id: Ifcc755655e4f441980cf77d76175a046112ca9ae
diff --git a/api/current.txt b/api/current.txt
index 85813f0..7a1ae48 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5651,6 +5651,7 @@
     method public void onProfileProvisioningComplete(android.content.Context, android.content.Intent);
     method public void onReadyForUserInitialization(android.content.Context, android.content.Intent);
     method public void onReceive(android.content.Context, android.content.Intent);
+    method public void onSystemUpdatePending(android.content.Context, android.content.Intent, long);
     field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED";
     field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED";
     field public static final java.lang.String ACTION_DEVICE_ADMIN_ENABLED = "android.app.action.DEVICE_ADMIN_ENABLED";
diff --git a/api/system-current.txt b/api/system-current.txt
index 043a55b..982b3bf 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5746,6 +5746,7 @@
     method public void onProfileProvisioningComplete(android.content.Context, android.content.Intent);
     method public void onReadyForUserInitialization(android.content.Context, android.content.Intent);
     method public void onReceive(android.content.Context, android.content.Intent);
+    method public void onSystemUpdatePending(android.content.Context, android.content.Intent, long);
     field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED";
     field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED";
     field public static final java.lang.String ACTION_DEVICE_ADMIN_ENABLED = "android.app.action.DEVICE_ADMIN_ENABLED";
@@ -5851,6 +5852,7 @@
     method public boolean isProfileOwnerApp(java.lang.String);
     method public boolean isUninstallBlocked(android.content.ComponentName, java.lang.String);
     method public void lockNow();
+    method public void notifyPendingSystemUpdate(long);
     method public void removeActiveAdmin(android.content.ComponentName);
     method public boolean removeCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String);
     method public boolean removeUser(android.content.ComponentName, android.os.UserHandle);
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index fe284ce..aea413d 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -264,6 +264,20 @@
     public static final String EXTRA_CHOOSE_PRIVATE_KEY_RESPONSE = "android.app.extra.CHOOSE_PRIVATE_KEY_RESPONSE";
 
     /**
+     * Broadcast action: notify device owner that there is a pending system update.
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_NOTIFY_PENDING_SYSTEM_UPDATE = "android.app.action.NOTIFY_PENDING_SYSTEM_UPDATE";
+
+    /**
+     * A long type extra for {@link #onSystemUpdatePending} recording the system time as given by
+     * {@link System#currentTimeMillis()} when the current pending system update is first available.
+     * @hide
+     */
+    public static final String EXTRA_SYSTEM_UPDATE_RECEIVED_TIME = "android.app.extra.SYSTEM_UPDATE_RECEIVED_TIME";
+
+    /**
      * Name under which a DevicePolicy component publishes information
      * about itself.  This meta-data must reference an XML resource containing
      * a device-admin tag.
@@ -486,6 +500,22 @@
     }
 
     /**
+     * Allows the receiver to be notified when information about a pending system update is
+     * available from the system update service. The same pending system update can trigger multiple
+     * calls to this method, so it is necessary to examine the incoming parameters for details about
+     * the update.
+     * <p>
+     * This callback is only applicable to device owners.
+     *
+     * @param context The running context as per {@link #onReceive}.
+     * @param intent The received intent as per {@link #onReceive}.
+     * @param receivedTime The time as given by {@link System#currentTimeMillis()} indicating when
+     *        the current pending update was first available. -1 if no pending update is available.
+     */
+    public void onSystemUpdatePending(Context context, Intent intent, long receivedTime) {
+    }
+
+    /**
      * Intercept standard device administrator broadcasts.  Implementations
      * should not override this method; it is better to implement the
      * convenience callbacks for each action.
@@ -530,6 +560,9 @@
             onLockTaskModeExiting(context, intent);
         } else if (ACTION_READY_FOR_USER_INITIALIZATION.equals(action)) {
             onReadyForUserInitialization(context, intent);
+        } else if (ACTION_NOTIFY_PENDING_SYSTEM_UPDATE.equals(action)) {
+            long receivedTime = intent.getLongExtra(EXTRA_SYSTEM_UPDATE_RECEIVED_TIME, -1);
+            onSystemUpdatePending(context, intent, receivedTime);
         }
     }
 }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 9e2da61..a20aa668 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -4301,4 +4301,24 @@
             Log.w(TAG, "Failed talking with device policy service", re);
         }
     }
+
+    /**
+     * Callable by the system update service to notify device owners about pending updates.
+     * The caller must hold {@link android.Manifest.permission#NOTIFY_PENDING_SYSTEM_UPDATE}
+     * permission.
+     *
+     * @param updateReceivedTime The time as given by {@link System#currentTimeMillis()} indicating
+     *        when the current pending update was first available. -1 if no update is available.
+     * @hide
+     */
+    @SystemApi
+    public void notifyPendingSystemUpdate(long updateReceivedTime) {
+        if (mService != null) {
+            try {
+                mService.notifyPendingSystemUpdate(updateReceivedTime);
+            } catch (RemoteException re) {
+                Log.w(TAG, "Could not notify device owner about pending system update", re);
+            }
+        }
+    }
 }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 1f7498e..087fc88 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -225,4 +225,6 @@
     boolean setKeyguardEnabledState(in ComponentName admin, boolean enabled);
     void setStatusBarEnabledState(in ComponentName who, boolean enabled);
     boolean getDoNotAskCredentialsOnBoot();
+
+    void notifyPendingSystemUpdate(in long updateReceivedTime);
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index fa5e4ad..62685a1 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1116,6 +1116,11 @@
     <permission android:name="android.permission.ACCESS_PDB_STATE"
         android:protectionLevel="signature" />
 
+    <!-- @hide Allows system update service to notify device owner about pending updates.
+   <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.NOTIFY_PENDING_SYSTEM_UPDATE"
+        android:protectionLevel="signatureOrSystem" />
+
     <!-- =========================================== -->
     <!-- Permissions associated with camera and image capture -->
     <!-- =========================================== -->
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index dc7fad6..80d075d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -22,6 +22,7 @@
 import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA;
 import static android.content.pm.PackageManager.GET_UNINSTALLED_PACKAGES;
 
+import android.Manifest.permission;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accounts.AccountManager;
 import android.app.Activity;
@@ -45,6 +46,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
@@ -6088,4 +6090,42 @@
         }
         return false;
     }
+
+    @Override
+    public void notifyPendingSystemUpdate(long updateReceivedTime) {
+        mContext.enforceCallingOrSelfPermission(permission.NOTIFY_PENDING_SYSTEM_UPDATE,
+                "Only the system update service can broadcast update information");
+
+        if (UserHandle.getCallingUserId() != UserHandle.USER_OWNER) {
+            Slog.w(LOG_TAG, "Only the system update service in the primary user" +
+                    "can broadcast update information.");
+            return;
+        }
+        Intent intent = new Intent(DeviceAdminReceiver.ACTION_NOTIFY_PENDING_SYSTEM_UPDATE);
+        intent.putExtra(DeviceAdminReceiver.EXTRA_SYSTEM_UPDATE_RECEIVED_TIME,
+                updateReceivedTime);
+
+        synchronized (this) {
+            String deviceOwnerPackage = getDeviceOwner();
+            if (deviceOwnerPackage == null) {
+                return;
+            }
+
+            try {
+                ActivityInfo[] receivers  = mContext.getPackageManager().getPackageInfo(
+                        deviceOwnerPackage, PackageManager.GET_RECEIVERS).receivers;
+                if (receivers != null) {
+                    for (int i = 0; i < receivers.length; i++) {
+                        if (permission.BIND_DEVICE_ADMIN.equals(receivers[i].permission)) {
+                            intent.setComponent(new ComponentName(deviceOwnerPackage,
+                                    receivers[i].name));
+                            mContext.sendBroadcastAsUser(intent, UserHandle.OWNER);
+                        }
+                    }
+                }
+            } catch (NameNotFoundException e) {
+                Log.e(LOG_TAG, "Cannot find device owner package", e);
+            }
+        }
+    }
 }