Loading services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java 0 → 100644 +188 −0 Original line number Diff line number Diff line /* * 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<Long, CallMetadataSyncData.Call> mCalls = new HashMap<>(); final List<CallMetadataSyncData.Call> 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<CallMetadataSyncData.Call> getCalls() { return mCalls.values(); } public void addRequest(CallMetadataSyncData.Call call) { mRequests.add(call); } public List<CallMetadataSyncData.Call> 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<Integer> 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<Integer> 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<Call> CREATOR = new Parcelable.Creator<>() { @Override public Call createFromParcel(Parcel source) { return Call.fromParcel(source); } @Override public Call[] newArray(int size) { return new Call[size]; } }; } } services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java +31 −13 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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; Loading @@ -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) Loading @@ -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) { Loading Loading @@ -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; Loading @@ -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; } Loading @@ -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. * Loading services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java 0 → 100644 +64 −0 Original line number Diff line number Diff line /* * 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); } } Loading
services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java 0 → 100644 +188 −0 Original line number Diff line number Diff line /* * 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<Long, CallMetadataSyncData.Call> mCalls = new HashMap<>(); final List<CallMetadataSyncData.Call> 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<CallMetadataSyncData.Call> getCalls() { return mCalls.values(); } public void addRequest(CallMetadataSyncData.Call call) { mRequests.add(call); } public List<CallMetadataSyncData.Call> 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<Integer> 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<Integer> 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<Call> CREATOR = new Parcelable.Creator<>() { @Override public Call createFromParcel(Parcel source) { return Call.fromParcel(source); } @Override public Call[] newArray(int size) { return new Call[size]; } }; } }
services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java +31 −13 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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; Loading @@ -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) Loading @@ -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) { Loading Loading @@ -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; Loading @@ -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; } Loading @@ -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. * Loading
services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java 0 → 100644 +64 −0 Original line number Diff line number Diff line /* * 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); } }