Switch framework to using new scheduled-work API

Also add the intended permission-use enforcement to said API.

Bug 14994893
Bug 14993295

Change-Id: I5a3ffd32d0702c68f4ef6da68f7fa6e9de674380
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 90d49968..d34181e 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1754,12 +1754,13 @@
         android:label="@string/permlab_recovery"
         android:description="@string/permdesc_recovery" />
 
-    <!-- Allows the system to bind to an application's idle services
+    <!-- Allows the system to bind to an application's task services
          @hide -->
-    <permission android:name="android.permission.BIND_IDLE_SERVICE"
+    <permission android:name="android.permission.BIND_TASK_SERVICE"
         android:protectionLevel="signature"
-        android:label="@string/permlab_bindIdleService"
-        android:description="@string/permdesc_bindIdleService" />
+        android:label="@string/permlab_bindTaskService"
+        android:description="@string/permdesc_bindTaskService" />
+    <uses-permission android:name="android.permission.BIND_TASK_SERVICE"/>
 
     <!-- ========================================= -->
     <!-- Permissions for special development tools -->
@@ -2875,10 +2876,7 @@
 
         <service android:name="com.android.server.MountServiceIdler"
                  android:exported="false"
-                 android:permission="android.permission.BIND_IDLE_SERVICE" >
-            <intent-filter>
-                <action android:name="android.service.idle.IdleService" />
-            </intent-filter>
+                 android:permission="android.permission.BIND_TASK_SERVICE" >
         </service>
 
     </application>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 241526e..10edcc8 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1260,11 +1260,11 @@
     <!-- Title of a permission that is never presented to the user.  This is not a
          permission that an application must be granted by the user.  Instead, it
          is part of a mechanism that applications use to indicate to the system
-         that they want to do occasional work while the device is idle.  -->
-    <string name="permlab_bindIdleService">run application during idle time</string>
+         that they want to do scheduled background work.  -->
+    <string name="permlab_bindTaskService">run the application\'s scheduled background work</string>
     <!-- Description of an application permission, so that the user can understand
          what is being done if they are curious. -->
-    <string name="permdesc_bindIdleService">This permission allows the Android system to run the application in the background while the device is not in use.</string>
+    <string name="permdesc_bindTaskService">This permission allows the Android system to run the application in the background when requested.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_diagnostic">read/write to resources owned by diag</string>
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index d31fb60..39410c2 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -629,6 +629,11 @@
             sendUmsIntent(true);
             mSendUmsConnectedOnBoot = false;
         }
+
+        /*
+         * Start scheduling nominally-daily fstrim operations
+         */
+        MountServiceIdler.scheduleIdlePass(mContext);
     }
 
     private final BroadcastReceiver mUserReceiver = new BroadcastReceiver() {
diff --git a/services/core/java/com/android/server/MountServiceIdler.java b/services/core/java/com/android/server/MountServiceIdler.java
index 8b19321..61790826 100644
--- a/services/core/java/com/android/server/MountServiceIdler.java
+++ b/services/core/java/com/android/server/MountServiceIdler.java
@@ -16,34 +16,94 @@
 
 package com.android.server;
 
-import android.app.maintenance.IdleService;
+import java.util.Calendar;
+
+import android.app.task.Task;
+import android.app.task.TaskManager;
+import android.app.task.TaskParams;
+import android.app.task.TaskService;
+import android.content.ComponentName;
+import android.content.Context;
 import android.util.Slog;
 
-public class MountServiceIdler extends IdleService {
+public class MountServiceIdler extends TaskService {
     private static final String TAG = "MountServiceIdler";
 
+    private static ComponentName sIdleService =
+            new ComponentName(MountServiceIdler.class.getPackage().getName(),
+                    MountServiceIdler.class.getName());
+
+    private static int MOUNT_TASK_ID = 808;
+
+    private boolean mStarted;
+    private TaskParams mTaskParams;
     private Runnable mFinishCallback = new Runnable() {
         @Override
         public void run() {
             Slog.i(TAG, "Got mount service completion callback");
-            finishIdle();
+            synchronized (mFinishCallback) {
+                if (mStarted) {
+                    taskFinished(mTaskParams, false);
+                    mStarted = false;
+                }
+            }
+            // ... and try again tomorrow
+            scheduleIdlePass(MountServiceIdler.this);
         }
     };
 
     @Override
-    public boolean onIdleStart() {
+    public boolean onStartTask(TaskParams params) {
         // The mount service will run an fstrim operation asynchronously
         // on a designated separate thread, so we provide it with a callback
         // that lets us cleanly end our idle timeslice.  It's safe to call
         // finishIdle() from any thread.
+        mTaskParams = params;
         MountService ms = MountService.sSelf;
         if (ms != null) {
+            synchronized (mFinishCallback) {
+                mStarted = true;
+            }
             ms.runIdleMaintenance(mFinishCallback);
         }
         return ms != null;
     }
 
     @Override
-    public void onIdleStop() {
+    public boolean onStopTask(TaskParams params) {
+        // Once we kick off the fstrim we aren't actually interruptible; just note
+        // that we don't need to call taskFinished(), and let everything happen in
+        // the callback from the mount service.
+        synchronized (mFinishCallback) {
+            mStarted = false;
+        }
+        return false;
+    }
+
+    /**
+     * Schedule the idle job that will ping the mount service
+     */
+    public static void scheduleIdlePass(Context context) {
+        TaskManager tm = (TaskManager) context.getSystemService(Context.TASK_SERVICE);
+
+        Calendar calendar = tomorrowMidnight();
+        final long timeToMidnight = calendar.getTimeInMillis() - System.currentTimeMillis();
+
+        Task.Builder builder = new Task.Builder(MOUNT_TASK_ID, sIdleService);
+        builder.setRequiresDeviceIdle(true);
+        builder.setRequiresCharging(true);
+        builder.setMinimumLatency(timeToMidnight);
+        tm.schedule(builder.build());
+    }
+
+    private static Calendar tomorrowMidnight() {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTimeInMillis(System.currentTimeMillis());
+        calendar.set(Calendar.HOUR, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MILLISECOND, 0);
+        calendar.add(Calendar.DAY_OF_MONTH, 1);
+        return calendar;
     }
 }
diff --git a/services/core/java/com/android/server/task/TaskManagerService.java b/services/core/java/com/android/server/task/TaskManagerService.java
index a6b68d9..0c55a1d 100644
--- a/services/core/java/com/android/server/task/TaskManagerService.java
+++ b/services/core/java/com/android/server/task/TaskManagerService.java
@@ -26,10 +26,14 @@
 import android.app.task.Task;
 import android.app.task.TaskManager;
 import android.content.BroadcastReceiver;
+import android.app.task.TaskService;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.Looper;
@@ -608,24 +612,43 @@
          */
         private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>();
 
-        // Determine whether the caller is allowed to persist tasks, with a small cache
-        // because the lookup is expensive enough that we'd like to avoid repeating it.
-        // This must be called from within the calling app's binder identity!
-        private boolean canCallerPersistTasks() {
+        // Enforce that only the app itself (or shared uid participant) can schedule a
+        // task that runs one of the app's services, as well as verifying that the
+        // named service properly requires the BIND_TASK_SERVICE permission
+        private void enforceValidJobRequest(int uid, Task job) {
+            final PackageManager pm = getContext().getPackageManager();
+            final ComponentName service = job.getService();
+            try {
+                ServiceInfo si = pm.getServiceInfo(service, 0);
+                if (si.applicationInfo.uid != uid) {
+                    throw new IllegalArgumentException("uid " + uid +
+                            " cannot schedule job in " + service.getPackageName());
+                }
+                if (!TaskService.PERMISSION_BIND.equals(si.permission)) {
+                    throw new IllegalArgumentException("Scheduled service " + service
+                            + " does not require android.permission.BIND_TASK_SERVICE permission");
+                }
+            } catch (NameNotFoundException e) {
+                throw new IllegalArgumentException("No such service: " + service);
+            }
+        }
+
+        private boolean canPersistJobs(int pid, int uid) {
+            // If we get this far we're good to go; all we need to do now is check
+            // whether the app is allowed to persist its scheduled work.
             final boolean canPersist;
-            final int callingUid = Binder.getCallingUid();
             synchronized (mPersistCache) {
-                Boolean cached = mPersistCache.get(callingUid);
+                Boolean cached = mPersistCache.get(uid);
                 if (cached != null) {
                     canPersist = cached.booleanValue();
                 } else {
                     // Persisting tasks is tantamount to running at boot, so we permit
                     // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED
                     // permission
-                    int result = getContext().checkCallingPermission(
-                            android.Manifest.permission.RECEIVE_BOOT_COMPLETED);
+                    int result = getContext().checkPermission(
+                            android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid);
                     canPersist = (result == PackageManager.PERMISSION_GRANTED);
-                    mPersistCache.put(callingUid, canPersist);
+                    mPersistCache.put(uid, canPersist);
                 }
             }
             return canPersist;
@@ -637,9 +660,12 @@
             if (DEBUG) {
                 Slog.d(TAG, "Scheduling task: " + task);
             }
-            final boolean canPersist = canCallerPersistTasks();
+            final int pid = Binder.getCallingPid();
             final int uid = Binder.getCallingUid();
 
+            enforceValidJobRequest(uid, task);
+            final boolean canPersist = canPersistJobs(pid, uid);
+
             long ident = Binder.clearCallingIdentity();
             try {
                 return TaskManagerService.this.schedule(task, uid, canPersist);