summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Evan Chen <evanxinchen@google.com> 2024-07-31 16:59:48 +0000
committer Evan Chen <evanxinchen@google.com> 2024-10-08 19:46:23 +0000
commit6f5a0ad1921813d5df1b17795b738cb556be2f12 (patch)
treef0595915e500530c318f103129debb08fc454d24
parentbeb79048744267afcb24be8a26017e2d9a74d24a (diff)
[W]SelfManaged Association device icon
Introduce custom self managed associaiton device icon API that allow a system app to set the device icon that displayed in the self managed association dialog and this device icon will be stored in AssociationInfo Test: CTS Bug: 359341630 Flag: android.companion.association_device_icon Change-Id: Ifa8f3f4016ff748a827e500824c86b95af12b1df
-rw-r--r--core/api/current.txt3
-rw-r--r--core/api/test-current.txt1
-rw-r--r--core/java/android/companion/AssociationInfo.java56
-rw-r--r--core/java/android/companion/AssociationRequest.java64
-rw-r--r--core/java/android/companion/CompanionDeviceManager.java44
-rw-r--r--core/java/android/companion/flags.aconfig8
-rw-r--r--packages/CompanionDeviceManager/res/layout/activity_confirmation.xml6
-rw-r--r--packages/CompanionDeviceManager/res/values/styles.xml6
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java11
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java2
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java4
-rw-r--r--services/companion/java/com/android/server/companion/association/AssociationDiskStore.java33
-rw-r--r--services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java9
-rw-r--r--services/companion/java/com/android/server/companion/utils/PermissionsUtils.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java3
15 files changed, 233 insertions, 19 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index 56852212ab1e..9d0f92a488b1 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9769,6 +9769,7 @@ package android.companion {
public final class AssociationInfo implements android.os.Parcelable {
method public int describeContents();
method @Nullable public android.companion.AssociatedDevice getAssociatedDevice();
+ method @FlaggedApi("android.companion.association_device_icon") @Nullable public android.graphics.drawable.Icon getDeviceIcon();
method @Nullable public android.net.MacAddress getDeviceMacAddress();
method @Nullable public String getDeviceProfile();
method @Nullable public CharSequence getDisplayName();
@@ -9782,6 +9783,7 @@ package android.companion {
public final class AssociationRequest implements android.os.Parcelable {
method public int describeContents();
+ method @FlaggedApi("android.companion.association_device_icon") @Nullable public android.graphics.drawable.Icon getDeviceIcon();
method @Nullable public String getDeviceProfile();
method @Nullable public CharSequence getDisplayName();
method public boolean isForceConfirmation();
@@ -9801,6 +9803,7 @@ package android.companion {
ctor public AssociationRequest.Builder();
method @NonNull public android.companion.AssociationRequest.Builder addDeviceFilter(@Nullable android.companion.DeviceFilter<?>);
method @NonNull public android.companion.AssociationRequest build();
+ method @FlaggedApi("android.companion.association_device_icon") @NonNull @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public android.companion.AssociationRequest.Builder setDeviceIcon(@NonNull android.graphics.drawable.Icon);
method @NonNull public android.companion.AssociationRequest.Builder setDeviceProfile(@NonNull String);
method @NonNull public android.companion.AssociationRequest.Builder setDisplayName(@NonNull CharSequence);
method @NonNull @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public android.companion.AssociationRequest.Builder setForceConfirmation(boolean);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 4fc70769a3b1..aa6ed36ac6a5 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -912,6 +912,7 @@ package android.companion {
ctor public AssociationInfo.Builder(@NonNull android.companion.AssociationInfo);
method @NonNull public android.companion.AssociationInfo build();
method @NonNull public android.companion.AssociationInfo.Builder setAssociatedDevice(@Nullable android.companion.AssociatedDevice);
+ method @FlaggedApi("android.companion.association_device_icon") @NonNull public android.companion.AssociationInfo.Builder setDeviceIcon(@Nullable android.graphics.drawable.Icon);
method @NonNull public android.companion.AssociationInfo.Builder setDeviceMacAddress(@Nullable android.net.MacAddress);
method @NonNull public android.companion.AssociationInfo.Builder setDeviceProfile(@Nullable String);
method @NonNull public android.companion.AssociationInfo.Builder setDisplayName(@Nullable CharSequence);
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index b4b96e2c69d6..7f30d7cccb57 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -22,6 +22,7 @@ import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UserIdInt;
+import android.graphics.drawable.Icon;
import android.net.MacAddress;
import android.os.Parcel;
import android.os.Parcelable;
@@ -86,6 +87,11 @@ public final class AssociationInfo implements Parcelable {
private final int mSystemDataSyncFlags;
/**
+ * A device icon displayed on a selfManaged association dialog.
+ */
+ private final Icon mDeviceIcon;
+
+ /**
* Creates a new Association.
*
* @hide
@@ -95,7 +101,7 @@ public final class AssociationInfo implements Parcelable {
@Nullable CharSequence displayName, @Nullable String deviceProfile,
@Nullable AssociatedDevice associatedDevice, boolean selfManaged,
boolean notifyOnDeviceNearby, boolean revoked, boolean pending, long timeApprovedMs,
- long lastTimeConnectedMs, int systemDataSyncFlags) {
+ long lastTimeConnectedMs, int systemDataSyncFlags, @Nullable Icon deviceIcon) {
if (id <= 0) {
throw new IllegalArgumentException("Association ID should be greater than 0");
}
@@ -119,6 +125,7 @@ public final class AssociationInfo implements Parcelable {
mTimeApprovedMs = timeApprovedMs;
mLastTimeConnectedMs = lastTimeConnectedMs;
mSystemDataSyncFlags = systemDataSyncFlags;
+ mDeviceIcon = deviceIcon;
}
/**
@@ -278,6 +285,20 @@ public final class AssociationInfo implements Parcelable {
}
/**
+ * Get the device icon of the associated device. The device icon represents the device type.
+ *
+ * @return the device icon, or {@code null} if no device icon is has been set for the
+ * associated device.
+ *
+ * @see AssociationRequest.Builder#setDeviceIcon(Icon)
+ */
+ @FlaggedApi(Flags.FLAG_ASSOCIATION_DEVICE_ICON)
+ @Nullable
+ public Icon getDeviceIcon() {
+ return mDeviceIcon;
+ }
+
+ /**
* Utility method for checking if the association represents a device with the given MAC
* address.
*
@@ -370,14 +391,16 @@ public final class AssociationInfo implements Parcelable {
&& Objects.equals(mDisplayName, that.mDisplayName)
&& Objects.equals(mDeviceProfile, that.mDeviceProfile)
&& Objects.equals(mAssociatedDevice, that.mAssociatedDevice)
- && mSystemDataSyncFlags == that.mSystemDataSyncFlags;
+ && mSystemDataSyncFlags == that.mSystemDataSyncFlags
+ && (mDeviceIcon == null ? that.mDeviceIcon == null
+ : mDeviceIcon.sameAs(that.mDeviceIcon));
}
@Override
public int hashCode() {
return Objects.hash(mId, mUserId, mPackageName, mTag, mDeviceMacAddress, mDisplayName,
mDeviceProfile, mAssociatedDevice, mSelfManaged, mNotifyOnDeviceNearby, mRevoked,
- mPending, mTimeApprovedMs, mLastTimeConnectedMs, mSystemDataSyncFlags);
+ mPending, mTimeApprovedMs, mLastTimeConnectedMs, mSystemDataSyncFlags, mDeviceIcon);
}
@Override
@@ -402,6 +425,12 @@ public final class AssociationInfo implements Parcelable {
dest.writeLong(mTimeApprovedMs);
dest.writeLong(mLastTimeConnectedMs);
dest.writeInt(mSystemDataSyncFlags);
+ if (mDeviceIcon != null) {
+ dest.writeInt(1);
+ mDeviceIcon.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
}
private AssociationInfo(@NonNull Parcel in) {
@@ -420,6 +449,11 @@ public final class AssociationInfo implements Parcelable {
mTimeApprovedMs = in.readLong();
mLastTimeConnectedMs = in.readLong();
mSystemDataSyncFlags = in.readInt();
+ if (in.readInt() == 1) {
+ mDeviceIcon = Icon.CREATOR.createFromParcel(in);
+ } else {
+ mDeviceIcon = null;
+ }
}
@NonNull
@@ -459,6 +493,7 @@ public final class AssociationInfo implements Parcelable {
private long mTimeApprovedMs;
private long mLastTimeConnectedMs;
private int mSystemDataSyncFlags;
+ private Icon mDeviceIcon;
/** @hide */
@TestApi
@@ -486,6 +521,7 @@ public final class AssociationInfo implements Parcelable {
mTimeApprovedMs = info.mTimeApprovedMs;
mLastTimeConnectedMs = info.mLastTimeConnectedMs;
mSystemDataSyncFlags = info.mSystemDataSyncFlags;
+ mDeviceIcon = info.mDeviceIcon;
}
/**
@@ -510,6 +546,7 @@ public final class AssociationInfo implements Parcelable {
mTimeApprovedMs = info.mTimeApprovedMs;
mLastTimeConnectedMs = info.mLastTimeConnectedMs;
mSystemDataSyncFlags = info.mSystemDataSyncFlags;
+ mDeviceIcon = info.mDeviceIcon;
}
/** @hide */
@@ -625,6 +662,16 @@ public final class AssociationInfo implements Parcelable {
/** @hide */
@TestApi
@NonNull
+ @SuppressLint("MissingGetterMatchingBuilder")
+ @FlaggedApi(Flags.FLAG_ASSOCIATION_DEVICE_ICON)
+ public Builder setDeviceIcon(@Nullable Icon deviceIcon) {
+ mDeviceIcon = deviceIcon;
+ return this;
+ }
+
+ /** @hide */
+ @TestApi
+ @NonNull
public AssociationInfo build() {
if (mId <= 0) {
throw new IllegalArgumentException("Association ID should be greater than 0");
@@ -648,7 +695,8 @@ public final class AssociationInfo implements Parcelable {
mPending,
mTimeApprovedMs,
mLastTimeConnectedMs,
- mSystemDataSyncFlags
+ mSystemDataSyncFlags,
+ mDeviceIcon
);
}
}
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index 2e969f83f836..41a6791d8a7b 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -23,12 +23,14 @@ import static com.android.internal.util.CollectionUtils.emptyIfNull;
import static java.util.Objects.requireNonNull;
import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.StringDef;
import android.annotation.UserIdInt;
import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.drawable.Icon;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -234,6 +236,13 @@ public final class AssociationRequest implements Parcelable {
private boolean mSkipPrompt;
/**
+ * The device icon displayed in selfManaged association dialog.
+ * @hide
+ */
+ @Nullable
+ private Icon mDeviceIcon;
+
+ /**
* Creates a new AssociationRequest.
*
* @param singleDevice
@@ -258,15 +267,16 @@ public final class AssociationRequest implements Parcelable {
@Nullable @DeviceProfile String deviceProfile,
@Nullable CharSequence displayName,
boolean selfManaged,
- boolean forceConfirmation) {
+ boolean forceConfirmation,
+ @Nullable Icon deviceIcon) {
mSingleDevice = singleDevice;
mDeviceFilters = requireNonNull(deviceFilters);
mDeviceProfile = deviceProfile;
mDisplayName = displayName;
mSelfManaged = selfManaged;
mForceConfirmation = forceConfirmation;
-
mCreationTime = System.currentTimeMillis();
+ mDeviceIcon = deviceIcon;
}
/**
@@ -318,6 +328,19 @@ public final class AssociationRequest implements Parcelable {
return mSingleDevice;
}
+ /**
+ * Get the device icon of the self-managed association request.
+ *
+ * @return the device icon, or {@code null} if no device icon has been set.
+ *
+ * @see Builder#setDeviceIcon(Icon)
+ */
+ @FlaggedApi(Flags.FLAG_ASSOCIATION_DEVICE_ICON)
+ @Nullable
+ public Icon getDeviceIcon() {
+ return mDeviceIcon;
+ }
+
/** @hide */
public void setPackageName(@NonNull String packageName) {
mPackageName = packageName;
@@ -365,6 +388,7 @@ public final class AssociationRequest implements Parcelable {
private CharSequence mDisplayName;
private boolean mSelfManaged = false;
private boolean mForceConfirmation = false;
+ private Icon mDeviceIcon = null;
public Builder() {}
@@ -450,6 +474,23 @@ public final class AssociationRequest implements Parcelable {
return this;
}
+ /**
+ * Set the device icon for the self-managed device and this icon will be
+ * displayed in the self-managed association dialog.
+ *
+ * @throws IllegalArgumentException if the icon is not exactly 24dp by 24dp
+ * or if it is {@link Icon#TYPE_URI} or {@link Icon#TYPE_URI_ADAPTIVE_BITMAP}.
+ * @see #setSelfManaged(boolean)
+ */
+ @NonNull
+ @RequiresPermission(REQUEST_COMPANION_SELF_MANAGED)
+ @FlaggedApi(Flags.FLAG_ASSOCIATION_DEVICE_ICON)
+ public Builder setDeviceIcon(@NonNull Icon deviceIcon) {
+ checkNotUsed();
+ mDeviceIcon = requireNonNull(deviceIcon);
+ return this;
+ }
+
/** @inheritDoc */
@NonNull
@Override
@@ -460,7 +501,7 @@ public final class AssociationRequest implements Parcelable {
+ "provide the display name of the device");
}
return new AssociationRequest(mSingleDevice, emptyIfNull(mDeviceFilters),
- mDeviceProfile, mDisplayName, mSelfManaged, mForceConfirmation);
+ mDeviceProfile, mDisplayName, mSelfManaged, mForceConfirmation, mDeviceIcon);
}
}
@@ -561,7 +602,9 @@ public final class AssociationRequest implements Parcelable {
&& Objects.equals(mDeviceProfilePrivilegesDescription,
that.mDeviceProfilePrivilegesDescription)
&& mCreationTime == that.mCreationTime
- && mSkipPrompt == that.mSkipPrompt;
+ && mSkipPrompt == that.mSkipPrompt
+ && (mDeviceIcon == null ? that.mDeviceIcon == null
+ : mDeviceIcon.sameAs(that.mDeviceIcon));
}
@Override
@@ -579,6 +622,8 @@ public final class AssociationRequest implements Parcelable {
_hash = 31 * _hash + Objects.hashCode(mDeviceProfilePrivilegesDescription);
_hash = 31 * _hash + Long.hashCode(mCreationTime);
_hash = 31 * _hash + Boolean.hashCode(mSkipPrompt);
+ _hash = 31 * _hash + Objects.hashCode(mDeviceIcon);
+
return _hash;
}
@@ -606,6 +651,12 @@ public final class AssociationRequest implements Parcelable {
dest.writeString8(mDeviceProfilePrivilegesDescription);
}
dest.writeLong(mCreationTime);
+ if (mDeviceIcon != null) {
+ dest.writeInt(1);
+ mDeviceIcon.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
}
@Override
@@ -650,6 +701,11 @@ public final class AssociationRequest implements Parcelable {
this.mDeviceProfilePrivilegesDescription = deviceProfilePrivilegesDescription;
this.mCreationTime = creationTime;
this.mSkipPrompt = skipPrompt;
+ if (in.readInt() == 1) {
+ mDeviceIcon = Icon.CREATOR.createFromParcel(in);
+ } else {
+ mDeviceIcon = null;
+ }
}
@NonNull
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 1cdf3b11f247..dfad6de4ba16 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -20,6 +20,8 @@ import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMIN
import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION;
import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER;
import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH;
+import static android.graphics.drawable.Icon.TYPE_URI;
+import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP;
import android.annotation.CallbackExecutor;
@@ -49,6 +51,11 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.graphics.drawable.VectorDrawable;
import android.net.MacAddress;
import android.os.Binder;
import android.os.Handler;
@@ -535,6 +542,13 @@ public final class CompanionDeviceManager {
Objects.requireNonNull(executor, "Executor cannot be null");
Objects.requireNonNull(callback, "Callback cannot be null");
+ final Icon deviceIcon = request.getDeviceIcon();
+
+ if (deviceIcon != null && !isValidIcon(deviceIcon, mContext)) {
+ throw new IllegalArgumentException("The size of the device icon must be 24dp x 24dp to"
+ + "ensure proper display");
+ }
+
try {
mService.associate(request, new AssociationRequestCallbackProxy(executor, callback),
mContext.getOpPackageName(), mContext.getUserId());
@@ -2027,4 +2041,34 @@ public final class CompanionDeviceManager {
}
}
}
+
+ private boolean isValidIcon(Icon icon, Context context) {
+ if (icon.getType() == TYPE_URI_ADAPTIVE_BITMAP || icon.getType() == TYPE_URI) {
+ throw new IllegalArgumentException("The URI based Icon is not supported.");
+ }
+ Drawable drawable = icon.loadDrawable(context);
+ float density = context.getResources().getDisplayMetrics().density;
+
+ if (drawable instanceof BitmapDrawable) {
+ Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
+
+ float widthDp = bitmap.getWidth() / density;
+ float heightDp = bitmap.getHeight() / density;
+
+ if (widthDp != 24 || heightDp != 24) {
+ return false;
+ }
+ } else if (drawable instanceof VectorDrawable) {
+ VectorDrawable vectorDrawable = (VectorDrawable) drawable;
+ float widthDp = vectorDrawable.getIntrinsicWidth() / density;
+ float heightDp = vectorDrawable.getIntrinsicHeight() / density;
+
+ if (widthDp != 24 || heightDp != 24) {
+ return false;
+ }
+ } else {
+ throw new IllegalArgumentException("The format of the device icon is unsupported.");
+ }
+ return true;
+ }
}
diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig
index 93d62cfeb537..2539a12a2a14 100644
--- a/core/java/android/companion/flags.aconfig
+++ b/core/java/android/companion/flags.aconfig
@@ -55,3 +55,11 @@ flag {
description: "Enable association failure code API"
bug: "331459560"
}
+
+flag {
+ name: "association_device_icon"
+ is_exported: true
+ namespace: "companion"
+ description: "Enable set device icon API"
+ bug: "341057668"
+}
diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
index 08155dd3e09a..5805332418d0 100644
--- a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
@@ -31,6 +31,12 @@
<!-- A header for selfManaged devices only. -->
<include layout="@layout/vendor_header" />
+ <!-- A device icon for selfManaged devices only. -->
+ <ImageView
+ android:id="@+id/device_icon"
+ android:visibility="gone"
+ android:contentDescription="@null"
+ style="@style/DeviceIcon" />
<!-- Do NOT change the ID of the root LinearLayout above:
it's referenced in CTS tests. -->
diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml
index fe7cfc64603a..a161a505a0ac 100644
--- a/packages/CompanionDeviceManager/res/values/styles.xml
+++ b/packages/CompanionDeviceManager/res/values/styles.xml
@@ -137,4 +137,10 @@
<item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
</style>
+ <style name="DeviceIcon">
+ <item name="android:layout_width">24dp</item>
+ <item name="android:layout_height">24dp</item>
+ <item name="android:layout_gravity">center</item>
+ </style>
+
</resources> \ No newline at end of file
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
index 7974a37aea57..39bbc25c9384 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
@@ -61,6 +61,7 @@ import android.graphics.BlendMode;
import android.graphics.BlendModeColorFilter;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
import android.net.MacAddress;
import android.os.Bundle;
import android.os.Handler;
@@ -130,6 +131,8 @@ public class CompanionAssociationActivity extends FragmentActivity implements
// Present for single device and multiple device only.
private ImageView mProfileIcon;
+ // Present for self managed association only;
+ private ImageView mDeviceIcon;
// Only present for selfManaged devices.
private ImageView mVendorHeaderImage;
@@ -306,6 +309,8 @@ public class CompanionAssociationActivity extends FragmentActivity implements
mVendorHeaderName = findViewById(R.id.vendor_header_name);
mVendorHeaderButton = findViewById(R.id.vendor_header_button);
+ mDeviceIcon = findViewById(R.id.device_icon);
+
mDeviceListRecyclerView = findViewById(R.id.device_list);
mMultipleDeviceSpinner = findViewById(R.id.spinner_multiple_device);
@@ -430,6 +435,7 @@ public class CompanionAssociationActivity extends FragmentActivity implements
final Drawable vendorIcon;
final CharSequence vendorName;
final Spanned title;
+ final Icon deviceIcon = mRequest.getDeviceIcon();
if (!SUPPORTED_SELF_MANAGED_PROFILES.contains(deviceProfile)) {
throw new RuntimeException("Unsupported profile " + deviceProfile);
@@ -452,6 +458,11 @@ public class CompanionAssociationActivity extends FragmentActivity implements
title = getHtmlFromResources(this, PROFILE_TITLES.get(deviceProfile), mAppLabel,
getString(R.string.device_type), deviceName);
+ if (deviceIcon != null) {
+ mDeviceIcon.setImageIcon(deviceIcon);
+ mDeviceIcon.setVisibility(View.VISIBLE);
+ }
+
if (PROFILE_SUMMARIES.containsKey(deviceProfile)) {
final int summaryResourceId = PROFILE_SUMMARIES.get(deviceProfile);
final Spanned summary = getHtmlFromResources(this, summaryResourceId,
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 42f69e9ae02f..95281c81fc33 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -676,7 +676,7 @@ public class CompanionDeviceManagerService extends SystemService {
final MacAddress macAddressObj = MacAddress.fromString(macAddress);
mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddressObj,
- null, null, null, false, null, null);
+ null, null, null, false, null, null, null);
}
private void checkCanCallNotificationApi(String callingPackage, int userId) {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 4fc9d55d3e17..280494544e3b 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -106,8 +106,8 @@ class CompanionDeviceShellCommand extends ShellCommand {
boolean selfManaged = getNextBooleanArg();
final MacAddress macAddress = MacAddress.fromString(address);
mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress,
- deviceProfile, deviceProfile, /* associatedDevice */ null, selfManaged,
- /* callback */ null, /* resultReceiver */ null);
+ deviceProfile, deviceProfile, /* associatedDevice */ null, false,
+ /* callback */ null, /* resultReceiver */ null, /* deviceIcon */ null);
}
break;
diff --git a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
index 46d60f9c8504..0d57b40f7da4 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
@@ -17,10 +17,12 @@
package com.android.server.companion.association;
import static com.android.internal.util.XmlUtils.readBooleanAttribute;
+import static com.android.internal.util.XmlUtils.readByteArrayAttribute;
import static com.android.internal.util.XmlUtils.readIntAttribute;
import static com.android.internal.util.XmlUtils.readLongAttribute;
import static com.android.internal.util.XmlUtils.readStringAttribute;
import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
+import static com.android.internal.util.XmlUtils.writeByteArrayAttribute;
import static com.android.internal.util.XmlUtils.writeIntAttribute;
import static com.android.internal.util.XmlUtils.writeLongAttribute;
import static com.android.internal.util.XmlUtils.writeStringAttribute;
@@ -36,6 +38,7 @@ import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.companion.AssociationInfo;
+import android.graphics.drawable.Icon;
import android.net.MacAddress;
import android.os.Environment;
import android.util.AtomicFile;
@@ -51,6 +54,7 @@ import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -172,6 +176,7 @@ public final class AssociationDiskStore {
private static final String XML_ATTR_TIME_APPROVED = "time_approved";
private static final String XML_ATTR_LAST_TIME_CONNECTED = "last_time_connected";
private static final String XML_ATTR_SYSTEM_DATA_SYNC_FLAGS = "system_data_sync_flags";
+ private static final String XML_ATTR_DEVICE_ICON = "device_icon";
private static final String LEGACY_XML_ATTR_DEVICE = "device";
@@ -393,7 +398,7 @@ public final class AssociationDiskStore {
return new AssociationInfo(associationId, userId, appPackage, tag,
MacAddress.fromString(deviceAddress), null, profile, null,
/* managedByCompanionApp */ false, notify, /* revoked */ false, /* pending */ false,
- timeApproved, Long.MAX_VALUE, /* systemDataSyncFlags */ 0);
+ timeApproved, Long.MAX_VALUE, /* systemDataSyncFlags */ 0, null);
}
private static Associations readAssociationsV1(@NonNull TypedXmlPullParser parser,
@@ -444,10 +449,12 @@ public final class AssociationDiskStore {
parser, XML_ATTR_LAST_TIME_CONNECTED, Long.MAX_VALUE);
final int systemDataSyncFlags = readIntAttribute(parser,
XML_ATTR_SYSTEM_DATA_SYNC_FLAGS, 0);
+ final Icon deviceIcon = byteArrayToIcon(
+ readByteArrayAttribute(parser, XML_ATTR_DEVICE_ICON));
return new AssociationInfo(associationId, userId, appPackage, tag, macAddress, displayName,
profile, null, selfManaged, notify, revoked, pending, timeApproved,
- lastTimeConnected, systemDataSyncFlags);
+ lastTimeConnected, systemDataSyncFlags, deviceIcon);
}
private static void writeAssociations(@NonNull XmlSerializer parent,
@@ -480,6 +487,8 @@ public final class AssociationDiskStore {
writeLongAttribute(
serializer, XML_ATTR_LAST_TIME_CONNECTED, a.getLastTimeConnectedMs());
writeIntAttribute(serializer, XML_ATTR_SYSTEM_DATA_SYNC_FLAGS, a.getSystemDataSyncFlags());
+ writeByteArrayAttribute(
+ serializer, XML_ATTR_DEVICE_ICON, iconToByteArray(a.getDeviceIcon()));
serializer.endTag(null, XML_TAG_ASSOCIATION);
}
@@ -494,4 +503,24 @@ public final class AssociationDiskStore {
private static @Nullable MacAddress stringToMacAddress(@Nullable String address) {
return address != null ? MacAddress.fromString(address) : null;
}
+
+ private static byte[] iconToByteArray(Icon deviceIcon)
+ throws IOException {
+ if (deviceIcon == null) {
+ return null;
+ }
+
+ ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+ deviceIcon.writeToStream(byteStream);
+ return byteStream.toByteArray();
+ }
+
+ private static Icon byteArrayToIcon(byte[] bytes) throws IOException {
+ if (bytes == null) {
+ return null;
+ }
+
+ ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);
+ return Icon.createFromStream(byteStream);
+ }
}
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index d56f17bf5edd..aebd11a7f582 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -48,6 +48,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManagerInternal;
+import android.graphics.drawable.Icon;
import android.net.MacAddress;
import android.os.Binder;
import android.os.Bundle;
@@ -281,7 +282,7 @@ public class AssociationRequestsProcessor {
createAssociation(userId, packageName, macAddress, request.getDisplayName(),
request.getDeviceProfile(), request.getAssociatedDevice(),
request.isSelfManaged(),
- callback, resultReceiver);
+ callback, resultReceiver, request.getDeviceIcon());
});
}
@@ -292,15 +293,15 @@ public class AssociationRequestsProcessor {
@Nullable MacAddress macAddress, @Nullable CharSequence displayName,
@Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
boolean selfManaged, @Nullable IAssociationRequestCallback callback,
- @Nullable ResultReceiver resultReceiver) {
+ @Nullable ResultReceiver resultReceiver, @Nullable Icon deviceIcon) {
final int id = mAssociationStore.getNextId();
final long timestamp = System.currentTimeMillis();
final AssociationInfo association = new AssociationInfo(id, userId, packageName,
/* tag */ null, macAddress, displayName, deviceProfile, associatedDevice,
selfManaged, /* notifyOnDeviceNearby */ false, /* revoked */ false,
- /* pending */ false, timestamp, Long.MAX_VALUE, /* systemDataSyncFlags */ 0);
-
+ /* pending */ false, timestamp, Long.MAX_VALUE, /* systemDataSyncFlags */ 0,
+ deviceIcon);
// Add role holder for association (if specified) and add new association to store.
maybeGrantRoleAndStoreAssociation(association, callback, resultReceiver);
}
diff --git a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
index c927cd04cf64..f37e0c94caca 100644
--- a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
@@ -86,7 +86,7 @@ public final class PermissionsUtils {
@NonNull AssociationRequest request, int packageUid) {
enforcePermissionForRequestingProfile(context, request.getDeviceProfile(), packageUid);
- if (request.isSelfManaged()) {
+ if (request.isSelfManaged() || request.getDeviceIcon() != null) {
enforcePermissionForRequestingSelfManaged(context, packageUid);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 4d067f6bfc62..9cdc229c1138 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -2000,7 +2000,8 @@ public class VirtualDeviceManagerServiceTest {
/* tag= */ null, MacAddress.BROADCAST_ADDRESS, displayName, deviceProfile,
/* associatedDevice= */ null, /* selfManaged= */ true,
/* notifyOnDeviceNearby= */ false, /* revoked= */ false, /* pending= */ false,
- /* timeApprovedMs= */0, /* lastTimeConnectedMs= */0, /* systemDataSyncFlags= */ -1);
+ /* timeApprovedMs= */0, /* lastTimeConnectedMs= */0,
+ /* systemDataSyncFlags= */ -1, /* deviceIcon= */ null);
}
/** Helper class to drop permissions temporarily and restore them at the end of a test. */