Introduce "IdleService" API to expose idle-time maintenance to apps
When an application wishes to do low-priority background work when the
device is otherwise idle (e.g. in a desk dock overnight), it declares
a service in its manifest that requires this permission:
android:permission="android.permission.BIND_IDLE_SERVICE
to launch, and which publishes this intent filter:
<intent-filter>
<action android:name="android.service.idle.IdleService" />
</intent-filter>
This string is declared in the API as IdleService.SERVICE_INTERFACE.
The service must be implemented by extending the new "IdleService"
class, which provides the API through which the system will communicate
with the app.
IdleService declares three methods, two of which are lifecycle callbacks
to the service, and the third of which is for the service itself to
invoke when appropriate. The lifecycle callbacks are
public abstract boolean onIdleStart();
public abstract void onIdleStop();
The first of these is a notification to the service that an idle
maintenance interval has begun. The service can then spin off
whatever non-UI work it wishes. When the interval is over, or if
the OS determines that idle services should be shut down immediately,
the onIdleStop() method will be invoked. The service must shut down
any background processing immediately when this method is called.
Both of these methods must return immediately. However, the OS
holds a wakelock on the application's behalf for the entire period
between the onIdleStart() and onIdleStop() callbacks. This means
that for system-arbitrated idle-time operation, the application does
not need to do any of its own wakelock management, and does not need
to hold any wakelock permissions.
The third method in IdleService is
public final void finishIdle();
Calling this method notifies the OS that the application has finished
whatever idle-time operation it needed to perform, and the OS is thus
free to release the wakelock and return to normal operation (or to
allow other apps to run their own idle services).
Currently the idle window granted to each idle service is ten minutes.
The OS is rather conservative about when these services are run; low
battery or any user activity will suppress them, and the OS will not
choose to run them particularly often.
Idle services are granted their execution windows in round-robin
fashion.
Bug 9680213
Change-Id: Idd6f35940c938c31b94aa4269a67870abf7125b6
diff --git a/Android.mk b/Android.mk
index 781d5d1..0865d24 100644
--- a/Android.mk
+++ b/Android.mk
@@ -89,6 +89,8 @@
core/java/android/app/backup/IFullBackupRestoreObserver.aidl \
core/java/android/app/backup/IRestoreObserver.aidl \
core/java/android/app/backup/IRestoreSession.aidl \
+ core/java/android/app/maintenance/IIdleCallback.aidl \
+ core/java/android/app/maintenance/IIdleService.aidl \
core/java/android/bluetooth/IBluetooth.aidl \
core/java/android/bluetooth/IBluetoothA2dp.aidl \
core/java/android/bluetooth/IBluetoothCallback.aidl \
diff --git a/api/current.txt b/api/current.txt
index 54352b0..997acdf 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4885,6 +4885,20 @@
}
+package android.app.maintenance {
+
+ public abstract class IdleService extends android.app.Service {
+ ctor public IdleService();
+ method public final void finishIdle();
+ method public final android.os.IBinder onBind(android.content.Intent);
+ method public abstract boolean onIdleStart();
+ method public abstract void onIdleStop();
+ field public static final java.lang.String PERMISSION_BIND = "android.permission.BIND_IDLE_SERVICE";
+ field public static final java.lang.String SERVICE_INTERFACE = "android.service.idle.IdleService";
+ }
+
+}
+
package android.appwidget {
public class AppWidgetHost {
diff --git a/core/java/android/app/maintenance/IIdleCallback.aidl b/core/java/android/app/maintenance/IIdleCallback.aidl
new file mode 100644
index 0000000..582dede
--- /dev/null
+++ b/core/java/android/app/maintenance/IIdleCallback.aidl
@@ -0,0 +1,53 @@
+/**
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.maintenance;
+
+import android.app.maintenance.IIdleService;
+
+/**
+ * The server side of the idle maintenance IPC protocols. The app-side implementation
+ * invokes on this interface to indicate completion of the (asynchronous) instructions
+ * issued by the server.
+ *
+ * In all cases, the 'who' parameter is the caller's service binder, used to track
+ * which idle service instance is reporting.
+ *
+ * {@hide}
+ */
+interface IIdleCallback {
+ /**
+ * Acknowledge receipt and processing of the asynchronous "start idle work" incall.
+ * 'result' is true if the app wants some time to perform ongoing background
+ * idle-time work; or false if the app declares that it does not need any time
+ * for such work.
+ */
+ void acknowledgeStart(int token, boolean result);
+
+ /**
+ * Acknowledge receipt and processing of the asynchronous "stop idle work" incall.
+ */
+ void acknowledgeStop(int token);
+
+ /*
+ * Tell the idle service manager that we're done with our idle maintenance, so that
+ * it can go on to the next one and stop attributing wakelock time to us etc.
+ *
+ * @param opToken The identifier passed in the startIdleMaintenance() call that
+ * indicated the beginning of this service's idle timeslice.
+ */
+ void idleFinished(int token);
+}
diff --git a/core/java/android/app/maintenance/IIdleService.aidl b/core/java/android/app/maintenance/IIdleService.aidl
new file mode 100644
index 0000000..54abccd
--- /dev/null
+++ b/core/java/android/app/maintenance/IIdleService.aidl
@@ -0,0 +1,34 @@
+/**
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.maintenance;
+
+import android.app.maintenance.IIdleCallback;
+
+/**
+ * Interface that the framework uses to communicate with application code
+ * that implements an idle-time "maintenance" service. End user code does
+ * not implement this interface directly; instead, the app's idle service
+ * implementation will extend android.app.maintenance.IdleService.
+ * {@hide}
+ */
+oneway interface IIdleService {
+ /**
+ * Begin your idle-time work.
+ */
+ void startIdleMaintenance(IIdleCallback callbackBinder, int token);
+ void stopIdleMaintenance(IIdleCallback callbackBinder, int token);
+}
diff --git a/core/java/android/app/maintenance/IdleService.java b/core/java/android/app/maintenance/IdleService.java
new file mode 100644
index 0000000..2331b81
--- /dev/null
+++ b/core/java/android/app/maintenance/IdleService.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.maintenance;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+
+/**
+ * Idle maintenance API. Full docs TBW (to be written).
+ */
+public abstract class IdleService extends Service {
+ private static final String TAG = "IdleService";
+
+ static final int MSG_START = 1;
+ static final int MSG_STOP = 2;
+ static final int MSG_FINISH = 3;
+
+ IdleHandler mHandler;
+ IIdleCallback mCallbackBinder;
+ int mToken;
+ final Object mHandlerLock = new Object();
+
+ void ensureHandler() {
+ synchronized (mHandlerLock) {
+ if (mHandler == null) {
+ mHandler = new IdleHandler(getMainLooper());
+ }
+ }
+ }
+
+ /**
+ * TBW: the idle service should supply an intent-filter handling this intent
+ * <p>
+ * <p class="note">The application must also protect the idle service with the
+ * {@code "android.permission.BIND_IDLE_SERVICE"} permission to ensure that other
+ * applications cannot maliciously bind to it. If an idle service's manifest
+ * declaration does not require that permission, it will never be invoked.
+ * </p>
+ */
+ @SdkConstant(SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE =
+ "android.service.idle.IdleService";
+
+ /**
+ * Idle services must be protected with this permission:
+ *
+ * <pre class="prettyprint">
+ * <service android:name="MyIdleService"
+ * android:permission="android.permission.BIND_IDLE_SERVICE" >
+ * ...
+ * </service>
+ * </pre>
+ *
+ * <p>If an idle service is declared in the manifest but not protected with this
+ * permission, that service will be ignored by the OS.
+ */
+ public static final String PERMISSION_BIND =
+ "android.permission.BIND_IDLE_SERVICE";
+
+ // Trampoline: the callbacks are always run on the main thread
+ IIdleService mBinder = new IIdleService.Stub() {
+ @Override
+ public void startIdleMaintenance(IIdleCallback callbackBinder, int token)
+ throws RemoteException {
+ ensureHandler();
+ Message msg = mHandler.obtainMessage(MSG_START, token, 0, callbackBinder);
+ mHandler.sendMessage(msg);
+ }
+
+ @Override
+ public void stopIdleMaintenance(IIdleCallback callbackBinder, int token)
+ throws RemoteException {
+ ensureHandler();
+ Message msg = mHandler.obtainMessage(MSG_STOP, token, 0, callbackBinder);
+ mHandler.sendMessage(msg);
+ }
+ };
+
+ /**
+ * Your application may begin doing "idle" maintenance work in the background.
+ * <p>
+ * Your application may continue to run in the background until it receives a call
+ * to {@link #onIdleStop()}, at which point you <i>must</i> cease doing work. The
+ * OS will hold a wakelock on your application's behalf from the time this method is
+ * called until after the following call to {@link #onIdleStop()} returns.
+ * </p>
+ * <p>
+ * Returning {@code false} from this method indicates that you have no ongoing work
+ * to do at present. The OS will respond by immediately calling {@link #onIdleStop()}
+ * and returning your application to its normal stopped state. Returning {@code true}
+ * indicates that the application is indeed performing ongoing work, so the OS will
+ * let your application run in this state until it's no longer appropriate.
+ * </p>
+ * <p>
+ * You will always receive a matching call to {@link #onIdleStop()} even if your
+ * application returns {@code false} from this method.
+ *
+ * @return {@code true} to indicate that the application wishes to perform some ongoing
+ * background work; {@code false} to indicate that it does not need to perform such
+ * work at present.
+ */
+ public abstract boolean onIdleStart();
+
+ /**
+ * Your app's maintenance opportunity is over. Once the application returns from
+ * this method, the wakelock held by the OS on its behalf will be released.
+ */
+ public abstract void onIdleStop();
+
+ /**
+ * Tell the OS that you have finished your idle work. Calling this more than once,
+ * or calling it when you have not received an {@link #onIdleStart()} callback, is
+ * an error.
+ *
+ * <p>It is safe to call {@link #finishIdle()} from any thread.
+ */
+ public final void finishIdle() {
+ ensureHandler();
+ mHandler.sendEmptyMessage(MSG_FINISH);
+ }
+
+ class IdleHandler extends Handler {
+ IdleHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_START: {
+ // Call the concrete onIdleStart(), reporting its return value back to
+ // the OS. If onIdleStart() throws, report it as a 'false' return but
+ // rethrow the exception at the offending app.
+ boolean result = false;
+ IIdleCallback callbackBinder = (IIdleCallback) msg.obj;
+ mCallbackBinder = callbackBinder;
+ final int token = mToken = msg.arg1;
+ try {
+ result = IdleService.this.onIdleStart();
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to start idle workload", e);
+ throw new RuntimeException(e);
+ } finally {
+ // don't bother if the service already called finishIdle()
+ if (mCallbackBinder != null) {
+ try {
+ callbackBinder.acknowledgeStart(token, result);
+ } catch (RemoteException re) {
+ Log.e(TAG, "System unreachable to start idle workload");
+ }
+ }
+ }
+ break;
+ }
+
+ case MSG_STOP: {
+ // Structured just like MSG_START for the stop-idle bookend call.
+ IIdleCallback callbackBinder = (IIdleCallback) msg.obj;
+ final int token = msg.arg1;
+ try {
+ IdleService.this.onIdleStop();
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to stop idle workload", e);
+ throw new RuntimeException(e);
+ } finally {
+ if (mCallbackBinder != null) {
+ try {
+ callbackBinder.acknowledgeStop(token);
+ } catch (RemoteException re) {
+ Log.e(TAG, "System unreachable to stop idle workload");
+ }
+ }
+ }
+ break;
+ }
+
+ case MSG_FINISH: {
+ if (mCallbackBinder != null) {
+ try {
+ mCallbackBinder.idleFinished(mToken);
+ } catch (RemoteException e) {
+ Log.e(TAG, "System unreachable to finish idling");
+ } finally {
+ mCallbackBinder = null;
+ }
+ } else {
+ Log.e(TAG, "finishIdle() called but the idle service is not started");
+ }
+ break;
+ }
+
+ default: {
+ Slog.w(TAG, "Unknown message " + msg.what);
+ }
+ }
+ }
+ }
+
+ /** @hide */
+ @Override
+ public final IBinder onBind(Intent intent) {
+ return mBinder.asBinder();
+ }
+
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2151d4c..bba62d3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1727,6 +1727,13 @@
android:label="@string/permlab_recovery"
android:description="@string/permdesc_recovery" />
+ <!-- Allows the system to bind to an application's idle services
+ @hide -->
+ <permission android:name="android.permission.BIND_IDLE_SERVICE"
+ android:protectionLevel="signature"
+ android:label="@string/permlab_bindIdleService"
+ android:description="@string/permdesc_bindIdleService" />
+
<!-- ========================================= -->
<!-- Permissions for special development tools -->
<!-- ========================================= -->
@@ -2728,6 +2735,14 @@
</intent-filter>
</service>
+ <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>
+ </service>
+
</application>
</manifest>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 785e788..ee68199 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1176,6 +1176,11 @@
<string name="permdesc_manageCaCertificates">Allows the app to install and uninstall CA certificates as trusted credentials.</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_bindIdleService">bind to idle services</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_bindIdleService">Allows the app to interact with application-defined idle services.</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>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_diagnostic">Allows the app to read and write to
diff --git a/services/core/java/com/android/server/IdleMaintenanceService.java b/services/core/java/com/android/server/IdleMaintenanceService.java
index b0a1aca..acc6abe 100644
--- a/services/core/java/com/android/server/IdleMaintenanceService.java
+++ b/services/core/java/com/android/server/IdleMaintenanceService.java
@@ -16,22 +16,38 @@
package com.android.server;
-import android.app.Activity;
-import android.app.ActivityManagerNative;
import android.app.AlarmManager;
import android.app.PendingIntent;
+import android.app.maintenance.IIdleCallback;
+import android.app.maintenance.IIdleService;
+import android.app.maintenance.IdleService;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.WorkSource;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseArray;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Random;
/**
* This service observes the device state and when applicable sends
@@ -47,12 +63,15 @@
*
* The end of a maintenance window is announced only if: a start was
* announced AND the screen turned on or a dream was stopped.
+ *
+ * Method naming note:
+ * Methods whose name ends with "Tm" must only be called from the main thread.
*/
public class IdleMaintenanceService extends BroadcastReceiver {
private static final boolean DEBUG = false;
- private static final String LOG_TAG = IdleMaintenanceService.class.getSimpleName();
+ private static final String TAG = IdleMaintenanceService.class.getSimpleName();
private static final int LAST_USER_ACTIVITY_TIME_INVALID = -1;
@@ -74,36 +93,480 @@
private static final String ACTION_FORCE_IDLE_MAINTENANCE =
"com.android.server.IdleMaintenanceService.action.FORCE_IDLE_MAINTENANCE";
- private static final Intent sIdleMaintenanceStartIntent;
- static {
- sIdleMaintenanceStartIntent = new Intent(Intent.ACTION_IDLE_MAINTENANCE_START);
- sIdleMaintenanceStartIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- };
+ static final int MSG_OP_COMPLETE = 1;
+ static final int MSG_IDLE_FINISHED = 2;
+ static final int MSG_TIMEOUT = 3;
- private static final Intent sIdleMaintenanceEndIntent;
- static {
- sIdleMaintenanceEndIntent = new Intent(Intent.ACTION_IDLE_MAINTENANCE_END);
- sIdleMaintenanceEndIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- }
+ // when a timeout happened, what were we expecting?
+ static final int VERB_BINDING = 1;
+ static final int VERB_IDLING = 2;
+ static final int VERB_ENDING = 3;
+
+ // What are our relevant timeouts / allocated slices?
+ static final long OP_TIMEOUT = 8 * 1000; // 8 seconds to bind or ack the start
+ static final long IDLE_TIMESLICE = 10 * 60 * 1000; // ten minutes for each idler
private final AlarmManager mAlarmService;
-
private final BatteryService mBatteryService;
-
private final PendingIntent mUpdateIdleMaintenanceStatePendingIntent;
-
private final Context mContext;
-
private final WakeLock mWakeLock;
-
- private final Handler mHandler;
+ private final WorkSource mSystemWorkSource = new WorkSource(Process.myUid());
private long mLastIdleMaintenanceStartTimeMillis;
-
private long mLastUserActivityElapsedTimeMillis = LAST_USER_ACTIVITY_TIME_INVALID;
-
private boolean mIdleMaintenanceStarted;
+ final IdleCallback mCallback;
+ final Handler mHandler;
+
+ final Random mTokenGenerator = new Random();
+
+ int makeToken() {
+ int token;
+ do {
+ token = mTokenGenerator.nextInt(Integer.MAX_VALUE);
+ } while (token == 0);
+ return token;
+ }
+
+ class ActiveTask {
+ public IdleServiceInfo who;
+ public int verb;
+ public int token;
+
+ ActiveTask(IdleServiceInfo target, int action) {
+ who = target;
+ verb = action;
+ token = makeToken();
+ }
+
+ @Override
+ public String toString() {
+ return "ActiveTask{" + Integer.toHexString(this.hashCode())
+ + " : verb=" + verb
+ + " : token=" + token
+ + " : "+ who + "}";
+ }
+ }
+
+ // What operations are in flight?
+ final SparseArray<ActiveTask> mPendingOperations = new SparseArray<ActiveTask>();
+
+ // Idle service queue management
+ class IdleServiceInfo {
+ public final ComponentName componentName;
+ public final int uid;
+ public IIdleService service;
+
+ IdleServiceInfo(ResolveInfo info, ComponentName cname) {
+ componentName = cname; // derived from 'info' but this avoids an extra object
+ uid = info.serviceInfo.applicationInfo.uid;
+ service = null;
+ }
+
+ @Override
+ public int hashCode() {
+ return componentName.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "IdleServiceInfo{" + componentName
+ + " / " + (service == null ? "null" : service.asBinder()) + "}";
+ }
+ }
+
+ final ArrayMap<ComponentName, IdleServiceInfo> mIdleServices =
+ new ArrayMap<ComponentName, IdleServiceInfo>();
+ final LinkedList<IdleServiceInfo> mIdleServiceQueue = new LinkedList<IdleServiceInfo>();
+ IdleServiceInfo mCurrentIdler; // set when we've committed to launching an idler
+ IdleServiceInfo mLastIdler; // end of queue when idling begins
+
+ void reportNoTimeout(int token, boolean result) {
+ final Message msg = mHandler.obtainMessage(MSG_OP_COMPLETE, result ? 1 : 0, token);
+ mHandler.sendMessage(msg);
+ }
+
+ // Binder acknowledgment trampoline
+ class IdleCallback extends IIdleCallback.Stub {
+ @Override
+ public void acknowledgeStart(int token, boolean result) throws RemoteException {
+ reportNoTimeout(token, result);
+ }
+
+ @Override
+ public void acknowledgeStop(int token) throws RemoteException {
+ reportNoTimeout(token, false);
+ }
+
+ @Override
+ public void idleFinished(int token) throws RemoteException {
+ if (DEBUG) {
+ Slog.v(TAG, "idleFinished: " + token);
+ }
+ final Message msg = mHandler.obtainMessage(MSG_IDLE_FINISHED, 0, token);
+ mHandler.sendMessage(msg);
+ }
+ }
+
+ // Stuff that we run on a Handler
+ class IdleHandler extends Handler {
+ public IdleHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ final int token = msg.arg2;
+
+ switch (msg.what) {
+ case MSG_OP_COMPLETE: {
+ if (DEBUG) {
+ Slog.i(TAG, "MSG_OP_COMPLETE of " + token);
+ }
+ ActiveTask task = mPendingOperations.get(token);
+ if (task != null) {
+ mPendingOperations.remove(token);
+ removeMessages(MSG_TIMEOUT);
+
+ handleOpCompleteTm(task, msg.arg1);
+ } else {
+ // Can happen in a race between timeout and actual
+ // (belated) completion of a "begin idling" or similar
+ // operation. In that state we've already processed the
+ // timeout, so we intentionally no-op here.
+ if (DEBUG) {
+ Slog.w(TAG, "Belated op-complete of " + token);
+ }
+ }
+ break;
+ }
+
+ case MSG_IDLE_FINISHED: {
+ if (DEBUG) {
+ Slog.i(TAG, "MSG_IDLE_FINISHED of " + token);
+ }
+ ActiveTask task = mPendingOperations.get(token);
+ if (task != null) {
+ if (DEBUG) {
+ Slog.i(TAG, "... removing task " + token);
+ }
+ mPendingOperations.remove(token);
+ removeMessages(MSG_TIMEOUT);
+
+ handleIdleFinishedTm(task);
+ } else {
+ // Can happen "legitimately" from an app explicitly calling
+ // idleFinished() after already having been told that its slice
+ // has ended.
+ if (DEBUG) {
+ Slog.w(TAG, "Belated idle-finished of " + token);
+ }
+ }
+ break;
+ }
+
+ case MSG_TIMEOUT: {
+ if (DEBUG) {
+ Slog.i(TAG, "MSG_TIMEOUT of " + token);
+ }
+ ActiveTask task = mPendingOperations.get(token);
+ if (task != null) {
+ mPendingOperations.remove(token);
+ removeMessages(MSG_OP_COMPLETE);
+
+ handleTimeoutTm(task);
+ } else {
+ // This one should not happen; we flushed timeout messages
+ // whenever we entered a state after which we have established
+ // that they are not appropriate.
+ Slog.w(TAG, "Unexpected timeout of " + token);
+ }
+ break;
+ }
+
+ default:
+ Slog.w(TAG, "Unknown message: " + msg.what);
+ }
+ }
+ }
+
+ void handleTimeoutTm(ActiveTask task) {
+ switch (task.verb) {
+ case VERB_BINDING: {
+ // We were trying to bind to this service, but it wedged or otherwise
+ // failed to respond in time. Let it stay in the queue for the next
+ // time around, but just give up on it for now and go on to the next.
+ startNextIdleServiceTm();
+ break;
+ }
+ case VERB_IDLING: {
+ // The service has reached the end of its designated idle timeslice.
+ // This is not considered an error.
+ if (DEBUG) {
+ Slog.i(TAG, "Idler reached end of timeslice: " + task.who);
+ }
+ sendEndIdleTm(task.who);
+ break;
+ }
+ case VERB_ENDING: {
+ if (mCurrentIdler == task.who) {
+ if (DEBUG) {
+ Slog.i(TAG, "Task timed out when ending; unbind needed");
+ }
+ handleIdleFinishedTm(task);
+ } else {
+ if (DEBUG) {
+ Slog.w(TAG, "Ending timeout for non-current idle service!");
+ }
+ }
+ break;
+ }
+ default: {
+ Slog.w(TAG, "Unknown timeout state " + task.verb);
+ break;
+ }
+ }
+ }
+
+ void handleOpCompleteTm(ActiveTask task, int result) {
+ if (DEBUG) {
+ Slog.i(TAG, "handleOpComplete : task=" + task + " result=" + result);
+ }
+ if (task.verb == VERB_IDLING) {
+ // If the service was told to begin idling and responded positively, then
+ // it has begun idling and will eventually either explicitly finish, or
+ // reach the end of its allotted timeslice. It's running free now, so we
+ // just schedule the idle-expiration timeout under the token it's already been
+ // given and let it keep going.
+ if (result != 0) {
+ scheduleOpTimeoutTm(task);
+ } else {
+ // The idle service has indicated that it does not, in fact,
+ // need to run at present, so we immediately indicate that it's
+ // to finish idling, and go on to the next idler.
+ if (DEBUG) {
+ Slog.i(TAG, "Idler declined idling; moving along");
+ }
+ sendEndIdleTm(task.who);
+ }
+ } else {
+ // In the idling case, the task will be cleared either as the result of a timeout
+ // or of an explicit idleFinished(). For all other operations (binding, ending) we
+ // are done with the task as such, so we remove it from our bookkeeping.
+ if (DEBUG) {
+ Slog.i(TAG, "Clearing task " + task);
+ }
+ mPendingOperations.remove(task.token);
+ if (task.verb == VERB_ENDING) {
+ // The last bit of handshaking around idle cessation for this target
+ handleIdleFinishedTm(task);
+ }
+ }
+ }
+
+ void handleIdleFinishedTm(ActiveTask task) {
+ final IdleServiceInfo who = task.who;
+ if (who == mCurrentIdler) {
+ if (DEBUG) {
+ Slog.i(TAG, "Current idler has finished: " + who);
+ Slog.i(TAG, "Attributing wakelock to system work source");
+ }
+ mContext.unbindService(mConnection);
+ startNextIdleServiceTm();
+ } else {
+ Slog.w(TAG, "finish from non-current idle service? " + who);
+ }
+ }
+
+ void updateIdleServiceQueueTm() {
+ if (DEBUG) {
+ Slog.i(TAG, "Updating idle service queue");
+ }
+ PackageManager pm = mContext.getPackageManager();
+ Intent idleIntent = new Intent(IdleService.SERVICE_INTERFACE);
+ List<ResolveInfo> services = pm.queryIntentServices(idleIntent, 0);
+ for (ResolveInfo info : services) {
+ if (info.serviceInfo != null) {
+ if (IdleService.PERMISSION_BIND.equals(info.serviceInfo.permission)) {
+ final ComponentName componentName = new ComponentName(
+ info.serviceInfo.packageName,
+ info.serviceInfo.name);
+ if (DEBUG) {
+ Slog.i(TAG, " - " + componentName);
+ }
+ if (!mIdleServices.containsKey(componentName)) {
+ if (DEBUG) {
+ Slog.i(TAG, " + not known; adding");
+ }
+ IdleServiceInfo serviceInfo = new IdleServiceInfo(info, componentName);
+ mIdleServices.put(componentName, serviceInfo);
+ mIdleServiceQueue.add(serviceInfo);
+ }
+ } else {
+ if (DEBUG) {
+ Slog.i(TAG, "Idle service " + info.serviceInfo
+ + " does not have required permission; ignoring");
+ }
+ }
+ }
+ }
+ }
+
+ void startNextIdleServiceTm() {
+ mWakeLock.setWorkSource(mSystemWorkSource);
+
+ if (mLastIdler == null) {
+ // we've run the queue; nothing more to do until the next idle interval.
+ if (DEBUG) {
+ Slog.i(TAG, "Queue already drained; nothing more to do");
+ }
+ return;
+ }
+
+ if (DEBUG) {
+ Slog.i(TAG, "startNextIdleService : last=" + mLastIdler + " cur=" + mCurrentIdler);
+ if (mIdleServiceQueue.size() > 0) {
+ int i = 0;
+ Slog.i(TAG, "Queue (" + mIdleServiceQueue.size() + "):");
+ for (IdleServiceInfo info : mIdleServiceQueue) {
+ Slog.i(TAG, " " + i + " : " + info);
+ i++;
+ }
+ }
+ }
+ if (mCurrentIdler != mLastIdler) {
+ if (mIdleServiceQueue.size() > 0) {
+ IdleServiceInfo target = mIdleServiceQueue.pop();
+ if (DEBUG) {
+ Slog.i(TAG, "starting next idle service " + target);
+ }
+ Intent idleIntent = new Intent(IdleService.SERVICE_INTERFACE);
+ idleIntent.setComponent(target.componentName);
+ mCurrentIdler = target;
+ ActiveTask task = new ActiveTask(target, VERB_BINDING);
+ scheduleOpTimeoutTm(task);
+ boolean bindOk = mContext.bindServiceAsUser(idleIntent, mConnection,
+ Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY, UserHandle.OWNER);
+ if (!bindOk) {
+ if (DEBUG) {
+ Slog.w(TAG, "bindService() to " + target.componentName
+ + " failed");
+ }
+ } else {
+ mIdleServiceQueue.add(target); // at the end for next time
+ if (DEBUG) { Slog.i(TAG, "Attributing wakelock to target uid " + target.uid); }
+ mWakeLock.setWorkSource(new WorkSource(target.uid));
+ }
+ } else {
+ // Queue is empty but mLastIdler is non-null -- eeep. Clear *everything*
+ // and wind up until the next time around.
+ Slog.e(TAG, "Queue unexpectedly empty; resetting. last="
+ + mLastIdler + " cur=" + mCurrentIdler);
+ mHandler.removeMessages(MSG_TIMEOUT);
+ mPendingOperations.clear();
+ stopIdleMaintenanceTm();
+ }
+ } else {
+ // we've reached the place we started, so mark the queue as drained
+ if (DEBUG) {
+ Slog.i(TAG, "Reached end of queue.");
+ }
+ stopIdleMaintenanceTm();
+ }
+ }
+
+ void sendStartIdleTm(IdleServiceInfo who) {
+ ActiveTask task = new ActiveTask(who, VERB_IDLING);
+ scheduleOpTimeoutTm(task);
+ try {
+ who.service.startIdleMaintenance(mCallback, task.token);
+ } catch (RemoteException e) {
+ // We bound to it, but now we can't reach it. Bail and go on to the
+ // next service.
+ mContext.unbindService(mConnection);
+ if (DEBUG) { Slog.i(TAG, "Attributing wakelock to system work source"); }
+ mHandler.removeMessages(MSG_TIMEOUT);
+ startNextIdleServiceTm();
+ }
+ }
+
+ void sendEndIdleTm(IdleServiceInfo who) {
+ ActiveTask task = new ActiveTask(who, VERB_ENDING);
+ scheduleOpTimeoutTm(task);
+ if (DEBUG) {
+ Slog.i(TAG, "Sending end-idle to " + who);
+ }
+ try {
+ who.service.stopIdleMaintenance(mCallback, task.token);
+ } catch (RemoteException e) {
+ // We bound to it, but now we can't reach it. Bail and go on to the
+ // next service.
+ mContext.unbindService(mConnection);
+ if (DEBUG) { Slog.i(TAG, "Attributing wakelock to system work source"); }
+ mHandler.removeMessages(MSG_TIMEOUT);
+ startNextIdleServiceTm();
+ }
+ }
+
+ ServiceConnection mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (DEBUG) {
+ Slog.i(TAG, "onServiceConnected(" + name + ")");
+ }
+ IdleServiceInfo info = mIdleServices.get(name);
+ if (info != null) {
+ // Bound! Cancel the bind timeout
+ mHandler.removeMessages(MSG_TIMEOUT);
+ // Now tell it to start its idle work
+ info.service = IIdleService.Stub.asInterface(service);
+ sendStartIdleTm(info);
+ } else {
+ // We bound to a service we don't know about. That's ungood.
+ Slog.e(TAG, "Connected to unexpected component " + name);
+ mContext.unbindService(this);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ if (DEBUG) {
+ Slog.i(TAG, "onServiceDisconnected(" + name + ")");
+ }
+ IdleServiceInfo who = mIdleServices.get(name);
+ if (who == mCurrentIdler) {
+ // Hm, okay; they didn't tell us they were finished but they
+ // went away. Crashed, probably. Oh well. They're gone, so
+ // we can't finish them cleanly; just force things along.
+ Slog.w(TAG, "Idler unexpectedly vanished: " + mCurrentIdler);
+ mContext.unbindService(this);
+ mHandler.removeMessages(MSG_TIMEOUT);
+ startNextIdleServiceTm();
+ } else {
+ // Not the current idler, so we don't interrupt our process...
+ if (DEBUG) {
+ Slog.w(TAG, "Disconnect of abandoned or unexpected service " + name);
+ }
+ }
+ }
+ };
+
+ // Schedules a timeout / end-of-work based on the task verb
+ void scheduleOpTimeoutTm(ActiveTask task) {
+ final long timeoutMillis = (task.verb == VERB_IDLING) ? IDLE_TIMESLICE : OP_TIMEOUT;
+ if (DEBUG) {
+ Slog.i(TAG, "Scheduling timeout (token " + task.token
+ + " : verb " + task.verb + ") for " + task + " in " + timeoutMillis);
+ }
+ mPendingOperations.put(task.token, task);
+ mHandler.removeMessages(MSG_TIMEOUT);
+ final Message msg = mHandler.obtainMessage(MSG_TIMEOUT, 0, task.token);
+ mHandler.sendMessageDelayed(msg, timeoutMillis);
+ }
+
+ // -------------------------------------------------------------------------------
public IdleMaintenanceService(Context context, BatteryService batteryService) {
mContext = context;
mBatteryService = batteryService;
@@ -111,9 +574,10 @@
mAlarmService = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
+ mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
- mHandler = new Handler(mContext.getMainLooper());
+ mHandler = new IdleHandler(mContext.getMainLooper());
+ mCallback = new IdleCallback();
Intent intent = new Intent(ACTION_UPDATE_IDLE_MAINTENANCE_STATE);
intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
@@ -159,26 +623,28 @@
mAlarmService.cancel(mUpdateIdleMaintenanceStatePendingIntent);
}
- private void updateIdleMaintenanceState(boolean noisy) {
+ private void updateIdleMaintenanceStateTm(boolean noisy) {
if (mIdleMaintenanceStarted) {
// Idle maintenance can be interrupted by user activity, or duration
// time out, or low battery.
- if (!lastUserActivityPermitsIdleMaintenanceRunning()
- || !batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning()) {
+ final boolean batteryOk
+ = batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning();
+ if (!lastUserActivityPermitsIdleMaintenanceRunning() || !batteryOk) {
unscheduleUpdateIdleMaintenanceState();
mIdleMaintenanceStarted = false;
- EventLogTags.writeIdleMaintenanceWindowFinish(SystemClock.elapsedRealtime(),
- mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(),
- isBatteryCharging() ? 1 : 0);
- sendIdleMaintenanceEndIntent();
// We stopped since we don't have enough battery or timed out but the
// user is not using the device, so we should be able to run maintenance
// in the next maintenance window since the battery may be charged
// without interaction and the min interval between maintenances passed.
- if (!batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning()) {
+ if (!batteryOk) {
scheduleUpdateIdleMaintenanceState(
getNextIdleMaintenanceIntervalStartFromNow());
}
+
+ EventLogTags.writeIdleMaintenanceWindowFinish(SystemClock.elapsedRealtime(),
+ mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(),
+ isBatteryCharging() ? 1 : 0);
+ scheduleIdleFinishTm();
}
} else if (deviceStatePermitsIdleMaintenanceStart(noisy)
&& lastUserActivityPermitsIdleMaintenanceStart(noisy)
@@ -191,7 +657,7 @@
mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(),
isBatteryCharging() ? 1 : 0);
mLastIdleMaintenanceStartTimeMillis = SystemClock.elapsedRealtime();
- sendIdleMaintenanceStartIntent();
+ startIdleMaintenanceTm();
} else if (lastUserActivityPermitsIdleMaintenanceStart(noisy)) {
if (lastRunPermitsIdleMaintenanceStart(noisy)) {
// The user does not use the device and we did not run maintenance in more
@@ -207,27 +673,57 @@
}
}
+ void startIdleMaintenanceTm() {
+ if (DEBUG) {
+ Slog.i(TAG, "*** Starting idle maintenance ***");
+ }
+ if (DEBUG) { Slog.i(TAG, "Attributing wakelock to system work source"); }
+ mWakeLock.setWorkSource(mSystemWorkSource);
+ mWakeLock.acquire();
+ updateIdleServiceQueueTm();
+ mCurrentIdler = null;
+ mLastIdler = (mIdleServiceQueue.size() > 0) ? mIdleServiceQueue.peekLast() : null;
+ startNextIdleServiceTm();
+ }
+
+ // Start a graceful wind-down of the idle maintenance state: end the current idler
+ // and pretend that we've finished running the queue. If there's no current idler,
+ // this is a no-op.
+ void scheduleIdleFinishTm() {
+ if (mCurrentIdler != null) {
+ if (DEBUG) {
+ Slog.i(TAG, "*** Finishing idle maintenance ***");
+ }
+ mLastIdler = mCurrentIdler;
+ sendEndIdleTm(mCurrentIdler);
+ } else {
+ if (DEBUG) {
+ Slog.w(TAG, "Asked to finish idle maintenance but we're done already");
+ }
+ }
+ }
+
+ // Actual finalization of the idle maintenance sequence
+ void stopIdleMaintenanceTm() {
+ if (mLastIdler != null) {
+ if (DEBUG) {
+ Slog.i(TAG, "*** Idle maintenance shutdown ***");
+ }
+ mWakeLock.setWorkSource(mSystemWorkSource);
+ mLastIdler = mCurrentIdler = null;
+ updateIdleMaintenanceStateTm(false); // resets 'started' and schedules next window
+ mWakeLock.release();
+ } else {
+ Slog.e(TAG, "ERROR: idle shutdown but invariants not held. last=" + mLastIdler
+ + " cur=" + mCurrentIdler + " size=" + mIdleServiceQueue.size());
+ }
+ }
+
private long getNextIdleMaintenanceIntervalStartFromNow() {
return mLastIdleMaintenanceStartTimeMillis + MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS
- SystemClock.elapsedRealtime();
}
- private void sendIdleMaintenanceStartIntent() {
- mWakeLock.acquire();
- try {
- ActivityManagerNative.getDefault().performIdleMaintenance();
- } catch (RemoteException e) {
- }
- mContext.sendOrderedBroadcastAsUser(sIdleMaintenanceStartIntent, UserHandle.ALL,
- null, this, mHandler, Activity.RESULT_OK, null, null);
- }
-
- private void sendIdleMaintenanceEndIntent() {
- mWakeLock.acquire();
- mContext.sendOrderedBroadcastAsUser(sIdleMaintenanceEndIntent, UserHandle.ALL,
- null, this, mHandler, Activity.RESULT_OK, null, null);
- }
-
private boolean deviceStatePermitsIdleMaintenanceStart(boolean noisy) {
final int minBatteryLevel = isBatteryCharging()
? MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_CHARGING
@@ -281,7 +777,7 @@
@Override
public void onReceive(Context context, Intent intent) {
if (DEBUG) {
- Log.i(LOG_TAG, intent.getAction());
+ Log.i(TAG, intent.getAction());
}
String action = intent.getAction();
if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
@@ -292,7 +788,7 @@
// next release. The only client for this for now is internal an holds
// a wake lock correctly.
if (mIdleMaintenanceStarted) {
- updateIdleMaintenanceState(false);
+ updateIdleMaintenanceStateTm(false);
}
} else if (Intent.ACTION_SCREEN_ON.equals(action)
|| Intent.ACTION_DREAMING_STOPPED.equals(action)) {
@@ -302,7 +798,7 @@
unscheduleUpdateIdleMaintenanceState();
// If the screen went on/stopped dreaming, we know the user is using the
// device which means that idle maintenance should be stopped if running.
- updateIdleMaintenanceState(false);
+ updateIdleMaintenanceStateTm(false);
} else if (Intent.ACTION_SCREEN_OFF.equals(action)
|| Intent.ACTION_DREAMING_STARTED.equals(action)) {
mLastUserActivityElapsedTimeMillis = SystemClock.elapsedRealtime();
@@ -311,17 +807,12 @@
// this timeout elapses since the device may go to sleep by then.
scheduleUpdateIdleMaintenanceState(MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START);
} else if (ACTION_UPDATE_IDLE_MAINTENANCE_STATE.equals(action)) {
- updateIdleMaintenanceState(false);
+ updateIdleMaintenanceStateTm(false);
} else if (ACTION_FORCE_IDLE_MAINTENANCE.equals(action)) {
long now = SystemClock.elapsedRealtime() - 1;
mLastUserActivityElapsedTimeMillis = now - MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START;
mLastIdleMaintenanceStartTimeMillis = now - MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS;
- updateIdleMaintenanceState(true);
- } else if (Intent.ACTION_IDLE_MAINTENANCE_START.equals(action)
- || Intent.ACTION_IDLE_MAINTENANCE_END.equals(action)) {
- // We were holding a wake lock while broadcasting the idle maintenance
- // intents but now that we finished the broadcast release the wake lock.
- mWakeLock.release();
+ updateIdleMaintenanceStateTm(true);
}
}
}
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index 816ae69..e6e4bca 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -107,6 +107,9 @@
class MountService extends IMountService.Stub
implements INativeDaemonConnectorCallbacks, Watchdog.Monitor {
+ // Static direct instance pointer for the tightly-coupled idle service to use
+ static MountService sSelf = null;
+
// TODO: listen for user creation/deletion
private static final boolean LOCAL_LOGD = false;
@@ -345,6 +348,7 @@
private static final int H_UNMOUNT_PM_DONE = 2;
private static final int H_UNMOUNT_MS = 3;
private static final int H_SYSTEM_READY = 4;
+ private static final int H_FSTRIM = 5;
private static final int RETRY_UNMOUNT_DELAY = 30; // in ms
private static final int MAX_UNMOUNT_RETRIES = 4;
@@ -494,6 +498,24 @@
}
break;
}
+ case H_FSTRIM: {
+ waitForReady();
+ Slog.i(TAG, "Running fstrim idle maintenance");
+ try {
+ // This method must be run on the main (handler) thread,
+ // so it is safe to directly call into vold.
+ mConnector.execute("fstrim", "dotrim");
+ EventLogTags.writeFstrimStart(SystemClock.elapsedRealtime());
+ } catch (NativeDaemonConnectorException ndce) {
+ Slog.e(TAG, "Failed to run fstrim!");
+ }
+ // invoke the completion callback, if any
+ Runnable callback = (Runnable) msg.obj;
+ if (callback != null) {
+ callback.run();
+ }
+ break;
+ }
}
}
};
@@ -608,27 +630,6 @@
}
};
- private final BroadcastReceiver mIdleMaintenanceReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- waitForReady();
- String action = intent.getAction();
- // Since fstrim will be run on a daily basis we do not expect
- // fstrim to be too long, so it is not interruptible. We will
- // implement interruption only in case we see issues.
- if (Intent.ACTION_IDLE_MAINTENANCE_START.equals(action)) {
- try {
- // This method runs on the handler thread,
- // so it is safe to directly call into vold.
- mConnector.execute("fstrim", "dotrim");
- EventLogTags.writeFstrimStart(SystemClock.elapsedRealtime());
- } catch (NativeDaemonConnectorException ndce) {
- Slog.e(TAG, "Failed to run fstrim!");
- }
- }
- }
- };
-
private final class MountServiceBinderListener implements IBinder.DeathRecipient {
final IMountServiceListener mListener;
@@ -646,6 +647,10 @@
}
}
+ void runIdleMaintenance(Runnable callback) {
+ mHandler.sendMessage(mHandler.obtainMessage(H_FSTRIM, callback));
+ }
+
private void doShareUnshareVolume(String path, String method, boolean enable) {
// TODO: Add support for multiple share methods
if (!method.equals("ums")) {
@@ -1337,6 +1342,8 @@
* @param context Binder context for this service
*/
public MountService(Context context) {
+ sSelf = this;
+
mContext = context;
synchronized (mVolumesLock) {
@@ -1363,12 +1370,6 @@
mUsbReceiver, new IntentFilter(UsbManager.ACTION_USB_STATE), null, mHandler);
}
- // Watch for idle maintenance changes
- IntentFilter idleMaintenanceFilter = new IntentFilter();
- idleMaintenanceFilter.addAction(Intent.ACTION_IDLE_MAINTENANCE_START);
- mContext.registerReceiverAsUser(mIdleMaintenanceReceiver, UserHandle.ALL,
- idleMaintenanceFilter, null, mHandler);
-
// Add OBB Action Handler to MountService thread.
mObbActionHandler = new ObbActionHandler(IoThread.get().getLooper());
diff --git a/services/core/java/com/android/server/MountServiceIdler.java b/services/core/java/com/android/server/MountServiceIdler.java
new file mode 100644
index 0000000..8b19321
--- /dev/null
+++ b/services/core/java/com/android/server/MountServiceIdler.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.app.maintenance.IdleService;
+import android.util.Slog;
+
+public class MountServiceIdler extends IdleService {
+ private static final String TAG = "MountServiceIdler";
+
+ private Runnable mFinishCallback = new Runnable() {
+ @Override
+ public void run() {
+ Slog.i(TAG, "Got mount service completion callback");
+ finishIdle();
+ }
+ };
+
+ @Override
+ public boolean onIdleStart() {
+ // 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.
+ MountService ms = MountService.sSelf;
+ if (ms != null) {
+ ms.runIdleMaintenance(mFinishCallback);
+ }
+ return ms != null;
+ }
+
+ @Override
+ public void onIdleStop() {
+ }
+}
diff --git a/tests/IdleServiceTest/Android.mk b/tests/IdleServiceTest/Android.mk
new file mode 100644
index 0000000..a7879c5
--- /dev/null
+++ b/tests/IdleServiceTest/Android.mk
@@ -0,0 +1,13 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := IdleServiceTest
+LOCAL_CERTIFICATE := platform
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_PACKAGE)
diff --git a/tests/IdleServiceTest/AndroidManifest.xml b/tests/IdleServiceTest/AndroidManifest.xml
new file mode 100644
index 0000000..16d2324
--- /dev/null
+++ b/tests/IdleServiceTest/AndroidManifest.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.idleservicetest">
+
+ <application>
+ <service android:name="TestService"
+ android:exported="true"
+ android:enabled="true"
+ android:permission="android.permission.BIND_IDLE_SERVICE" >
+ <intent-filter>
+ <action android:name="android.service.idle.IdleService" />
+ </intent-filter>
+ </service>
+
+ <service android:name="CrashingTestService"
+ android:exported="true"
+ android:enabled="true"
+ android:permission="android.permission.BIND_IDLE_SERVICE" >
+ <intent-filter>
+ <action android:name="android.service.idle.IdleService" />
+ </intent-filter>
+ </service>
+
+ <service android:name="TimeoutTestService"
+ android:exported="true"
+ android:enabled="true"
+ android:permission="android.permission.BIND_IDLE_SERVICE" >
+ <intent-filter>
+ <action android:name="android.service.idle.IdleService" />
+ </intent-filter>
+ </service>
+
+ <!-- UnpermissionedTestService should never run because it does
+ not require the necessary permission in its <service> block -->
+ <service android:name="UnpermissionedTestService"
+ android:exported="true"
+ android:enabled="true" >
+ <intent-filter>
+ <action android:name="android.service.idle.IdleService" />
+ </intent-filter>
+ </service>
+
+ </application>
+</manifest>
diff --git a/tests/IdleServiceTest/src/com/android/idleservicetest/CrashingTestService.java b/tests/IdleServiceTest/src/com/android/idleservicetest/CrashingTestService.java
new file mode 100644
index 0000000..022ebcf
--- /dev/null
+++ b/tests/IdleServiceTest/src/com/android/idleservicetest/CrashingTestService.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.idleservicetest;
+
+import android.app.maintenance.IdleService;
+import android.os.Handler;
+import android.util.Log;
+
+public class CrashingTestService extends IdleService {
+ static final String TAG = "CrashingTestService";
+
+ String mNull = null;
+
+ @Override
+ public boolean onIdleStart() {
+ Log.i(TAG, "Idle maintenance: onIdleStart()");
+
+ Handler h = new Handler();
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ Log.i(TAG, "Explicitly crashing");
+ if (mNull.equals("")) {
+ Log.i(TAG, "won't happen");
+ }
+ }
+ };
+ Log.i(TAG, "Posting explicit crash in 15 seconds");
+ h.postDelayed(r, 15 * 1000);
+ return true;
+ }
+
+ @Override
+ public void onIdleStop() {
+ Log.i(TAG, "Idle maintenance: onIdleStop()");
+ }
+
+}
diff --git a/tests/IdleServiceTest/src/com/android/idleservicetest/TestService.java b/tests/IdleServiceTest/src/com/android/idleservicetest/TestService.java
new file mode 100644
index 0000000..7e9805f
--- /dev/null
+++ b/tests/IdleServiceTest/src/com/android/idleservicetest/TestService.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.idleservicetest;
+
+import android.app.maintenance.IdleService;
+import android.os.Handler;
+import android.util.Log;
+
+public class TestService extends IdleService {
+ static final String TAG = "TestService";
+
+ @Override
+ public boolean onIdleStart() {
+ Log.i(TAG, "Idle maintenance: onIdleStart()");
+
+ Handler h = new Handler();
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ Log.i(TAG, "Explicitly finishing idle");
+ finishIdle();
+ }
+ };
+ Log.i(TAG, "Posting explicit finish in 15 seconds");
+ h.postDelayed(r, 15 * 1000);
+ return true;
+ }
+
+ @Override
+ public void onIdleStop() {
+ Log.i(TAG, "Idle maintenance: onIdleStop()");
+ }
+
+}
diff --git a/tests/IdleServiceTest/src/com/android/idleservicetest/TimeoutTestService.java b/tests/IdleServiceTest/src/com/android/idleservicetest/TimeoutTestService.java
new file mode 100644
index 0000000..b2ba21b
--- /dev/null
+++ b/tests/IdleServiceTest/src/com/android/idleservicetest/TimeoutTestService.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.idleservicetest;
+
+import android.app.maintenance.IdleService;
+import android.util.Log;
+
+public class TimeoutTestService extends IdleService {
+ private static final String TAG = "TimeoutTestService";
+
+ @Override
+ public boolean onIdleStart() {
+ Log.i(TAG, "onIdleStart() but anticipating time-slice timeout");
+ return true;
+ }
+
+ @Override
+ public void onIdleStop() {
+ Log.i(TAG, "onIdleStop() so we're done");
+ }
+
+}
diff --git a/tests/IdleServiceTest/src/com/android/idleservicetest/UnpermissionedTestService.java b/tests/IdleServiceTest/src/com/android/idleservicetest/UnpermissionedTestService.java
new file mode 100644
index 0000000..b9fe32b
--- /dev/null
+++ b/tests/IdleServiceTest/src/com/android/idleservicetest/UnpermissionedTestService.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.idleservicetest;
+
+import android.app.maintenance.IdleService;
+import android.util.Log;
+
+// Should never be invoked because its manifest declaration does not
+// require the necessary permission.
+public class UnpermissionedTestService extends IdleService {
+ private static final String TAG = "UnpermissionedTestService";
+
+ @Override
+ public boolean onIdleStart() {
+ Log.e(TAG, "onIdleStart() for this service should never be called!");
+ return false;
+ }
+
+ @Override
+ public void onIdleStop() {
+ Log.e(TAG, "onIdleStop() for this service should never be called!");
+ }
+
+}