blob: 6bea94498f5f8eff3c0c3b6d0468f99284550bb8 [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.Activity;
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.IsolatedService;
import android.app.stubs.LaunchpadActivity;
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.app.stubs.R;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Process;
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.util.SparseArray;
import androidx.test.filters.FlakyTest;
import androidx.test.InstrumentationRegistry;
import com.android.compatibility.common.util.IBinderParcelable;
import com.android.compatibility.common.util.SystemUtil;
import com.android.server.am.nano.ActivityManagerServiceDumpProcessesProto;
import com.android.server.am.nano.ProcessRecordProto;
import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
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 static final String APP_ZYGOTE_PROCESS_NAME = "android.app.stubs_zygote";
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 mIsolatedService;
private Intent mExternalService;
private Executor mContextMainExecutor;
private HandlerThread mBackgroundThread;
private Executor mBackgroundThreadExecutor;
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;
private Thread mOnServiceConnectedThread;
public TestConnection(boolean expectDisconnect, boolean setReporter) {
mExpectDisconnect = expectDisconnect;
mSetReporter = setReporter;
mMonitor = !setReporter;
}
void setMonitor(boolean v) {
mMonitor = v;
}
public Thread getOnServiceConnectedThread() {
return mOnServiceConnectedThread;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mOnServiceConnectedThread = Thread.currentThread();
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");
}
}
}
}
final class IsolatedConnection implements ServiceConnection {
private IBinder mService;
private int mUid;
private int mPid;
private int mPpid;
private Thread mOnServiceConnectedThread;
public IsolatedConnection() {
mUid = mPid = -1;
}
public void waitForService(int timeoutMs) {
final long endTime = System.currentTimeMillis() + timeoutMs;
boolean timeout = false;
synchronized (this) {
while (mService == null) {
final long delay = endTime - System.currentTimeMillis();
if (delay < 0) {
timeout = true;
break;
}
try {
wait(delay);
} catch (final java.lang.InterruptedException e) {
// do nothing
}
}
}
if (timeout) {
throw new RuntimeException("Timed out waiting for connection");
}
}
public int getUid() {
return mUid;
}
public int getPid() {
return mPid;
}
public int getPpid() {
return mPpid;
}
public boolean zygotePreloadCalled() {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(LocalService.SERVICE_LOCAL);
try {
mService.transact(LocalService.GET_ZYGOTE_PRELOAD_CALLED, data, reply, 0);
} catch (RemoteException e) {
finishBad("DeadObjectException when sending reporting object");
}
boolean value = reply.readBoolean();
reply.recycle();
data.recycle();
return value;
}
public void setValue(int value) {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(LocalService.SERVICE_LOCAL);
data.writeInt(value);
try {
mService.transact(LocalService.SET_VALUE_CODE, data, null, 0);
} catch (RemoteException e) {
finishBad("DeadObjectException when sending reporting object");
}
data.recycle();
}
public int getValue() {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(LocalService.SERVICE_LOCAL);
try {
mService.transact(LocalService.GET_VALUE_CODE, data, reply, 0);
} catch (RemoteException e) {
finishBad("DeadObjectException when sending reporting object");
}
int value = reply.readInt();
reply.recycle();
data.recycle();
return value;
}
public int getPidIpc() {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(LocalService.SERVICE_LOCAL);
try {
mService.transact(LocalService.GET_PID_CODE, data, reply, 0);
} catch (RemoteException e) {
finishBad("DeadObjectException when sending reporting object");
}
int value = reply.readInt();
reply.recycle();
data.recycle();
return value;
}
public int getPpidIpc() {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(LocalService.SERVICE_LOCAL);
try {
mService.transact(LocalService.GET_PPID_CODE, data, reply, 0);
} catch (RemoteException e) {
finishBad("DeadObjectException when sending reporting object");
}
int value = reply.readInt();
reply.recycle();
data.recycle();
return value;
}
public int getUidIpc() {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(LocalService.SERVICE_LOCAL);
try {
mService.transact(LocalService.GET_UID_CODE, data, reply, 0);
} catch (RemoteException e) {
finishBad("DeadObjectException when sending reporting object");
}
int value = reply.readInt();
reply.recycle();
data.recycle();
return value;
}
public Thread getOnServiceConnectedThread() {
return mOnServiceConnectedThread;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (this) {
mOnServiceConnectedThread = Thread.currentThread();
mService = service;
mUid = getUidIpc();
mPid = getPidIpc();
mPpid = getPpidIpc();
notifyAll();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
synchronized (this) {
mService = null;
}
}
}
private byte[] executeShellCommand(String cmd) {
try {
ParcelFileDescriptor pfd =
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.executeShellCommand(cmd);
byte[] buf = new byte[512];
int bytesRead;
FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
ByteArrayOutputStream stdout = new ByteArrayOutputStream();
while ((bytesRead = fis.read(buf)) != -1) {
stdout.write(buf, 0, bytesRead);
}
fis.close();
return stdout.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public ActivityManagerServiceDumpProcessesProto getActivityManagerProcesses() {
byte[] dump = executeShellCommand("dumpsys activity --proto processes");
try {
return ActivityManagerServiceDumpProcessesProto.parseFrom(dump);
} catch (InvalidProtocolBufferNanoException e) {
throw new RuntimeException("Failed parsing proto", e);
}
}
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);
mIsolatedService = new Intent(mContext, IsolatedService.class);
mStateReceiver = new MockBinder();
getNotificationManager().createNotificationChannel(new NotificationChannel(
NOTIFICATION_CHANNEL_ID, "name", NotificationManager.IMPORTANCE_DEFAULT));
mContextMainExecutor = mContext.getMainExecutor();
}
private void setupBackgroundThread() {
HandlerThread thread = new HandlerThread("ServiceTestBackgroundThread");
thread.start();
Handler handler = new Handler(thread.getLooper());
mBackgroundThread = thread;
mBackgroundThreadExecutor = new Executor() {
@Override
public void execute(Runnable runnable) {
handler.post(runnable);
}
};
}
@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);
if (mBackgroundThread != null) {
mBackgroundThread.quitSafely();
}
mBackgroundThread = null;
mBackgroundThreadExecutor = null;
}
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);
}
public void testBindServiceWithExecutor() throws Exception {
setupBackgroundThread();
TestConnection conn = new TestConnection(true, false);
mExpectedServiceState = STATE_START_1;
mContext.bindService(
mLocalService, Context.BIND_AUTO_CREATE, mBackgroundThreadExecutor, conn);
waitForResultOrThrow(DELAY, EXIST_CONN_TO_RECEIVE_SERVICE);
assertEquals(mBackgroundThread, conn.getOnServiceConnectedThread());
mContext.unbindService(conn);
}
/* 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);
}
@FlakyTest
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);
}
}
/**
* Verify that we can't use bindIsolatedService() on a non-isolated service.
*/
@MediumTest
public void testFailBindNonIsolatedService() throws Exception {
EmptyConnection conn = new EmptyConnection();
try {
mContext.bindIsolatedService(mLocalService, 0, "isolated", mContextMainExecutor, conn);
mContext.unbindService(conn);
fail("Didn't get IllegalArgumentException");
} catch (IllegalArgumentException e) {
// This is expected.
}
}
/**
* Verify that bindIsolatedService() correctly makes different instances when given
* different instance names.
*/
@MediumTest
public void testBindIsolatedServiceInstances() throws Exception {
IsolatedConnection conn1a = null;
IsolatedConnection conn1b = null;
IsolatedConnection conn2 = null;
try {
conn1a = new IsolatedConnection();
mContext.bindIsolatedService(
mIsolatedService, Context.BIND_AUTO_CREATE, "1", mContextMainExecutor, conn1a);
conn1b = new IsolatedConnection();
mContext.bindIsolatedService(
mIsolatedService, Context.BIND_AUTO_CREATE, "1", mContextMainExecutor, conn1b);
conn2 = new IsolatedConnection();
mContext.bindIsolatedService(
mIsolatedService, Context.BIND_AUTO_CREATE, "2", mContextMainExecutor, conn2);
conn1a.waitForService(DELAY);
conn1b.waitForService(DELAY);
conn2.waitForService(DELAY);
if (conn1a.getPid() != conn1b.getPid()) {
fail("Connections to same service name in different pids");
}
if (conn1a.getPid() == conn2.getPid()) {
fail("Connections to different service names in same pids");
}
conn1a.setValue(1);
assertEquals(1, conn1a.getValue());
assertEquals(1, conn1b.getValue());
conn2.setValue(2);
assertEquals(1, conn1a.getValue());
assertEquals(1, conn1b.getValue());
assertEquals(2, conn2.getValue());
conn1b.setValue(3);
assertEquals(3, conn1a.getValue());
assertEquals(3, conn1b.getValue());
assertEquals(2, conn2.getValue());
} finally {
if (conn2 != null) {
mContext.unbindService(conn2);
}
if (conn1b != null) {
mContext.unbindService(conn1b);
}
if (conn1a != null) {
mContext.unbindService(conn1a);
}
}
}
public void testBindIsolatedServiceOnBackgroundThread() throws Exception {
setupBackgroundThread();
IsolatedConnection conn = new IsolatedConnection();
mContext.bindIsolatedService(mIsolatedService, Context.BIND_AUTO_CREATE,
"background_instance", mBackgroundThreadExecutor, conn);
conn.waitForService(DELAY);
assertEquals(mBackgroundThread, conn.getOnServiceConnectedThread());
mContext.unbindService(conn);
}
static final int BINDING_WEAK = 0;
static final int BINDING_STRONG = 1;
static final int BINDING_ANY = -1;
final class IsolatedConnectionInfo {
final int mStrong;
final String mInstanceName;
final String mLabel;
int mGroup;
int mImportance;
IsolatedConnection mConnection;
IsolatedConnectionInfo(int group, int importance, int strong) {
mGroup = group;
mImportance = importance;
mStrong = strong;
mInstanceName = group + "_" + importance;
StringBuilder b = new StringBuilder(mInstanceName);
b.append('_');
if (strong == BINDING_WEAK) {
b.append('W');
} else if (strong == BINDING_STRONG) {
b.append('S');
} else {
b.append(strong);
}
mLabel = b.toString();
}
void setGroup(int group) {
mGroup = group;
}
void setImportance(int importance) {
mImportance = importance;
}
boolean match(int group, int strong) {
return (group < 0 || mGroup == group)
&& (strong == BINDING_ANY || mStrong == strong);
}
boolean bind(Context context) {
if (mConnection != null) {
return true;
}
Log.i("XXXXXXX", "Binding " + mLabel + ": conn=" + mConnection
+ " context=" + context);
mConnection = new IsolatedConnection();
boolean result = context.bindIsolatedService(
mIsolatedService,
Context.BIND_AUTO_CREATE | Context.BIND_DEBUG_UNBIND
| (mStrong == BINDING_STRONG ? 0 : Context.BIND_ALLOW_OOM_MANAGEMENT),
mInstanceName, mContextMainExecutor, mConnection);
if (!result) {
mConnection = null;
}
return result;
}
IsolatedConnection getConnection() {
return mConnection;
}
void unbind(Context context) {
if (mConnection != null) {
Log.i("XXXXXXX", "Unbinding " + mLabel + ": conn=" + mConnection
+ " context=" + context);
context.unbindService(mConnection);
mConnection = null;
}
}
}
final class LruOrderItem {
static final int FLAG_SKIP_UNKNOWN = 1<<0;
final IsolatedConnectionInfo mInfo;
final int mUid;
final int mFlags;
LruOrderItem(IsolatedConnectionInfo info, int flags) {
mInfo = info;
mUid = -1;
mFlags = flags;
}
LruOrderItem(int uid, int flags) {
mInfo = null;
mUid = uid;
mFlags = flags;
}
IsolatedConnectionInfo getInfo() {
return mInfo;
}
int getUid() {
return mInfo != null ? mInfo.getConnection().getUid() : mUid;
}
int getFlags() {
return mFlags;
}
}
private void doBind(Context context, IsolatedConnectionInfo[] connections, int group,
int strong) {
for (IsolatedConnectionInfo ci : connections) {
if (ci.match(group, strong)) {
ci.bind(context);
}
}
}
private void doBind(Context context, IsolatedConnectionInfo[] connections, int[] selected) {
for (int i : selected) {
boolean result = connections[i].bind(context);
if (!result) {
fail("Unable to bind connection " + connections[i].mLabel);
}
}
}
private void doWaitForService(IsolatedConnectionInfo[] connections, int group,
int strong) {
for (IsolatedConnectionInfo ci : connections) {
if (ci.match(group, strong)) {
ci.mConnection.waitForService(DELAY);
}
}
}
private void doUpdateServiceGroup(Context context, IsolatedConnectionInfo[] connections,
int group, int strong) {
for (IsolatedConnectionInfo ci : connections) {
if (ci.match(group, strong)) {
context.updateServiceGroup(ci.mConnection, ci.mGroup, ci.mImportance);
}
}
}
private void doUnbind(Context context, IsolatedConnectionInfo[] connections, int group,
int strong) {
for (IsolatedConnectionInfo ci : connections) {
if (ci.match(group, strong)) {
ci.unbind(context);
}
}
}
private void doUnbind(Context context, IsolatedConnectionInfo[] connections, int[] selected) {
for (int i : selected) {
connections[i].unbind(context);
}
}
List<ProcessRecordProto> getLruProcesses() {
ActivityManagerServiceDumpProcessesProto dump = getActivityManagerProcesses();
SparseArray<ProcessRecordProto> procs = new SparseArray<>();
ProcessRecordProto[] procsList = dump.procs;
for (ProcessRecordProto proc : procsList) {
procs.put(proc.lruIndex, proc);
}
ArrayList<ProcessRecordProto> lruProcs = new ArrayList<>();
for (int i = 0; i < procs.size(); i++) {
lruProcs.add(procs.valueAt(i));
}
return lruProcs;
}
String printProc(int i, ProcessRecordProto proc) {
return "#" + i + ": " + proc.processName
+ " pid=" + proc.pid + " uid=" + proc.uid
+ (proc.isolatedAppId != 0 ? " isolated=" + proc.isolatedAppId : "");
}
private void logProc(int i, ProcessRecordProto proc) {
Log.i("XXXXXXXX", printProc(i, proc));
}
private void verifyLruOrder(LruOrderItem[] orderItems) {
List<ProcessRecordProto> procs = getLruProcesses();
Log.i("XXXXXXXX", "Processes:");
int orderI = 0;
for (int i = procs.size() - 1; i >= 0; i--) {
ProcessRecordProto proc = procs.get(i);
logProc(i, proc);
final LruOrderItem lru = orderItems[orderI];
Log.i("XXXXXXXX", "Expecting uid: " + lru.getUid());
int procUid = proc.isolatedAppId != 0 ? proc.isolatedAppId : proc.uid;
if (procUid != lru.getUid()) {
if ((lru.getFlags() & LruOrderItem.FLAG_SKIP_UNKNOWN) != 0) {
while (i > 0) {
i--;
proc = procs.get(i);
logProc(i, proc);
procUid = proc.isolatedAppId != 0 ? proc.isolatedAppId : proc.uid;
if (procUid == lru.getUid()) {
break;
}
}
}
if (procUid != lru.getUid()) {
if ((lru.getFlags() & LruOrderItem.FLAG_SKIP_UNKNOWN) != 0) {
fail("Didn't find expected LRU proc uid=" + lru.getUid());
}
fail("Expected proc uid=" + lru.getUid() + " at found proc "
+ printProc(i, proc));
}
}
orderI++;
if (orderI >= orderItems.length) {
return;
}
}
}
@MediumTest
public void testAppZygotePreload() throws Exception {
IsolatedConnection conn = new IsolatedConnection();
try {
mContext.bindIsolatedService(
mIsolatedService, Context.BIND_AUTO_CREATE, "1", mContextMainExecutor, conn);
conn.waitForService(DELAY);
// Verify application preload was done
assertTrue(conn.zygotePreloadCalled());
} finally {
if (conn != null) {
mContext.unbindService(conn);
}
}
}
@MediumTest
public void testAppZygoteServices() throws Exception {
IsolatedConnection conn1a = null;
IsolatedConnection conn1b = null;
IsolatedConnection conn2 = null;
int appZygotePid;
try {
conn1a = new IsolatedConnection();
mContext.bindIsolatedService(
mIsolatedService, Context.BIND_AUTO_CREATE, "1", mContextMainExecutor, conn1a);
conn1b = new IsolatedConnection();
mContext.bindIsolatedService(
mIsolatedService, Context.BIND_AUTO_CREATE, "1", mContextMainExecutor, conn1b);
conn2 = new IsolatedConnection();
mContext.bindIsolatedService(
mIsolatedService, Context.BIND_AUTO_CREATE, "2", mContextMainExecutor, conn2);
conn1a.waitForService(DELAY);
conn1b.waitForService(DELAY);
conn2.waitForService(DELAY);
// Get PPID of each service, and verify they're identical
int ppid1a = conn1a.getPpid();
int ppid1b = conn1b.getPpid();
int ppid2 = conn2.getPpid();
assertEquals(ppid1a, ppid1b);
assertEquals(ppid1b, ppid2);
// Find the app zygote process hosting these
String result = SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
"ps -p " + Integer.toString(ppid1a) + " -o NAME=");
result = result.replaceAll("\\s+", "");
assertEquals(result, APP_ZYGOTE_PROCESS_NAME);
appZygotePid = ppid1a;
} finally {
if (conn2 != null) {
mContext.unbindService(conn2);
}
if (conn1b != null) {
mContext.unbindService(conn1b);
}
if (conn1a != null) {
mContext.unbindService(conn1a);
}
}
// Sleep for 2 seconds and bind a service again, see it uses the same Zygote
try {
conn1a = new IsolatedConnection();
mContext.bindIsolatedService(
mIsolatedService, Context.BIND_AUTO_CREATE, "1", mContextMainExecutor, conn1a);
conn1a.waitForService(DELAY);
int ppid1a = conn1a.getPpid();
assertEquals(appZygotePid, ppid1a);
} finally {
if (conn1a != null) {
mContext.unbindService(conn1a);
}
}
// Sleep for 10 seconds, verify the app_zygote is gone
Thread.sleep(10000);
String result = SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
"ps -p " + Integer.toString(appZygotePid) + " -o NAME=");
result = result.replaceAll("\\s+", "");
assertEquals("", result);
}
/**
* Test that the system properly orders processes bound by an activity within the
* LRU list.
*/
@MediumTest
public void testActivityServiceBindingLru() throws Exception {
// Bring up the activity we will hang services off of.
runLaunchpad(LaunchpadActivity.ACTIVITY_PREPARE);
final Activity a = getRunningActivity();
final int CONN_1_1_W = 0;
final int CONN_1_1_S = 1;
final int CONN_1_2_W = 2;
final int CONN_1_2_S = 3;
final int CONN_2_1_W = 4;
final int CONN_2_1_S = 5;
final int CONN_2_2_W = 6;
final int CONN_2_2_S = 7;
final int CONN_2_3_W = 8;
final int CONN_2_3_S = 9;
// We are going to have both weak and strong references to services, so we can allow
// some to go down in the LRU list.
final IsolatedConnectionInfo[] connections = new IsolatedConnectionInfo[] {
new IsolatedConnectionInfo(1, 1, BINDING_WEAK),
new IsolatedConnectionInfo(1, 1, BINDING_STRONG),
new IsolatedConnectionInfo(1, 2, BINDING_WEAK),
new IsolatedConnectionInfo(1, 2, BINDING_STRONG),
new IsolatedConnectionInfo(2, 1, BINDING_WEAK),
new IsolatedConnectionInfo(2, 1, BINDING_STRONG),
new IsolatedConnectionInfo(2, 2, BINDING_WEAK),
new IsolatedConnectionInfo(2, 2, BINDING_STRONG),
new IsolatedConnectionInfo(2, 3, BINDING_WEAK),
new IsolatedConnectionInfo(2, 3, BINDING_STRONG),
};
final int[] REV_GROUP_1_STRONG = new int[] {
CONN_1_2_S, CONN_1_1_S
};
final int[] REV_GROUP_2_STRONG = new int[] {
CONN_2_3_S, CONN_2_2_S, CONN_2_1_S
};
final int[] MIXED_GROUP_3_STRONG = new int[] {
CONN_2_3_S, CONN_1_1_S, CONN_2_1_S, CONN_2_2_S
};
boolean passed = false;
try {
// Start the group 1 processes as weak.
doBind(a, connections, 1, BINDING_WEAK);
doUpdateServiceGroup(a, connections, 1, BINDING_WEAK);
// Wait for them to come up.
doWaitForService(connections, 1, BINDING_WEAK);
// Now fully bind to the services.
doBind(a, connections, 1, BINDING_STRONG);
doWaitForService(connections, 1, BINDING_STRONG);
verifyLruOrder(new LruOrderItem[] {
new LruOrderItem(Process.myUid(), 0),
new LruOrderItem(connections[CONN_1_1_W], 0),
new LruOrderItem(connections[CONN_1_2_W], 0),
});
// Now remove the full binding, leaving only the weak.
doUnbind(a, connections, 1, BINDING_STRONG);
// Start the group 2 processes as weak.
doBind(a, connections, 2, BINDING_WEAK);
// Wait for them to come up.
doWaitForService(connections, 2, BINDING_WEAK);
// Set the group and index. In this case we do it after we know the process
// is started, to make sure setting it directly works.
doUpdateServiceGroup(a, connections, 2, BINDING_WEAK);
// Now fully bind to group 2
doBind(a, connections, REV_GROUP_2_STRONG);
verifyLruOrder(new LruOrderItem[] {
new LruOrderItem(Process.myUid(), 0),
new LruOrderItem(connections[CONN_2_1_W], 0),
new LruOrderItem(connections[CONN_2_2_W], 0),
new LruOrderItem(connections[CONN_2_3_W], 0),
new LruOrderItem(connections[CONN_1_1_W], LruOrderItem.FLAG_SKIP_UNKNOWN),
new LruOrderItem(connections[CONN_1_2_W], 0),
});
// Bring group 1 back to the foreground, but in the opposite order.
doBind(a, connections, REV_GROUP_1_STRONG);
verifyLruOrder(new LruOrderItem[] {
new LruOrderItem(Process.myUid(), 0),
new LruOrderItem(connections[CONN_1_1_W], 0),
new LruOrderItem(connections[CONN_1_2_W], 0),
new LruOrderItem(connections[CONN_2_1_W], LruOrderItem.FLAG_SKIP_UNKNOWN),
new LruOrderItem(connections[CONN_2_2_W], 0),
new LruOrderItem(connections[CONN_2_3_W], 0),
});
// Now remove all full bindings, keeping only weak.
doUnbind(a, connections, 1, BINDING_STRONG);
doUnbind(a, connections, 2, BINDING_STRONG);
// Change the grouping and importance to make sure that gets reflected.
connections[CONN_1_1_W].setGroup(3);
connections[CONN_1_1_W].setImportance(1);
connections[CONN_2_1_W].setGroup(3);
connections[CONN_2_1_W].setImportance(2);
connections[CONN_2_2_W].setGroup(3);
connections[CONN_2_2_W].setImportance(3);
connections[CONN_2_3_W].setGroup(3);
connections[CONN_2_3_W].setImportance(4);
doUpdateServiceGroup(a, connections, 3, BINDING_WEAK);
// Now bind them back up in an interesting order.
doBind(a, connections, MIXED_GROUP_3_STRONG);
verifyLruOrder(new LruOrderItem[] {
new LruOrderItem(Process.myUid(), 0),
new LruOrderItem(connections[CONN_1_1_W], 0),
new LruOrderItem(connections[CONN_2_1_W], 0),
new LruOrderItem(connections[CONN_2_2_W], 0),
new LruOrderItem(connections[CONN_2_3_W], 0),
new LruOrderItem(connections[CONN_1_2_W], LruOrderItem.FLAG_SKIP_UNKNOWN),
});
passed = true;
} finally {
if (!passed) {
List<ProcessRecordProto> procs = getLruProcesses();
Log.i("XXXXXXXX", "Processes:");
for (int i = procs.size() - 1; i >= 0; i--) {
ProcessRecordProto proc = procs.get(i);
logProc(i, proc);
}
}
doUnbind(a, connections, -1, BINDING_ANY);
}
}
}