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/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);