/* * Copyright (C) 2024 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 android.provider; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Context; import android.net.Uri; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; /** * E2eeContactKeysManager provides access to the provider of end-to-end encryption contact keys. * It manages two types of keys - {@link E2eeContactKey} and {@link E2eeSelfKey}. * * Keys are uniquely identified by: * * Contact keys also use lookupKey which is an opaque value used to identify a contact in * ContactsProvider. */ @FlaggedApi(Flags.FLAG_USER_KEYS) public final class E2eeContactKeysManager { /** * The authority for the end-to-end encryption contact keys provider. * * @hide */ public static final String AUTHORITY = "com.android.contactkeys.contactkeysprovider"; /** * A content:// style uri to the authority for the end-to-end encryption contact keys provider. * * @hide */ @NonNull public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY); /** * Maximum size of an end-to-end encryption contact key. */ private static final int MAX_KEY_SIZE_BYTES = 5000; /** * Special value to distinguish a null array in a parcelable object. */ private static final int ARRAY_IS_NULL = -1; @NonNull private final ContentResolver mContentResolver; /** @hide */ public E2eeContactKeysManager(@NonNull Context context) { Objects.requireNonNull(context); mContentResolver = context.getContentResolver(); } /** * Inserts a new entry into the end-to-end encryption contact keys table or updates one if it * already exists. * The inserted/updated end-to-end encryption contact key is owned by the caller app. * * @param lookupKey value that references the contact * @param deviceId an app-specified identifier for the device * @param accountId an app-specified identifier for the account * @param keyValue the raw bytes for the key (max size is {@link #getMaxKeySizeBytes} bytes) */ @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public void updateOrInsertE2eeContactKey(@NonNull String lookupKey, @NonNull String deviceId, @NonNull String accountId, @NonNull byte[] keyValue) { validateKeyLength(keyValue); Bundle extras = new Bundle(); extras.putString(E2eeContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey)); extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId)); extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId)); extras.putByteArray(E2eeContactKeys.KEY_VALUE, Objects.requireNonNull(keyValue)); nullSafeCall(mContentResolver, E2eeContactKeys.UPDATE_OR_INSERT_CONTACT_KEY_METHOD, extras); } /** * Retrieves an end-to-end encryption contact key entry given the lookup key, device ID, * accountId and inferred caller package name. * * @param lookupKey the value that references the contact * @param deviceId an app-specified identifier for the device * @param accountId an app-specified identifier for the account * @return a {@link E2eeContactKey} object containing the contact key information, * or null if no contact key is found. */ @RequiresPermission(android.Manifest.permission.READ_CONTACTS) @Nullable public E2eeContactKey getE2eeContactKey( @NonNull String lookupKey, @NonNull String deviceId, @NonNull String accountId) { Bundle extras = new Bundle(); extras.putString(E2eeContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey)); extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId)); extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId)); Bundle response = nullSafeCall(mContentResolver, E2eeContactKeys.GET_CONTACT_KEY_METHOD, extras); if (response == null) { return null; } return response.getParcelable(E2eeContactKeys.KEY_CONTACT_KEY, E2eeContactKey.class); } /** * Retrieves all end-to-end encryption contact key entries that belong to apps visible to * the caller. * The keys will be stripped of deviceId, timeUpdated and keyValue data. * * @param lookupKey the value that references the contact * @return a list of {@link E2eeContactKey} objects containing the contact key * information, or an empty list if no keys are found. */ @RequiresPermission(android.Manifest.permission.READ_CONTACTS) @NonNull public List getAllE2eeContactKeys(@NonNull String lookupKey) { Bundle extras = new Bundle(); extras.putString(E2eeContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey)); Bundle response = nullSafeCall(mContentResolver, E2eeContactKeys.GET_ALL_CONTACT_KEYS_METHOD, extras); if (response == null) { return new ArrayList<>(); } List value = response.getParcelableArrayList( E2eeContactKeys.KEY_CONTACT_KEYS, E2eeContactKey.class); if (value == null) { return new ArrayList<>(); } return value; } /** * Retrieves all end-to-end encryption contact key entries for a given lookupKey that belong to * the caller app. * * @param lookupKey the value that references the contact * @return a list of {@link E2eeContactKey} objects containing the end-to-end encryption * contact key information, or an empty list if no keys are found. */ @RequiresPermission(android.Manifest.permission.READ_CONTACTS) @NonNull public List getOwnerE2eeContactKeys(@NonNull String lookupKey) { Bundle extras = new Bundle(); extras.putString(E2eeContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey)); Bundle response = nullSafeCall(mContentResolver, E2eeContactKeys.GET_OWNER_CONTACT_KEYS_METHOD, extras); if (response == null) { return new ArrayList<>(); } List value = response.getParcelableArrayList( E2eeContactKeys.KEY_CONTACT_KEYS, E2eeContactKey.class); if (value == null) { return new ArrayList<>(); } return value; } /** * Updates an end-to-end encryption contact key entry's local verification state that belongs * to the caller app. * * @param lookupKey the value that references the contact * @param deviceId an app-specified identifier for the device * @param accountId an app-specified identifier for the account * @param localVerificationState the new local verification state * @return true if the entry was updated, false otherwise. */ @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean updateE2eeContactKeyLocalVerificationState(@NonNull String lookupKey, @NonNull String deviceId, @NonNull String accountId, @VerificationState int localVerificationState) { validateVerificationState(localVerificationState); Bundle extras = new Bundle(); extras.putString(E2eeContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey)); extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId)); extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId)); extras.putInt(E2eeContactKeys.LOCAL_VERIFICATION_STATE, localVerificationState); Bundle response = nullSafeCall(mContentResolver, E2eeContactKeys.UPDATE_CONTACT_KEY_LOCAL_VERIFICATION_STATE_METHOD, extras); return response != null && response.getBoolean(E2eeContactKeys.KEY_UPDATED_ROWS); } /** * Updates an end-to-end encryption contact key entry's local verification state that belongs * to the app identified by ownerPackageName. * * @param lookupKey the value that references the contact * @param deviceId an app-specified identifier for the device * @param accountId an app-specified identifier for the account * @param ownerPackageName the package name of the app that owns the key * @param localVerificationState the new local verification state * @return true if the entry was updated, false otherwise. * @hide */ @SystemApi @RequiresPermission(allOf = { android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateE2eeContactKeyLocalVerificationState(@NonNull String lookupKey, @NonNull String deviceId, @NonNull String accountId, @NonNull String ownerPackageName, @VerificationState int localVerificationState) { validateVerificationState(localVerificationState); final Bundle extras = new Bundle(); extras.putString(E2eeContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey)); extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId)); extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId)); extras.putString(E2eeContactKeys.OWNER_PACKAGE_NAME, Objects.requireNonNull(ownerPackageName)); extras.putInt(E2eeContactKeys.LOCAL_VERIFICATION_STATE, localVerificationState); final Bundle response = nullSafeCall(mContentResolver, E2eeContactKeys.UPDATE_CONTACT_KEY_LOCAL_VERIFICATION_STATE_METHOD, extras); return response != null && response.getBoolean(E2eeContactKeys.KEY_UPDATED_ROWS); } /** * Updates an end-to-end encryption contact key entry's remote verification state that belongs * to the caller app. * * @param lookupKey the value that references the contact * @param deviceId an app-specified identifier for the device * @param accountId an app-specified identifier for the account * @param remoteVerificationState the new remote verification state * @return true if the entry was updated, false otherwise. */ @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean updateE2eeContactKeyRemoteVerificationState(@NonNull String lookupKey, @NonNull String deviceId, @NonNull String accountId, @VerificationState int remoteVerificationState) { validateVerificationState(remoteVerificationState); Bundle extras = new Bundle(); extras.putString(E2eeContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey)); extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId)); extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId)); extras.putInt(E2eeContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState); Bundle response = nullSafeCall(mContentResolver, E2eeContactKeys.UPDATE_CONTACT_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras); return response != null && response.getBoolean(E2eeContactKeys.KEY_UPDATED_ROWS); } /** * Updates an end-to-end encryption contact key entry's remote verification state that belongs * to the app identified by ownerPackageName. * * @param lookupKey the value that references the contact * @param deviceId an app-specified identifier for the device * @param accountId an app-specified identifier for the account * @param ownerPackageName the package name of the app that owns the key * @param remoteVerificationState the new remote verification state * @return true if the entry was updated, false otherwise. * @hide */ @SystemApi @RequiresPermission(allOf = { android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateE2eeContactKeyRemoteVerificationState(@NonNull String lookupKey, @NonNull String deviceId, @NonNull String accountId, @NonNull String ownerPackageName, @VerificationState int remoteVerificationState) { validateVerificationState(remoteVerificationState); final Bundle extras = new Bundle(); extras.putString(E2eeContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey)); extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId)); extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId)); extras.putString(E2eeContactKeys.OWNER_PACKAGE_NAME, Objects.requireNonNull(ownerPackageName)); extras.putInt(E2eeContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState); final Bundle response = nullSafeCall(mContentResolver, E2eeContactKeys.UPDATE_CONTACT_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras); return response != null && response.getBoolean(E2eeContactKeys.KEY_UPDATED_ROWS); } private static void validateVerificationState(int verificationState) { if (verificationState != VERIFICATION_STATE_UNVERIFIED && verificationState != VERIFICATION_STATE_VERIFICATION_FAILED && verificationState != VERIFICATION_STATE_VERIFIED) { throw new IllegalArgumentException("Verification state value " + verificationState + " is not supported"); } } /** * Removes an end-to-end encryption contact key entry that belongs to the caller app. * * @param lookupKey the value that references the contact * @param deviceId an app-specified identifier for the device * @param accountId an app-specified identifier for the account * @return true if the entry was removed, false otherwise. */ @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean removeE2eeContactKey(@NonNull String lookupKey, @NonNull String deviceId, @NonNull String accountId) { final Bundle extras = new Bundle(); extras.putString(E2eeContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey)); extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId)); extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId)); final Bundle response = nullSafeCall(mContentResolver, E2eeContactKeys.REMOVE_CONTACT_KEY_METHOD, extras); return response != null && response.getBoolean(E2eeContactKeys.KEY_UPDATED_ROWS); } /** * Inserts a new entry into the end-to-end encryption self keys table or updates one if it * already exists. * * @param deviceId an app-specified identifier for the device * @param accountId an app-specified identifier for the account * @param keyValue the raw bytes for the key (max size is {@link #getMaxKeySizeBytes} bytes) * @return true if the entry was added or updated, false otherwise. */ @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean updateOrInsertE2eeSelfKey(@NonNull String deviceId, @NonNull String accountId, @NonNull byte[] keyValue) { validateKeyLength(keyValue); Bundle extras = new Bundle(); extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId)); extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId)); extras.putByteArray(E2eeContactKeys.KEY_VALUE, Objects.requireNonNull(keyValue)); Bundle response = nullSafeCall(mContentResolver, E2eeContactKeys.UPDATE_OR_INSERT_SELF_KEY_METHOD, extras); return response != null && response.getBoolean(E2eeContactKeys.KEY_UPDATED_ROWS); } private static void validateKeyLength(byte[] keyValue) { Objects.requireNonNull(keyValue); if (keyValue.length == 0 || keyValue.length > getMaxKeySizeBytes()) { throw new IllegalArgumentException("Key value length is " + keyValue.length + "." + " Should be more than 0 and less than " + getMaxKeySizeBytes()); } } /** * Updates an end-to-end encryption self key entry's remote verification state. * * @param deviceId an app-specified identifier for the device * @param accountId an app-specified identifier for the account * @param remoteVerificationState the new remote verification state * @return true if the entry was updated, false otherwise. */ @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean updateE2eeSelfKeyRemoteVerificationState(@NonNull String deviceId, @NonNull String accountId, @VerificationState int remoteVerificationState) { validateVerificationState(remoteVerificationState); Bundle extras = new Bundle(); extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId)); extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId)); extras.putInt(E2eeContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState); Bundle response = nullSafeCall(mContentResolver, E2eeContactKeys.UPDATE_SELF_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras); return response != null && response.getBoolean(E2eeContactKeys.KEY_UPDATED_ROWS); } /** * Updates an end-to-end encryption self key entry's remote verification state that belongs to * the app identified by ownerPackageName. * * @param deviceId an app-specified identifier for the device * @param accountId an app-specified identifier for the account * @param ownerPackageName the package name of the app that owns the key * @param remoteVerificationState the new remote verification state * @return true if the entry was updated, false otherwise. * @hide */ @SystemApi @RequiresPermission(allOf = { android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateE2eeSelfKeyRemoteVerificationState(@NonNull String deviceId, @NonNull String accountId, @NonNull String ownerPackageName, @VerificationState int remoteVerificationState) { validateVerificationState(remoteVerificationState); Bundle extras = new Bundle(); extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId)); extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId)); extras.putString(E2eeContactKeys.OWNER_PACKAGE_NAME, Objects.requireNonNull(ownerPackageName)); extras.putInt(E2eeContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState); Bundle response = nullSafeCall(mContentResolver, E2eeContactKeys.UPDATE_SELF_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras); return response != null && response.getBoolean(E2eeContactKeys.KEY_UPDATED_ROWS); } /** * Maximum size of an end-to-end encryption contact key. */ public static int getMaxKeySizeBytes() { return MAX_KEY_SIZE_BYTES; } /** * Returns an end-to-end encryption self key entry given the deviceId and the inferred package * name of the caller. * * @param deviceId an app-specified identifier for the device * @param accountId an app-specified identifier for the account * @return a {@link E2eeSelfKey} object containing the end-to-end encryption self key * information, or null if no self key is found. */ @RequiresPermission(android.Manifest.permission.READ_CONTACTS) @Nullable public E2eeSelfKey getE2eeSelfKey(@NonNull String deviceId, @NonNull String accountId) { Bundle extras = new Bundle(); extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId)); extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId)); Bundle response = nullSafeCall(mContentResolver, E2eeContactKeys.GET_SELF_KEY_METHOD, extras); if (response == null) { return null; } return response.getParcelable(E2eeContactKeys.KEY_CONTACT_KEY, E2eeSelfKey.class); } /** * Returns all end-to-end encryption self key entries that belong to apps visible to the caller. * The keys will be stripped of deviceId, timeUpdated and keyValue data. * * @return a list of {@link E2eeSelfKey} objects containing the end-to-end encryption self key * information, or an empty list if no self keys are found. */ @RequiresPermission(android.Manifest.permission.READ_CONTACTS) @NonNull public List getAllE2eeSelfKeys() { Bundle extras = new Bundle(); Bundle response = nullSafeCall(mContentResolver, E2eeContactKeys.GET_ALL_SELF_KEYS_METHOD, extras); if (response == null) { return new ArrayList<>(); } List value = response.getParcelableArrayList(E2eeContactKeys.KEY_CONTACT_KEYS, E2eeSelfKey.class); if (value == null) { return new ArrayList<>(); } return value; } /** * Returns all end-to-end encryption self key entries that are owned by the caller app. * * @return a list of {@link E2eeSelfKey} objects containing the end-to-end encryption self key * information, or an empty list if no self keys are found. */ @RequiresPermission(android.Manifest.permission.READ_CONTACTS) @NonNull public List getOwnerE2eeSelfKeys() { Bundle extras = new Bundle(); Bundle response = nullSafeCall(mContentResolver, E2eeContactKeys.GET_OWNER_SELF_KEYS_METHOD, extras); if (response == null) { return new ArrayList<>(); } List value = response.getParcelableArrayList(E2eeContactKeys.KEY_CONTACT_KEYS, E2eeSelfKey.class); if (value == null) { return new ArrayList<>(); } return value; } /** * Removes an end-to-end encryption self key entry given the deviceId and the inferred * package name of the caller. * * @param deviceId an app-specified identifier for the device * @param accountId an app-specified identifier for the account * @return true if the entry was removed, false otherwise. */ @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean removeE2eeSelfKey(@NonNull String deviceId, @NonNull String accountId) { Bundle extras = new Bundle(); extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId)); extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId)); Bundle response = nullSafeCall(mContentResolver, E2eeContactKeys.REMOVE_SELF_KEY_METHOD, extras); return response != null && response.getBoolean(E2eeContactKeys.KEY_UPDATED_ROWS); } private Bundle nullSafeCall(@NonNull ContentResolver resolver, @NonNull String method, @Nullable Bundle extras) { try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY_URI)) { return client.call(method, null, extras); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } } /** * Possible values of verification state. * * @hide */ @IntDef(prefix = {"VERIFICATION_STATE_"}, value = { VERIFICATION_STATE_UNVERIFIED, VERIFICATION_STATE_VERIFICATION_FAILED, VERIFICATION_STATE_VERIFIED }) @Retention(RetentionPolicy.SOURCE) public @interface VerificationState { } /** * Unverified state of a contact end to end encrypted key. */ public static final int VERIFICATION_STATE_UNVERIFIED = 0; /** * Failed verification state of a contact end to end encrypted key. */ public static final int VERIFICATION_STATE_VERIFICATION_FAILED = 1; /** * Verified state of a contact end to end encrypted key. */ public static final int VERIFICATION_STATE_VERIFIED = 2; /** @hide */ public static final class E2eeContactKeys { private E2eeContactKeys() { } /** *

* An opaque value that contains hints on how to find the contact if * its row id changed as a result of a sync or aggregation. *

*/ public static final String LOOKUP_KEY = "lookup"; /** *

* An app-specified identifier for the device for which the end-to-end encryption * contact key can be used. *

*/ public static final String DEVICE_ID = "device_id"; /** *

* An app-specified identifier for the account for which the end-to-end encryption * contact key can be used. * Usually a phone number. *

*/ public static final String ACCOUNT_ID = "account_id"; /** *

* The display name for the contact. *

*/ public static final String DISPLAY_NAME = "display_name"; /** *

* The phone number as the user entered it. *

*/ public static final String PHONE_NUMBER = "number"; /** *

* The email address. *

*/ public static final String EMAIL_ADDRESS = "address"; /** *

* Timestamp at which the key was updated. *

*/ public static final String TIME_UPDATED = "time_updated"; /** *

* The raw bytes for the key. *

*/ public static final String KEY_VALUE = "key_value"; /** *

* The package name of the package that created the key. *

*/ public static final String OWNER_PACKAGE_NAME = "owner_package_name"; /** *

* Describes the local verification state for the key, for instance QR-code based * verification. *

*/ public static final String LOCAL_VERIFICATION_STATE = "local_verification_state"; /** *

* Describes the remote verification state for the key, for instance through a key * transparency server. *

*/ public static final String REMOTE_VERIFICATION_STATE = "remote_verification_state"; /** * The method to invoke in order to add a new key for a contact. */ public static final String UPDATE_OR_INSERT_CONTACT_KEY_METHOD = "updateOrInsertContactKey"; /** * The method to invoke in order to retrieve key for a single contact. */ public static final String GET_CONTACT_KEY_METHOD = "getContactKey"; /** * The method to invoke in order to retrieve all end-to-end encryption contact keys. */ public static final String GET_ALL_CONTACT_KEYS_METHOD = "getAllContactKeys"; /** * The method to invoke in order to retrieve end-to-end encryption contact keys that belong * to the caller. */ public static final String GET_OWNER_CONTACT_KEYS_METHOD = "getOwnerContactKeys"; /** * The method to invoke in order to update an end-to-end encryption contact key local * verification state. */ public static final String UPDATE_CONTACT_KEY_LOCAL_VERIFICATION_STATE_METHOD = "updateContactKeyLocalVerificationState"; /** * The method to invoke in order to update an end-to-end encryption contact key remote * verification state. */ public static final String UPDATE_CONTACT_KEY_REMOTE_VERIFICATION_STATE_METHOD = "updateContactKeyRemoteVerificationState"; /** * The method to invoke in order to remove a end-to-end encryption contact key. */ public static final String REMOVE_CONTACT_KEY_METHOD = "removeContactKey"; /** * The method to invoke in order to add a new self key. */ public static final String UPDATE_OR_INSERT_SELF_KEY_METHOD = "updateOrInsertSelfKey"; /** * The method to invoke in order to update a self key remote verification state. */ public static final String UPDATE_SELF_KEY_REMOTE_VERIFICATION_STATE_METHOD = "updateSelfKeyRemoteVerificationState"; /** * The method to invoke in order to retrieve a self key. */ public static final String GET_SELF_KEY_METHOD = "getSelfKey"; /** * The method to invoke in order to retrieve all self keys. */ public static final String GET_ALL_SELF_KEYS_METHOD = "getAllSelfKeys"; /** * The method to invoke in order to retrieve self keys that belong to the caller. */ public static final String GET_OWNER_SELF_KEYS_METHOD = "getOwnerSelfKeys"; /** * The method to invoke in order to remove a new self key. */ public static final String REMOVE_SELF_KEY_METHOD = "removeSelfKey"; /** * Key in the incoming Bundle for all the end-to-end encryption contact keys. */ public static final String KEY_CONTACT_KEYS = "key_contact_keys"; /** * Key in the incoming Bundle for a single end-to-end encryption contact key. */ public static final String KEY_CONTACT_KEY = "key_contact_key"; /** * Key in the incoming Bundle for a number of modified rows. */ public static final String KEY_UPDATED_ROWS = "key_updated_rows"; } /** * A parcelable class encapsulating other users' end to end encrypted contact key. */ public static final class E2eeContactKey extends E2eeBaseKey implements Parcelable { /** * Describes the local verification state for the key, for instance QR-code based * verification. */ private final int mLocalVerificationState; /** * The display name for the contact. */ private final String mDisplayName; /** * The phone number as the user entered it. */ private final String mPhoneNumber; /** * The email address. */ private final String mEmailAddress; /** * @hide */ public E2eeContactKey(@Nullable String deviceId, @NonNull String accountId, @NonNull String ownerPackageName, long timeUpdated, @Nullable byte[] keyValue, @VerificationState int localVerificationState, @VerificationState int remoteVerificationState, @Nullable String displayName, @Nullable String phoneNumber, @Nullable String emailAddress) { super(deviceId, accountId, ownerPackageName, timeUpdated, keyValue, remoteVerificationState); this.mLocalVerificationState = localVerificationState; this.mDisplayName = displayName; this.mPhoneNumber = phoneNumber; this.mEmailAddress = emailAddress; } /** * Gets the local verification state for the key, for instance QR-code based verification. * * @return The local verification state for the key. */ public @VerificationState int getLocalVerificationState() { return mLocalVerificationState; } /** * Gets the display name for the contact. * * @return The display name for the contact. */ @Nullable public String getDisplayName() { return mDisplayName; } /** * Gets the phone number as the user entered it. * * @return The phone number as the user entered it. */ @Nullable public String getPhoneNumber() { return mPhoneNumber; } /** * Gets the email address. * * @return The email address. */ @Nullable public String getEmailAddress() { return mEmailAddress; } @Override public int hashCode() { return Objects.hash(mDeviceId, mAccountId, mOwnerPackageName, mTimeUpdated, Arrays.hashCode(mKeyValue), mLocalVerificationState, mRemoteVerificationState, mDisplayName, mPhoneNumber, mEmailAddress); } @Override public boolean equals(Object obj) { if (obj == null) return false; if (obj == this) return true; if (!(obj instanceof E2eeContactKey toCompare)) { return false; } return Objects.equals(mDeviceId, toCompare.mDeviceId) && Objects.equals(mAccountId, toCompare.mAccountId) && Objects.equals(mOwnerPackageName, toCompare.mOwnerPackageName) && mTimeUpdated == toCompare.mTimeUpdated && Arrays.equals(mKeyValue, toCompare.mKeyValue) && mLocalVerificationState == toCompare.mLocalVerificationState && mRemoteVerificationState == toCompare.mRemoteVerificationState && Objects.equals(mDisplayName, toCompare.mDisplayName) && Objects.equals(mPhoneNumber, toCompare.mPhoneNumber) && Objects.equals(mEmailAddress, toCompare.mEmailAddress); } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString8(mDeviceId); dest.writeString8(mAccountId); dest.writeString8(mOwnerPackageName); dest.writeLong(mTimeUpdated); dest.writeInt(mKeyValue != null ? mKeyValue.length : ARRAY_IS_NULL); if (mKeyValue != null) { dest.writeByteArray(mKeyValue); } dest.writeInt(mLocalVerificationState); dest.writeInt(mRemoteVerificationState); dest.writeString8(mDisplayName); dest.writeString8(mPhoneNumber); dest.writeString8(mEmailAddress); } @Override public int describeContents() { return 0; } @NonNull public static final Creator CREATOR = new Creator<>() { @Override public E2eeContactKey createFromParcel(Parcel source) { String deviceId = source.readString8(); String accountId = source.readString8(); String ownerPackageName = source.readString8(); long timeUpdated = source.readLong(); int keyValueLength = source.readInt(); byte[] keyValue; if (keyValueLength > 0) { keyValue = new byte[keyValueLength]; source.readByteArray(keyValue); } else { keyValue = null; } int localVerificationState = source.readInt(); int remoteVerificationState = source.readInt(); String displayName = source.readString8(); String number = source.readString8(); String address = source.readString8(); return new E2eeContactKey(deviceId, accountId, ownerPackageName, timeUpdated, keyValue, localVerificationState, remoteVerificationState, displayName, number, address); } @Override public E2eeContactKey[] newArray(int size) { return new E2eeContactKey[size]; } }; } /** * A parcelable class encapsulating self end to end encrypted contact key. */ public static final class E2eeSelfKey extends E2eeBaseKey implements Parcelable { /** * @hide */ public E2eeSelfKey(@Nullable String deviceId, @NonNull String accountId, @NonNull String ownerPackageName, long timeUpdated, @Nullable byte[] keyValue, @VerificationState int remoteVerificationState) { super(deviceId, accountId, ownerPackageName, timeUpdated, keyValue, remoteVerificationState); } @Override public int hashCode() { return Objects.hash(mDeviceId, mAccountId, mOwnerPackageName, mTimeUpdated, Arrays.hashCode(mKeyValue), mRemoteVerificationState); } @Override public boolean equals(Object obj) { if (obj == null) return false; if (obj == this) return true; if (!(obj instanceof E2eeSelfKey toCompare)) { return false; } return Objects.equals(mDeviceId, toCompare.mDeviceId) && Objects.equals(mAccountId, toCompare.mAccountId) && Objects.equals(mOwnerPackageName, toCompare.mOwnerPackageName) && mTimeUpdated == toCompare.mTimeUpdated && Arrays.equals(mKeyValue, toCompare.mKeyValue) && mRemoteVerificationState == toCompare.mRemoteVerificationState; } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString8(mDeviceId); dest.writeString8(mAccountId); dest.writeString8(mOwnerPackageName); dest.writeLong(mTimeUpdated); dest.writeInt(mKeyValue != null ? mKeyValue.length : ARRAY_IS_NULL); if (mKeyValue != null) { dest.writeByteArray(mKeyValue); } dest.writeInt(mRemoteVerificationState); } @Override public int describeContents() { return 0; } @NonNull public static final Creator CREATOR = new Creator<>() { @Override public E2eeSelfKey createFromParcel(Parcel source) { String deviceId = source.readString8(); String accountId = source.readString8(); String ownerPackageName = source.readString8(); long timeUpdated = source.readLong(); int keyValueLength = source.readInt(); byte[] keyValue; if (keyValueLength > 0) { keyValue = new byte[keyValueLength]; source.readByteArray(keyValue); } else { keyValue = null; } int remoteVerificationState = source.readInt(); return new E2eeSelfKey(deviceId, accountId, ownerPackageName, timeUpdated, keyValue, remoteVerificationState); } @Override public E2eeSelfKey[] newArray(int size) { return new E2eeSelfKey[size]; } }; } /** * An abstract class that's extended by self and contact key classes. * * @hide */ abstract static class E2eeBaseKey { /** * An app-specified identifier for the device for which the key can be used. */ protected final String mDeviceId; /** * An app-specified identifier for the account for which the key can be used. * Usually a phone number. */ protected final String mAccountId; /** * Owner application package name. */ protected final String mOwnerPackageName; /** * Timestamp at which the key was updated. */ protected final long mTimeUpdated; /** * The raw bytes for the key. */ protected final byte[] mKeyValue; /** * Describes the remote verification state for the end-to-end encryption key, for instance * through a key transparency server. */ protected final int mRemoteVerificationState; protected E2eeBaseKey(@Nullable String deviceId, @NonNull String accountId, @NonNull String ownerPackageName, long timeUpdated, @Nullable byte[] keyValue, @VerificationState int remoteVerificationState) { this.mDeviceId = deviceId; this.mAccountId = accountId; this.mOwnerPackageName = ownerPackageName; this.mTimeUpdated = timeUpdated; this.mKeyValue = keyValue == null ? null : Arrays.copyOf(keyValue, keyValue.length); this.mRemoteVerificationState = remoteVerificationState; } /** * Gets the app-specified identifier for the device for which the end-to-end encryption * key can be used. * Returns null if the app doesn't have the required visibility into * the end-to-end encryption key. * * @return An app-specified identifier for the device. */ @Nullable public String getDeviceId() { return mDeviceId; } /** * Gets the app-specified identifier for the account for which the end-to-end encryption * key can be used. * Usually a phone number. * * @return An app-specified identifier for the account. */ @NonNull public String getAccountId() { return mAccountId; } /** * Gets the owner application package name. * * @return The owner application package name. */ @NonNull public String getOwnerPackageName() { return mOwnerPackageName; } /** * Gets the timestamp at which the end-to-end encryption key was updated. Returns -1 if * the app doesn't have the required visibility into the key. * * @return The timestamp at which the key was updated in the System.currentTimeMillis() * base. */ public long getTimeUpdated() { return mTimeUpdated; } /** * Gets the raw bytes for the end-to-end encryption key. * Returns null if the app doesn't have the required visibility into * the end-to-end encryption key. * * @return A copy of the raw bytes for the end-to-end encryption key. */ @Nullable public byte[] getKeyValue() { return mKeyValue == null ? null : Arrays.copyOf(mKeyValue, mKeyValue.length); } /** * Gets the remote verification state for the end-to-end encryption key, for instance * through a key transparency server. * * @return The remote verification state for the end-to-end encryption key. */ public @VerificationState int getRemoteVerificationState() { return mRemoteVerificationState; } } }