blob: 679ad04ccb3257e99bd96314730a43bd2772d358 [file] [log] [blame]
package org.robolectric.shadows;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
import android.content.ComponentName;
import android.net.MacAddress;
import android.os.Build.VERSION_CODES;
import android.os.Handler;
import androidx.annotation.Nullable;
import com.google.auto.value.AutoValue;
import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
/** Shadow for CompanionDeviceManager. */
@Implements(value = CompanionDeviceManager.class, minSdk = VERSION_CODES.O)
public class ShadowCompanionDeviceManager {
protected final Set<RoboAssociationInfo> associations = new HashSet<>();
protected final Set<ComponentName> hasNotificationAccess = new HashSet<>();
protected ComponentName lastRequestedNotificationAccess;
protected AssociationRequest lastAssociationRequest;
protected CompanionDeviceManager.Callback lastAssociationCallback;
@Implementation
@SuppressWarnings("JdkCollectors") // toImmutableList is only supported in Java 8+.
protected List<String> getAssociations() {
return ImmutableList.copyOf(
associations.stream().map(RoboAssociationInfo::deviceMacAddress).collect(toList()));
}
public void addAssociation(String newAssociation) {
associations.add(RoboAssociationInfo.builder().setDeviceMacAddress(newAssociation).build());
}
public void addAssociation(AssociationInfo info) {
associations.add(createShadowAssociationInfo(info));
}
@Implementation
protected void disassociate(String deviceMacAddress) {
RoboAssociationInfo associationInfo =
associations.stream()
.filter(
association ->
Ascii.equalsIgnoreCase(deviceMacAddress, association.deviceMacAddress()))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Association does not exist"));
associations.remove(associationInfo);
}
@Implementation(minSdk = VERSION_CODES.TIRAMISU)
protected void disassociate(int associationId) {
RoboAssociationInfo associationInfo =
associations.stream()
.filter(association -> associationId == association.id())
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Association does not exist"));
associations.remove(associationInfo);
}
@Implementation
protected boolean hasNotificationAccess(ComponentName component) {
checkHasAssociation();
return hasNotificationAccess.contains(component);
}
public void setNotificationAccess(ComponentName component, boolean hasAccess) {
if (hasAccess) {
hasNotificationAccess.add(component);
} else {
hasNotificationAccess.remove(component);
}
}
@Implementation
protected void requestNotificationAccess(ComponentName component) {
checkHasAssociation();
lastRequestedNotificationAccess = component;
}
@Implementation
protected void associate(
AssociationRequest request, CompanionDeviceManager.Callback callback, Handler handler) {
lastAssociationRequest = request;
lastAssociationCallback = callback;
}
@Implementation(minSdk = VERSION_CODES.TIRAMISU)
protected void associate(
AssociationRequest request, Executor executor, CompanionDeviceManager.Callback callback) {
associate(request, callback, /* handler= */ null);
}
public AssociationRequest getLastAssociationRequest() {
return lastAssociationRequest;
}
public CompanionDeviceManager.Callback getLastAssociationCallback() {
return lastAssociationCallback;
}
public ComponentName getLastRequestedNotificationAccess() {
return lastRequestedNotificationAccess;
}
private void checkHasAssociation() {
if (associations.isEmpty()) {
throw new IllegalStateException("App must have an association before calling this API");
}
}
@Implementation(minSdk = VERSION_CODES.TIRAMISU)
protected List<AssociationInfo> getMyAssociations() {
return this.associations.stream()
.map(this::createAssociationInfo)
.collect(toCollection(ArrayList::new));
}
/** Convert {@link RoboAssociationInfo} to actual {@link AssociationInfo}. */
private AssociationInfo createAssociationInfo(RoboAssociationInfo info) {
return new AssociationInfo(
info.id(),
info.userId(),
info.packageName(),
MacAddress.fromString(info.deviceMacAddress()),
info.displayName(),
info.deviceProfile(),
info.selfManaged(),
info.notifyOnDeviceNearby(),
info.revoked(),
info.timeApprovedMs(),
info.lastTimeConnectedMs());
}
private RoboAssociationInfo createShadowAssociationInfo(AssociationInfo info) {
return RoboAssociationInfo.create(
info.getId(),
info.getUserId(),
info.getPackageName(),
info.getDeviceMacAddress().toString(),
info.getDisplayName(),
info.getDeviceProfile(),
info.isSelfManaged(),
info.isNotifyOnDeviceNearby(),
info.isRevoked(),
info.getTimeApprovedMs(),
info.getLastTimeConnectedMs());
}
/**
* This is a copy of frameworks/base/core/java/android/companion/AssociationInfo.java to store
* full AssociationInfo data without breaking existing Android test dependencies.
*/
@AutoValue
abstract static class RoboAssociationInfo {
public abstract int id();
public abstract int userId();
@Nullable
public abstract String packageName();
@Nullable
public abstract String deviceMacAddress();
@Nullable
public abstract CharSequence displayName();
@Nullable
public abstract String deviceProfile();
public abstract boolean selfManaged();
public abstract boolean notifyOnDeviceNearby();
public abstract boolean revoked();
public abstract long timeApprovedMs();
public abstract long lastTimeConnectedMs();
public static Builder builder() {
return new AutoValue_ShadowCompanionDeviceManager_RoboAssociationInfo.Builder()
.setId(1)
.setUserId(1)
.setSelfManaged(false)
.setNotifyOnDeviceNearby(false)
.setTimeApprovedMs(0)
.setLastTimeConnectedMs(0);
}
public static RoboAssociationInfo create(
int id,
int userId,
String packageName,
String deviceMacAddress,
CharSequence displayName,
String deviceProfile,
boolean selfManaged,
boolean notifyOnDeviceNearby,
boolean revoked,
long timeApprovedMs,
long lastTimeConnectedMs) {
return RoboAssociationInfo.builder()
.setId(id)
.setUserId(userId)
.setPackageName(packageName)
.setDeviceMacAddress(deviceMacAddress)
.setDisplayName(displayName)
.setSelfManaged(selfManaged)
.setNotifyOnDeviceNearby(notifyOnDeviceNearby)
.setTimeApprovedMs(timeApprovedMs)
.setRevoked(revoked)
.setLastTimeConnectedMs(lastTimeConnectedMs)
.build();
}
/** Builder for {@link AssociationInfo}. */
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setId(int id);
public abstract Builder setUserId(int userId);
public abstract Builder setPackageName(String packageName);
public abstract Builder setDeviceMacAddress(String deviceMacAddress);
public abstract Builder setDisplayName(CharSequence displayName);
public abstract Builder setDeviceProfile(String deviceProfile);
public abstract Builder setSelfManaged(boolean selfManaged);
public abstract Builder setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby);
public abstract Builder setRevoked(boolean revoked);
public abstract Builder setTimeApprovedMs(long timeApprovedMs);
public abstract Builder setLastTimeConnectedMs(long lastTimeConnectedMs);
public abstract RoboAssociationInfo build();
}
}
}