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/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();
+    }
+
+}