From c28f1329196d7a011b2284c958eb32e921ca685b Mon Sep 17 00:00:00 2001 From: Christine Franks Date: Wed, 19 Apr 2023 02:03:38 -0700 Subject: [PATCH] Add and update cross device sync data holders This allows easier handling of the sync messages as they are prepared for transmssion across the CDM channel, and for processing once they arrive. Bug: 265466098 Test: atest FrameworksServicesTests:com.android.server.companion.datatransfer.contextsync Change-Id: I597899971bb24b9318b99ed8f5b1625eaef6c9b8 --- .../contextsync/CallMetadataSyncData.java | 188 ++++++++++++++++++ .../contextsync/CrossDeviceCall.java | 44 ++-- .../contextsync/CallMetadataSyncDataTest.java | 64 ++++++ 3 files changed, 283 insertions(+), 13 deletions(-) create mode 100644 services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java create mode 100644 services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java new file mode 100644 index 0000000000000..1e4bb9a504bae --- /dev/null +++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2023 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 com.android.server.companion.datatransfer.contextsync; + +import android.annotation.NonNull; +import android.companion.ContextSyncMessage; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** A read-only snapshot of an {@link ContextSyncMessage}. */ +class CallMetadataSyncData { + + final Map mCalls = new HashMap<>(); + final List mRequests = new ArrayList<>(); + + public void addCall(CallMetadataSyncData.Call call) { + mCalls.put(call.getId(), call); + } + + public boolean hasCall(long id) { + return mCalls.containsKey(id); + } + + public Collection getCalls() { + return mCalls.values(); + } + + public void addRequest(CallMetadataSyncData.Call call) { + mRequests.add(call); + } + + public List getRequests() { + return mRequests; + } + + public static class Call implements Parcelable { + private long mId; + private String mCallerId; + private byte[] mAppIcon; + private String mAppName; + private String mAppIdentifier; + private int mStatus; + private final Set mControls = new HashSet<>(); + + public static Call fromParcel(Parcel parcel) { + final Call call = new Call(); + call.setId(parcel.readLong()); + call.setCallerId(parcel.readString()); + call.setAppIcon(parcel.readBlob()); + call.setAppName(parcel.readString()); + call.setAppIdentifier(parcel.readString()); + call.setStatus(parcel.readInt()); + final int numberOfControls = parcel.readInt(); + for (int i = 0; i < numberOfControls; i++) { + call.addControl(parcel.readInt()); + } + return call; + } + + @Override + public void writeToParcel(Parcel parcel, int parcelableFlags) { + parcel.writeLong(mId); + parcel.writeString(mCallerId); + parcel.writeBlob(mAppIcon); + parcel.writeString(mAppName); + parcel.writeString(mAppIdentifier); + parcel.writeInt(mStatus); + parcel.writeInt(mControls.size()); + for (int control : mControls) { + parcel.writeInt(control); + } + } + + void setId(long id) { + mId = id; + } + + void setCallerId(String callerId) { + mCallerId = callerId; + } + + void setAppIcon(byte[] appIcon) { + mAppIcon = appIcon; + } + + void setAppName(String appName) { + mAppName = appName; + } + + void setAppIdentifier(String appIdentifier) { + mAppIdentifier = appIdentifier; + } + + void setStatus(int status) { + mStatus = status; + } + + void addControl(int control) { + mControls.add(control); + } + + long getId() { + return mId; + } + + String getCallerId() { + return mCallerId; + } + + byte[] getAppIcon() { + return mAppIcon; + } + + String getAppName() { + return mAppName; + } + + String getAppIdentifier() { + return mAppIdentifier; + } + + int getStatus() { + return mStatus; + } + + Set getControls() { + return mControls; + } + + boolean hasControl(int control) { + return mControls.contains(control); + } + + @Override + public boolean equals(Object other) { + if (other instanceof CallMetadataSyncData.Call) { + return ((Call) other).getId() == getId(); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hashCode(mId); + } + + @Override + public int describeContents() { + return 0; + } + + @NonNull public static final Parcelable.Creator CREATOR = new Parcelable.Creator<>() { + + @Override + public Call createFromParcel(Parcel source) { + return Call.fromParcel(source); + } + + @Override + public Call[] newArray(int size) { + return new Call[size]; + } + }; + } +} diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java index 077fd2a1157d3..dd0bbf2790eeb 100644 --- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java +++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java @@ -39,6 +39,8 @@ public class CrossDeviceCall { private static final String TAG = "CrossDeviceCall"; + public static final String EXTRA_CALL_ID = + "com.android.companion.datatransfer.contextsync.extra.CALL_ID"; private static final int APP_ICON_BITMAP_DIMENSION = 256; private static final AtomicLong sNextId = new AtomicLong(1); @@ -47,6 +49,7 @@ public class CrossDeviceCall { private final Call mCall; @VisibleForTesting boolean mIsEnterprise; @VisibleForTesting boolean mIsOtt; + private final String mCallingAppPackageName; private String mCallingAppName; private byte[] mCallingAppIcon; private String mCallerDisplayName; @@ -59,7 +62,7 @@ public class CrossDeviceCall { CallAudioState callAudioState) { mId = sNextId.getAndIncrement(); mCall = call; - final String callingAppPackageName = call != null + mCallingAppPackageName = call != null ? call.getDetails().getAccountHandle().getComponentName().getPackageName() : null; mIsOtt = call != null && (call.getDetails().getCallCapabilities() & Call.Details.PROPERTY_SELF_MANAGED) @@ -69,13 +72,13 @@ public class CrossDeviceCall { == Call.Details.PROPERTY_ENTERPRISE_CALL; try { final ApplicationInfo applicationInfo = packageManager - .getApplicationInfo(callingAppPackageName, + .getApplicationInfo(mCallingAppPackageName, PackageManager.ApplicationInfoFlags.of(0)); mCallingAppName = packageManager.getApplicationLabel(applicationInfo).toString(); mCallingAppIcon = renderDrawableToByteArray( packageManager.getApplicationIcon(applicationInfo)); } catch (PackageManager.NameNotFoundException e) { - Slog.e(TAG, "Could not get application info for package " + callingAppPackageName, e); + Slog.e(TAG, "Could not get application info for package " + mCallingAppPackageName, e); } mIsMuted = callAudioState != null && callAudioState.isMuted(); if (call != null) { @@ -170,7 +173,8 @@ public class CrossDeviceCall { } } - private int convertStateToStatus(int callState) { + /** Converts a Telecom call state to a Context Sync status. */ + public static int convertStateToStatus(int callState) { switch (callState) { case Call.STATE_HOLDING: return android.companion.Telecom.Call.ON_HOLD; @@ -178,20 +182,30 @@ public class CrossDeviceCall { return android.companion.Telecom.Call.ONGOING; case Call.STATE_RINGING: return android.companion.Telecom.Call.RINGING; - case Call.STATE_NEW: - case Call.STATE_DIALING: - case Call.STATE_DISCONNECTED: - case Call.STATE_SELECT_PHONE_ACCOUNT: - case Call.STATE_CONNECTING: - case Call.STATE_DISCONNECTING: - case Call.STATE_PULLING_CALL: - case Call.STATE_AUDIO_PROCESSING: - case Call.STATE_SIMULATED_RINGING: default: return android.companion.Telecom.Call.UNKNOWN_STATUS; } } + /** + * Converts a Context Sync status to a Telecom call state. Note that this is lossy for + * and RINGING_SILENCED, as Telecom does not distinguish between RINGING and RINGING_SILENCED. + */ + public static int convertStatusToState(int status) { + switch (status) { + case android.companion.Telecom.Call.ON_HOLD: + return Call.STATE_HOLDING; + case android.companion.Telecom.Call.ONGOING: + return Call.STATE_ACTIVE; + case android.companion.Telecom.Call.RINGING: + case android.companion.Telecom.Call.RINGING_SILENCED: + return Call.STATE_RINGING; + case android.companion.Telecom.Call.UNKNOWN_STATUS: + default: + return Call.STATE_NEW; + } + } + public long getId() { return mId; } @@ -208,6 +222,10 @@ public class CrossDeviceCall { return mCallingAppIcon; } + public String getCallingAppPackageName() { + return mCallingAppPackageName; + } + /** * Get a human-readable "caller id" to display as the origin of the call. * diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java new file mode 100644 index 0000000000000..c5a9af7d909d9 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2023 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 com.android.server.companion.datatransfer.contextsync; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Parcel; +import android.testing.AndroidTestingRunner; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidTestingRunner.class) +public class CallMetadataSyncDataTest { + + @Test + public void call_writeToParcel_fromParcel_reconstructsSuccessfully() { + final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call(); + final long id = 5; + final String callerId = "callerId"; + final byte[] appIcon = "appIcon".getBytes(); + final String appName = "appName"; + final String appIdentifier = "com.google.test"; + final int status = 1; + final int control1 = 2; + final int control2 = 3; + call.setId(id); + call.setCallerId(callerId); + call.setAppIcon(appIcon); + call.setAppName(appName); + call.setAppIdentifier(appIdentifier); + call.setStatus(status); + call.addControl(control1); + call.addControl(control2); + + Parcel parcel = Parcel.obtain(); + call.writeToParcel(parcel, /* flags= */ 0); + parcel.setDataPosition(0); + final CallMetadataSyncData.Call reconstructedCall = CallMetadataSyncData.Call.fromParcel( + parcel); + + assertThat(reconstructedCall.getId()).isEqualTo(id); + assertThat(reconstructedCall.getCallerId()).isEqualTo(callerId); + assertThat(reconstructedCall.getAppIcon()).isEqualTo(appIcon); + assertThat(reconstructedCall.getAppName()).isEqualTo(appName); + assertThat(reconstructedCall.getAppIdentifier()).isEqualTo(appIdentifier); + assertThat(reconstructedCall.getStatus()).isEqualTo(status); + assertThat(reconstructedCall.getControls()).containsExactly(control1, control2); + } +}