diff options
| author | 2014-02-03 20:35:01 +0000 | |
|---|---|---|
| committer | 2014-02-03 20:35:01 +0000 | |
| commit | 31b4834b6d5eb1e5156950930b69221819a84766 (patch) | |
| tree | 23e90d539815121638e172865db79be02fc93c09 | |
| parent | ef83738da65a79defc6dc4f12433b327d670127e (diff) | |
| parent | d417d625d244356bc770e2692fd59e754a72f59f (diff) | |
Merge "Introduce "IdleService" API to expose idle-time maintenance to apps"
16 files changed, 1221 insertions, 83 deletions
diff --git a/Android.mk b/Android.mk index 781d5d1c8bd0..0865d24a20ee 100644 --- a/Android.mk +++ b/Android.mk @@ -89,6 +89,8 @@ LOCAL_SRC_FILES += \ 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 9a97fe4efd53..7673ff3f830c 100644 --- a/api/current.txt +++ b/api/current.txt @@ -4886,6 +4886,20 @@ package android.app.backup { } +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 000000000000..582dede9574c --- /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 000000000000..54abccdc1b4f --- /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 000000000000..2331b81aba1b --- /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 b7a06b50fef1..315b11910857 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 785e788c5c3e..ee6819903c44 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 b0a1aca37d7c..acc6abe89b33 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 @@ import android.util.Slog; * * 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 @@ public class IdleMaintenanceService extends BroadcastReceiver { 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; - private final AlarmManager mAlarmService; + // 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 @@ public class IdleMaintenanceService extends BroadcastReceiver { 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 @@ public class IdleMaintenanceService extends BroadcastReceiver { 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 @@ public class IdleMaintenanceService extends BroadcastReceiver { 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,25 +673,55 @@ public class IdleMaintenanceService extends BroadcastReceiver { } } - private long getNextIdleMaintenanceIntervalStartFromNow() { - return mLastIdleMaintenanceStartTimeMillis + MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS - - SystemClock.elapsedRealtime(); + 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(); } - private void sendIdleMaintenanceStartIntent() { - mWakeLock.acquire(); - try { - ActivityManagerNative.getDefault().performIdleMaintenance(); - } catch (RemoteException e) { + // 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"); + } } - 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); + // 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 boolean deviceStatePermitsIdleMaintenanceStart(boolean noisy) { @@ -281,7 +777,7 @@ public class IdleMaintenanceService extends BroadcastReceiver { @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 @@ public class IdleMaintenanceService extends BroadcastReceiver { // 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 @@ public class IdleMaintenanceService extends BroadcastReceiver { 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 @@ public class IdleMaintenanceService extends BroadcastReceiver { // 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 816ae6967cfd..e6e4bca4f966 100644 --- a/services/core/java/com/android/server/MountService.java +++ b/services/core/java/com/android/server/MountService.java @@ -107,6 +107,9 @@ import javax.crypto.spec.PBEKeySpec; 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 @@ class MountService extends IMountService.Stub 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 @@ class MountService extends IMountService.Stub } 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 @@ class MountService extends IMountService.Stub } }; - 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 @@ class MountService extends IMountService.Stub } } + 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 @@ class MountService extends IMountService.Stub * @param context Binder context for this service */ public MountService(Context context) { + sSelf = this; + mContext = context; synchronized (mVolumesLock) { @@ -1363,12 +1370,6 @@ class MountService extends IMountService.Stub 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 000000000000..8b1932143eab --- /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 000000000000..a7879c591840 --- /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 000000000000..16d2324dcd04 --- /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 000000000000..022ebcf6e927 --- /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 000000000000..7e9805fb913c --- /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 000000000000..b2ba21b542b2 --- /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 000000000000..b9fe32bf3923 --- /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!"); + } + +} |