Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions services/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

**Bug Fixes**

* Make ShellExecutor more resilient to low memory conditions by binding to
androidx.test.services.SpeakEasyService

**New Features**

**Breaking Changes**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* Client for the ShellCommandExecutor service, to allow an instrumentation to executes a shell
Expand All @@ -44,6 +45,7 @@
final class ShellCommandClient {

private static final String TAG = "ShellCommandClient";
private static final ConcurrentHashMap<String, Command> stubCache = new ConcurrentHashMap<>();

private ShellCommandClient() {
// Should not be initialized
Expand Down Expand Up @@ -88,25 +90,28 @@ public static synchronized InputStream execOnServer(
shellEnv = new HashMap<>();
}

FindResult result;

try {
result = BlockingFind.getResult(Looper.getMainLooper(), context, secret);
if (!result.found) {
Log.e(TAG, "Couldn't find a published binder");
Command commandStub = stubCache.get(secret);
if (commandStub == null || !commandStub.asBinder().isBinderAlive()) {
FindResult result;
try {
result = BlockingFind.getResult(Looper.getMainLooper(), context, secret);
if (!result.found) {
Log.e(TAG, "Couldn't find a published binder");
throw new ClientNotConnected(
"Couldn't find a binder published by androidx.test.services. It is very likely that"
+ " androidx.test.services was killed, which is usually due to low memory"
+ " conditions. Consider switching to a device with more memory.");
}
} catch (InterruptedException e) {
throw new ClientNotConnected(
"Couldn't find a binder published by androidx.test.services. It is very likely that"
+ " androidx.test.services was killed, which is usually due to low memory"
+ " conditions. Consider switching to a device with more memory.");
"Search for an androidx.test.services binder was interrupted", e);
}
} catch (InterruptedException e) {
throw new ClientNotConnected(
"Search for an androidx.test.services binder was interrupted", e);
commandStub = Command.Stub.asInterface(result.binder);
stubCache.put(secret, commandStub);
}

ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();

Command commandStub = Command.Stub.asInterface(result.binder);
// Only use timeout version if timeout is greater than 0
if (timeoutMs > 0L) {
// NOTICE: this is not be supported on older versions of the Command server.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,20 @@

package androidx.test.services.speakeasy.client;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Build;
import android.os.IBinder;
import android.util.Log;
import androidx.test.services.speakeasy.SpeakEasyProtocol;
import java.security.SecureRandom;
import java.util.Random;

/** Allows callers to access the speakeasy binder registry when they have a Context. */
public final class AppConnection implements Connection {
private static final String TAG = "AppConnection";
private static final String PACKAGE_NAME = "androidx.test.services";
static final String SERVICE =
"androidx.test.services.speakeasy.server.SpeakEasyService";
Expand All @@ -45,11 +49,36 @@ public AppConnection(Context context) {
this.random = checkNotNull(random);
}

private static final ServiceConnection serviceConnection =
new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {}

@Override
public void onServiceDisconnected(ComponentName name) {}
};
private static boolean isBound = false;

private synchronized void ensureBound() {
if (!isBound) {
try {
isBound =
context.bindService(
makeIntent(),
serviceConnection,
Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT | Context.BIND_ABOVE_CLIENT);
} catch (SecurityException | IllegalArgumentException e) {
Log.w(TAG, "Failed to bind to SpeakEasyService", e);
}
}
}

@Override
public void publish(IBinder binder, PublishResultReceiver rr) {
checkNotNull(binder);
checkNotNull(rr);

ensureBound();
String key = Long.toHexString(random.nextLong());
Intent intent = makeIntent();
intent.putExtras(SpeakEasyProtocol.Publish.asBundle(key, binder, rr));
Expand All @@ -60,6 +89,8 @@ public void publish(IBinder binder, PublishResultReceiver rr) {
public void find(String key, FindResultReceiver rr) {
checkNotNull(key);
checkNotNull(rr);

ensureBound();
Intent intent = makeIntent();
intent.putExtras(SpeakEasyProtocol.Find.asBundle(key, rr));
startForegroundService(context, intent);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
Expand Down Expand Up @@ -72,7 +73,7 @@ public void onStart(Intent intent, int id) {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
onStart(intent, startId);
return START_NOT_STICKY;
return START_STICKY;
}

@Override
Expand Down Expand Up @@ -108,6 +109,9 @@ public void run() {
}

private void serveIntent(Intent in, int startId) {
if (in == null || in.getExtras() == null) {
return;
}
SpeakEasyProtocol sep = SpeakEasyProtocol.fromBundle(in.getExtras());
if (null == sep) {
return;
Expand Down Expand Up @@ -148,9 +152,14 @@ private void serveIntent(Intent in, int startId) {
}
}

private final IBinder binder = new Binder();

@Override
public IBinder onBind(Intent i) {
return null;
if (i != null && i.getExtras() != null) {
serveIntent(i, -1);
}
return binder;
}

private static class DeathCallback implements SpeakEasy.BinderDeathCallback {
Expand Down
Loading