blob: d660040563608393dd96ee8417ce37f8e6faa3bb [file] [log] [blame]
/*
* Copyright (C) 2008 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.cts;
import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.stubs.ActivityTestsBase;
import android.app.stubs.LocalDeniedService;
import android.app.stubs.LocalForegroundService;
import android.app.stubs.LocalGrantedService;
import android.app.stubs.LocalService;
import android.app.stubs.NullService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.support.test.InstrumentationRegistry;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.SystemClock;
import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
import android.app.stubs.R;
import com.android.compatibility.common.util.IBinderParcelable;
import com.android.compatibility.common.util.SystemUtil;
import java.util.List;
public class ServiceTest extends ActivityTestsBase {
private static final String TAG = "ServiceTest";
private static final String NOTIFICATION_CHANNEL_ID = TAG;
private static final int STATE_START_1 = 0;
private static final int STATE_START_2 = 1;
private static final int STATE_START_3 = 2;
private static final int STATE_UNBIND = 3;
private static final int STATE_DESTROY = 4;
private static final int STATE_REBIND = 5;
private static final int STATE_UNBIND_ONLY = 6;
private static final int DELAY = 5000;
private static final
String EXIST_CONN_TO_RECEIVE_SERVICE = "existing connection to receive service";
private static final String EXIST_CONN_TO_LOSE_SERVICE = "existing connection to lose service";
private static final String EXTERNAL_SERVICE_PACKAGE = "com.android.app2";
private static final String EXTERNAL_SERVICE_COMPONENT =
EXTERNAL_SERVICE_PACKAGE + "/android.app.stubs.LocalService";
private int mExpectedServiceState;
private Context mContext;
private Intent mLocalService;
private Intent mLocalDeniedService;
private Intent mLocalForegroundService;
private Intent mLocalGrantedService;
private Intent mLocalService_ApplicationHasPermission;
private Intent mLocalService_ApplicationDoesNotHavePermission;
private Intent mExternalService;
private IBinder mStateReceiver;
private static class EmptyConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
private static class NullServiceConnection implements ServiceConnection {
boolean mNullBinding = false;
@Override public void onServiceConnected(ComponentName name, IBinder service) {}
@Override public void onServiceDisconnected(ComponentName name) {}
@Override
public void onNullBinding(ComponentName name) {
synchronized (this) {
mNullBinding = true;
this.notifyAll();
}
}
public void waitForNullBinding(final long timeout) {
long now = SystemClock.uptimeMillis();
final long end = now + timeout;
synchronized (this) {
while (!mNullBinding && (now < end)) {
try {
this.wait(end - now);
} catch (InterruptedException e) {
}
now = SystemClock.uptimeMillis();
}
}
}
public boolean nullBindingReceived() {
synchronized (this) {
return mNullBinding;
}
}
}
private class TestConnection implements ServiceConnection {
private final boolean mExpectDisconnect;
private final boolean mSetReporter;
private boolean mMonitor;
private int mCount;
public TestConnection(boolean expectDisconnect, boolean setReporter) {
mExpectDisconnect = expectDisconnect;
mSetReporter = setReporter;
mMonitor = !setReporter;
}
void setMonitor(boolean v) {
mMonitor = v;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (mSetReporter) {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(LocalService.SERVICE_LOCAL);
data.writeStrongBinder(mStateReceiver);
try {
service.transact(LocalService.SET_REPORTER_CODE, data, null, 0);
} catch (RemoteException e) {
finishBad("DeadObjectException when sending reporting object");
}
data.recycle();
}
if (mMonitor) {
mCount++;
if (mExpectedServiceState == STATE_START_1) {
if (mCount == 1) {
finishGood();
} else {
finishBad("onServiceConnected() again on an object when it "
+ "should have been the first time");
}
} else if (mExpectedServiceState == STATE_START_2) {
if (mCount == 2) {
finishGood();
} else {
finishBad("onServiceConnected() the first time on an object "
+ "when it should have been the second time");
}
} else {
finishBad("onServiceConnected() called unexpectedly");
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
if (mMonitor) {
if (mExpectedServiceState == STATE_DESTROY) {
if (mExpectDisconnect) {
finishGood();
} else {
finishBad("onServiceDisconnected() when it shouldn't have been");
}
} else {
finishBad("onServiceDisconnected() called unexpectedly");
}
}
}
}
private void startExpectResult(Intent service) {
startExpectResult(service, new Bundle());
}
private void startExpectResult(Intent service, Bundle bundle) {
bundle.putParcelable(LocalService.REPORT_OBJ_NAME, new IBinderParcelable(mStateReceiver));
boolean success = false;
try {
mExpectedServiceState = STATE_START_1;
mContext.startService(new Intent(service).putExtras(bundle));
waitForResultOrThrow(DELAY, "service to start first time");
mExpectedServiceState = STATE_START_2;
mContext.startService(new Intent(service).putExtras(bundle));
waitForResultOrThrow(DELAY, "service to start second time");
success = true;
} finally {
if (!success) {
mContext.stopService(service);
}
}
mExpectedServiceState = STATE_DESTROY;
mContext.stopService(service);
waitForResultOrThrow(DELAY, "service to be destroyed");
}
private NotificationManager getNotificationManager() {
NotificationManager notificationManager =
(NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE);
return notificationManager;
}
private void sendNotification(int id, String title) {
Notification notification = new Notification.Builder(getContext(), NOTIFICATION_CHANNEL_ID)
.setContentTitle(title)
.setSmallIcon(R.drawable.black)
.build();
getNotificationManager().notify(id, notification);
}
private void cancelNotification(int id) {
getNotificationManager().cancel(id);
}
private void assertNotification(int id, String expectedTitle) {
String packageName = getContext().getPackageName();
String errorMessage = null;
for (int i = 1; i<=2; i++) {
errorMessage = null;
StatusBarNotification[] sbns = getNotificationManager().getActiveNotifications();
for (StatusBarNotification sbn : sbns) {
if (sbn.getId() == id && sbn.getPackageName().equals(packageName)) {
String actualTitle =
sbn.getNotification().extras.getString(Notification.EXTRA_TITLE);
if (expectedTitle.equals(actualTitle)) {
return;
}
// It's possible the notification hasn't been updated yet, so save the error
// message to only fail after retrying.
errorMessage = String.format("Wrong title for notification #%d: "
+ "expected '%s', actual '%s'", id, expectedTitle, actualTitle);
Log.w(TAG, errorMessage);
}
}
// Notification might not be rendered yet, wait and try again...
try {
Thread.sleep(DELAY);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
if (errorMessage != null) {
fail(errorMessage);
}
fail("No notification with id " + id + " for package " + packageName);
}
private void assertNoNotification(int id) {
String packageName = getContext().getPackageName();
StatusBarNotification found = null;
for (int i = 1; i<=2; i++) {
found = null;
StatusBarNotification[] sbns = getNotificationManager().getActiveNotifications();
for (StatusBarNotification sbn : sbns) {
if (sbn.getId() == id && sbn.getPackageName().equals(packageName)) {
found = sbn;
break;
}
}
if (found != null) {
// Notification might not be canceled yet, wait and try again...
try {
Thread.sleep(DELAY);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
assertNull("Found notification with id " + id + " for package " + packageName + ": "
+ found, found);
}
/**
* test the service lifecycle, a service can be used in two ways:
* 1 It can be started and allowed to run until someone stops it or it stops itself.
* In this mode, it's started by calling Context.startService()
* and stopped by calling Context.stopService().
* It can stop itself by calling Service.stopSelf() or Service.stopSelfResult().
* Only one stopService() call is needed to stop the service,
* no matter how many times startService() was called.
* 2 It can be operated programmatically using an interface that it defines and exports.
* Clients establish a connection to the Service object
* and use that connection to call into the service.
* The connection is established by calling Context.bindService(),
* and is closed by calling Context.unbindService().
* Multiple clients can bind to the same service.
* If the service has not already been launched, bindService() can optionally launch it.
*/
private void bindExpectResult(Intent service) {
TestConnection conn = new TestConnection(true, false);
TestConnection conn2 = new TestConnection(false, false);
boolean success = false;
try {
// Expect to see the TestConnection connected.
mExpectedServiceState = STATE_START_1;
mContext.bindService(service, conn, 0);
mContext.startService(service);
waitForResultOrThrow(DELAY, EXIST_CONN_TO_RECEIVE_SERVICE);
// Expect to see the second TestConnection connected.
mContext.bindService(service, conn2, 0);
waitForResultOrThrow(DELAY, "new connection to receive service");
mContext.unbindService(conn2);
success = true;
} finally {
if (!success) {
mContext.unbindService(conn);
mContext.unbindService(conn2);
mContext.stopService(service);
}
}
// Expect to see the TestConnection disconnected.
mExpectedServiceState = STATE_DESTROY;
mContext.stopService(service);
waitForResultOrThrow(DELAY, EXIST_CONN_TO_LOSE_SERVICE);
mContext.unbindService(conn);
conn = new TestConnection(true, true);
success = false;
try {
// Expect to see the TestConnection connected.
conn.setMonitor(true);
mExpectedServiceState = STATE_START_1;
mContext.bindService(service, conn, 0);
mContext.startService(service);
waitForResultOrThrow(DELAY, EXIST_CONN_TO_RECEIVE_SERVICE);
success = true;
} finally {
if (!success) {
mContext.unbindService(conn);
mContext.stopService(service);
}
}
// Expect to see the service unbind and then destroyed.
conn.setMonitor(false);
mExpectedServiceState = STATE_UNBIND;
mContext.stopService(service);
waitForResultOrThrow(DELAY, EXIST_CONN_TO_LOSE_SERVICE);
mContext.unbindService(conn);
conn = new TestConnection(true, true);
success = false;
try {
// Expect to see the TestConnection connected.
conn.setMonitor(true);
mExpectedServiceState = STATE_START_1;
mContext.bindService(service, conn, 0);
mContext.startService(service);
waitForResultOrThrow(DELAY, EXIST_CONN_TO_RECEIVE_SERVICE);
success = true;
} finally {
if (!success) {
mContext.unbindService(conn);
mContext.stopService(service);
}
}
// Expect to see the service unbind but not destroyed.
conn.setMonitor(false);
mExpectedServiceState = STATE_UNBIND_ONLY;
mContext.unbindService(conn);
waitForResultOrThrow(DELAY, "existing connection to unbind service");
// Expect to see the service rebound.
mExpectedServiceState = STATE_REBIND;
mContext.bindService(service, conn, 0);
waitForResultOrThrow(DELAY, "existing connection to rebind service");
// Expect to see the service unbind and then destroyed.
mExpectedServiceState = STATE_UNBIND;
mContext.stopService(service);
waitForResultOrThrow(DELAY, EXIST_CONN_TO_LOSE_SERVICE);
mContext.unbindService(conn);
}
/**
* test automatically create the service as long as the binding exists
* and disconnect from an application service
*/
private void bindAutoExpectResult(Intent service) {
TestConnection conn = new TestConnection(false, true);
boolean success = false;
try {
conn.setMonitor(true);
mExpectedServiceState = STATE_START_1;
mContext.bindService(
service, conn, Context.BIND_AUTO_CREATE);
waitForResultOrThrow(DELAY, "connection to start and receive service");
success = true;
} finally {
if (!success) {
mContext.unbindService(conn);
}
}
mExpectedServiceState = STATE_UNBIND;
mContext.unbindService(conn);
waitForResultOrThrow(DELAY, "disconnecting from service");
}
@Override
protected void setUp() throws Exception {
super.setUp();
mContext = getContext();
mLocalService = new Intent(mContext, LocalService.class);
mExternalService = new Intent();
mExternalService.setComponent(ComponentName.unflattenFromString(EXTERNAL_SERVICE_COMPONENT));
mLocalForegroundService = new Intent(mContext, LocalForegroundService.class);
mLocalDeniedService = new Intent(mContext, LocalDeniedService.class);
mLocalGrantedService = new Intent(mContext, LocalGrantedService.class);
mLocalService_ApplicationHasPermission = new Intent(
LocalService.SERVICE_LOCAL_GRANTED, null /*uri*/, mContext, LocalService.class);
mLocalService_ApplicationDoesNotHavePermission = new Intent(
LocalService.SERVICE_LOCAL_DENIED, null /*uri*/, mContext, LocalService.class);
mStateReceiver = new MockBinder();
getNotificationManager().createNotificationChannel(new NotificationChannel(
NOTIFICATION_CHANNEL_ID, "name", NotificationManager.IMPORTANCE_DEFAULT));
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
getNotificationManager().deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
mContext.stopService(mLocalService);
mContext.stopService(mLocalForegroundService);
mContext.stopService(mLocalGrantedService);
mContext.stopService(mLocalService_ApplicationHasPermission);
mContext.stopService(mExternalService);
}
private class MockBinder extends Binder {
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply,
int flags) throws RemoteException {
if (code == LocalService.STARTED_CODE) {
data.enforceInterface(LocalService.SERVICE_LOCAL);
int count = data.readInt();
if (mExpectedServiceState == STATE_START_1) {
if (count == 1) {
finishGood();
} else {
finishBad("onStart() again on an object when it "
+ "should have been the first time");
}
} else if (mExpectedServiceState == STATE_START_2) {
if (count == 2) {
finishGood();
} else {
finishBad("onStart() the first time on an object when it "
+ "should have been the second time");
}
} else if (mExpectedServiceState == STATE_START_3) {
if (count == 3) {
finishGood();
} else {
finishBad("onStart() the first time on an object when it "
+ "should have been the third time");
}
} else {
finishBad("onStart() was called when not expected (state="
+ mExpectedServiceState + ")");
}
return true;
} else if (code == LocalService.DESTROYED_CODE) {
data.enforceInterface(LocalService.SERVICE_LOCAL);
if (mExpectedServiceState == STATE_DESTROY) {
finishGood();
} else {
finishBad("onDestroy() was called when not expected (state="
+ mExpectedServiceState + ")");
}
return true;
} else if (code == LocalService.UNBIND_CODE) {
data.enforceInterface(LocalService.SERVICE_LOCAL);
if (mExpectedServiceState == STATE_UNBIND) {
mExpectedServiceState = STATE_DESTROY;
} else if (mExpectedServiceState == STATE_UNBIND_ONLY) {
finishGood();
} else {
finishBad("onUnbind() was called when not expected (state="
+ mExpectedServiceState + ")");
}
return true;
} else if (code == LocalService.REBIND_CODE) {
data.enforceInterface(LocalService.SERVICE_LOCAL);
if (mExpectedServiceState == STATE_REBIND) {
finishGood();
} else {
finishBad("onRebind() was called when not expected (state="
+ mExpectedServiceState + ")");
}
return true;
} else {
return super.onTransact(code, data, reply, flags);
}
}
}
public void testLocalStartClass() throws Exception {
startExpectResult(mLocalService);
}
public void testLocalStartAction() throws Exception {
startExpectResult(new Intent(
LocalService.SERVICE_LOCAL, null /*uri*/, mContext, LocalService.class));
}
public void testLocalBindClass() throws Exception {
bindExpectResult(mLocalService);
}
/* Just the Intent for a foreground service */
private Intent foregroundServiceIntent(int command) {
return new Intent(mLocalForegroundService)
.putExtras(LocalForegroundService.newCommand(mStateReceiver, command));
}
private void startForegroundService(int command) {
mContext.startService(foregroundServiceIntent(command));
}
/* Start the service in a way that promises to go into the foreground */
private void startRequiredForegroundService(int command) {
mContext.startForegroundService(foregroundServiceIntent(command));
}
@MediumTest
public void testForegroundService_dontRemoveNotificationOnStop() throws Exception {
boolean success = false;
try {
// Start service as foreground - it should show notification #1
mExpectedServiceState = STATE_START_1;
startForegroundService(LocalForegroundService.COMMAND_START_FOREGROUND);
waitForResultOrThrow(DELAY, "service to start first time");
assertNotification(1, LocalForegroundService.getNotificationTitle(1));
// Stop foreground without removing notification - it should still show notification #1
mExpectedServiceState = STATE_START_2;
startForegroundService(
LocalForegroundService.COMMAND_STOP_FOREGROUND_DONT_REMOVE_NOTIFICATION);
waitForResultOrThrow(DELAY, "service to stop foreground");
assertNotification(1, LocalForegroundService.getNotificationTitle(1));
// Sends another notification reusing the same notification id.
String newTitle = "YODA I AM";
sendNotification(1, newTitle);
assertNotification(1, newTitle);
// Start service as foreground again - it should kill notification #1 and show #2
mExpectedServiceState = STATE_START_3;
startForegroundService(LocalForegroundService.COMMAND_START_FOREGROUND);
waitForResultOrThrow(DELAY, "service to start foreground 2nd time");
assertNoNotification(1);
assertNotification(2, LocalForegroundService.getNotificationTitle(2));
success = true;
} finally {
if (!success) {
mContext.stopService(mLocalForegroundService);
}
}
mExpectedServiceState = STATE_DESTROY;
mContext.stopService(mLocalForegroundService);
waitForResultOrThrow(DELAY, "service to be destroyed");
assertNoNotification(1);
assertNoNotification(2);
}
@MediumTest
public void testForegroundService_removeNotificationOnStop() throws Exception {
testForegroundServiceRemoveNotificationOnStop(false);
}
@MediumTest
public void testForegroundService_removeNotificationOnStopUsingFlags() throws Exception {
testForegroundServiceRemoveNotificationOnStop(true);
}
private void testForegroundServiceRemoveNotificationOnStop(boolean usingFlags)
throws Exception {
boolean success = false;
try {
// Start service as foreground - it should show notification #1
Log.d(TAG, "Expecting first start state...");
mExpectedServiceState = STATE_START_1;
startForegroundService(LocalForegroundService.COMMAND_START_FOREGROUND);
waitForResultOrThrow(DELAY, "service to start first time");
assertNotification(1, LocalForegroundService.getNotificationTitle(1));
// Stop foreground removing notification
Log.d(TAG, "Expecting second start state...");
mExpectedServiceState = STATE_START_2;
if (usingFlags) {
startForegroundService(LocalForegroundService
.COMMAND_STOP_FOREGROUND_REMOVE_NOTIFICATION_USING_FLAGS);
} else {
startForegroundService(LocalForegroundService
.COMMAND_STOP_FOREGROUND_REMOVE_NOTIFICATION);
}
waitForResultOrThrow(DELAY, "service to stop foreground");
assertNoNotification(1);
// Start service as foreground again - it should show notification #2
mExpectedServiceState = STATE_START_3;
startForegroundService(LocalForegroundService.COMMAND_START_FOREGROUND);
waitForResultOrThrow(DELAY, "service to start as foreground 2nd time");
assertNotification(2, LocalForegroundService.getNotificationTitle(2));
success = true;
} finally {
if (!success) {
mContext.stopService(mLocalForegroundService);
}
}
mExpectedServiceState = STATE_DESTROY;
mContext.stopService(mLocalForegroundService);
waitForResultOrThrow(DELAY, "service to be destroyed");
assertNoNotification(1);
assertNoNotification(2);
}
public void testRunningServices() throws Exception {
final int maxReturnedServices = 10;
final Bundle bundle = new Bundle();
bundle.putParcelable(LocalService.REPORT_OBJ_NAME, new IBinderParcelable(mStateReceiver));
boolean success = false;
ActivityManager am = mContext.getSystemService(ActivityManager.class);
// Put target app on whitelist so we can start its services.
SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
"cmd deviceidle whitelist +" + EXTERNAL_SERVICE_PACKAGE);
// No services should be reported back at the beginning
assertEquals(0, am.getRunningServices(maxReturnedServices).size());
try {
mExpectedServiceState = STATE_START_1;
// Start external service.
mContext.startService(new Intent(mExternalService).putExtras(bundle));
waitForResultOrThrow(DELAY, "external service to start first time");
// Ensure we can't see service.
assertEquals(0, am.getRunningServices(maxReturnedServices).size());
// Start local service.
mContext.startService(new Intent(mLocalService).putExtras(bundle));
waitForResultOrThrow(DELAY, "local service to start first time");
success = true;
// Ensure we can see service and it is ours.
List<ActivityManager.RunningServiceInfo> services = am.getRunningServices(maxReturnedServices);
assertEquals(1, services.size());
assertEquals(android.os.Process.myUid(), services.get(0).uid);
} finally {
SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
"cmd deviceidle whitelist -" + EXTERNAL_SERVICE_PACKAGE);
if (!success) {
mContext.stopService(mLocalService);
mContext.stopService(mExternalService);
}
}
mExpectedServiceState = STATE_DESTROY;
mContext.stopService(mExternalService);
waitForResultOrThrow(DELAY, "external service to be destroyed");
mContext.stopService(mLocalService);
waitForResultOrThrow(DELAY, "local service to be destroyed");
// Once our service has stopped, make sure we can't see any services.
assertEquals(0, am.getRunningServices(maxReturnedServices).size());
}
@MediumTest
public void testForegroundService_detachNotificationOnStop() throws Exception {
String newTitle = null;
boolean success = false;
try {
// Start service as foreground - it should show notification #1
mExpectedServiceState = STATE_START_1;
startForegroundService(LocalForegroundService.COMMAND_START_FOREGROUND);
waitForResultOrThrow(DELAY, "service to start first time");
assertNotification(1, LocalForegroundService.getNotificationTitle(1));
// Detaching notification
mExpectedServiceState = STATE_START_2;
startForegroundService(
LocalForegroundService.COMMAND_STOP_FOREGROUND_DETACH_NOTIFICATION);
waitForResultOrThrow(DELAY, "service to stop foreground");
assertNotification(1, LocalForegroundService.getNotificationTitle(1));
// Sends another notification reusing the same notification id.
newTitle = "YODA I AM";
sendNotification(1, newTitle);
assertNotification(1, newTitle);
// Start service as foreground again - it should show notification #2..
mExpectedServiceState = STATE_START_3;
startForegroundService(LocalForegroundService.COMMAND_START_FOREGROUND);
waitForResultOrThrow(DELAY, "service to start as foreground 2nd time");
assertNotification(2, LocalForegroundService.getNotificationTitle(2));
//...but keeping notification #1
assertNotification(1, newTitle);
success = true;
} finally {
if (!success) {
mContext.stopService(mLocalForegroundService);
}
}
mExpectedServiceState = STATE_DESTROY;
mContext.stopService(mLocalForegroundService);
waitForResultOrThrow(DELAY, "service to be destroyed");
if (newTitle == null) {
assertNoNotification(1);
} else {
assertNotification(1, newTitle);
cancelNotification(1);
assertNoNotification(1);
}
assertNoNotification(2);
}
class TestSendCallback implements PendingIntent.OnFinished {
public volatile int result = -1;
@Override
public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
String resultData, Bundle resultExtras) {
Log.i(TAG, "foreground service PendingIntent callback got " + resultCode);
this.result = resultCode;
}
}
@MediumTest
public void testForegroundService_pendingIntentForeground() throws Exception {
boolean success = false;
PendingIntent pi = PendingIntent.getForegroundService(mContext, 1,
foregroundServiceIntent(LocalForegroundService.COMMAND_START_FOREGROUND),
PendingIntent.FLAG_CANCEL_CURRENT);
TestSendCallback callback = new TestSendCallback();
try {
mExpectedServiceState = STATE_START_1;
pi.send(5038, callback, null);
waitForResultOrThrow(DELAY, "service to start first time");
assertTrue(callback.result > -1);
success = true;
} finally {
if (!success) {
mContext.stopService(mLocalForegroundService);
}
}
mExpectedServiceState = STATE_DESTROY;
mContext.stopService(mLocalForegroundService);
waitForResultOrThrow(DELAY, "pendingintent service to be destroyed");
}
@MediumTest
public void testLocalBindAction() throws Exception {
bindExpectResult(new Intent(
LocalService.SERVICE_LOCAL, null /*uri*/, mContext, LocalService.class));
}
@MediumTest
public void testLocalBindAutoClass() throws Exception {
bindAutoExpectResult(mLocalService);
}
@MediumTest
public void testLocalBindAutoAction() throws Exception {
bindAutoExpectResult(new Intent(
LocalService.SERVICE_LOCAL, null /*uri*/, mContext, LocalService.class));
}
@MediumTest
public void testLocalStartClassPermissions() throws Exception {
startExpectResult(mLocalGrantedService);
startExpectResult(mLocalDeniedService);
}
@MediumTest
public void testLocalStartActionPermissions() throws Exception {
startExpectResult(mLocalService_ApplicationHasPermission);
startExpectResult(mLocalService_ApplicationDoesNotHavePermission);
}
@MediumTest
public void testLocalBindClassPermissions() throws Exception {
bindExpectResult(mLocalGrantedService);
bindExpectResult(mLocalDeniedService);
}
@MediumTest
public void testLocalBindActionPermissions() throws Exception {
bindExpectResult(mLocalService_ApplicationHasPermission);
bindExpectResult(mLocalService_ApplicationDoesNotHavePermission);
}
@MediumTest
public void testLocalBindAutoClassPermissionGranted() throws Exception {
bindAutoExpectResult(mLocalGrantedService);
}
@MediumTest
public void testLocalBindAutoActionPermissionGranted() throws Exception {
bindAutoExpectResult(mLocalService_ApplicationHasPermission);
}
@MediumTest
public void testLocalUnbindTwice() throws Exception {
EmptyConnection conn = new EmptyConnection();
mContext.bindService(
mLocalService_ApplicationHasPermission, conn, 0);
mContext.unbindService(conn);
try {
mContext.unbindService(conn);
fail("No exception thrown on the second unbind");
} catch (IllegalArgumentException e) {
// expected
}
}
@MediumTest
public void testImplicitIntentFailsOnApiLevel21() throws Exception {
Intent intent = new Intent(LocalService.SERVICE_LOCAL);
EmptyConnection conn = new EmptyConnection();
try {
mContext.bindService(intent, conn, 0);
mContext.unbindService(conn);
fail("Implicit intents should be disallowed for apps targeting API 21+");
} catch (IllegalArgumentException e) {
// expected
}
}
/**
* Verify that when the requested service's onBind() returns null,
* the connection's onNullBinding() method is invoked.
*/
@MediumTest
public void testNullServiceBinder() throws Exception {
Intent intent = new Intent(mContext, NullService.class);
intent.setAction("testNullServiceBinder");
NullServiceConnection conn1 = new NullServiceConnection();
NullServiceConnection conn2 = new NullServiceConnection();
try {
assertTrue(mContext.bindService(intent, conn1, Context.BIND_AUTO_CREATE));
conn1.waitForNullBinding(DELAY);
assertTrue(conn1.nullBindingReceived());
assertTrue(mContext.bindService(intent, conn2, Context.BIND_AUTO_CREATE));
conn2.waitForNullBinding(DELAY);
assertTrue(conn2.nullBindingReceived());
} finally {
mContext.unbindService(conn1);
mContext.unbindService(conn2);
}
}
}