Merge "Introduce "IdleService" API to expose idle-time maintenance to apps"

This commit is contained in:
Christopher Tate
2014-02-03 20:35:01 +00:00
committed by Android (Google) Code Review
16 changed files with 1223 additions and 85 deletions

View File

@ -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 \

View File

@ -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 {

View File

@ -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);
}

View File

@ -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);
}

View File

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

View File

@ -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>

View File

@ -1175,6 +1175,11 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE]-->
<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. -->

View File

@ -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;
// 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,27 +673,57 @@ public class IdleMaintenanceService extends BroadcastReceiver {
}
}
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 @@ 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);
}
}
}

View File

@ -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());

View File

@ -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() {
}
}

View File

@ -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)

View File

@ -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>

View File

@ -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()");
}
}

View File

@ -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()");
}
}

View File

@ -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");
}
}

View File

@ -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!");
}
}