Loading apex/blobstore/framework/java/android/app/blob/BlobHandle.java +24 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,9 @@ import android.os.Parcelable; import com.android.internal.util.Preconditions; import java.util.Arrays; import java.util.Objects; /** * An identifier to represent a blob. */ Loading Loading @@ -173,6 +176,27 @@ public final class BlobHandle implements Parcelable { dest.writeString(tag); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || !(obj instanceof BlobHandle)) { return false; } final BlobHandle other = (BlobHandle) obj; return this.algorithm.equals(other.algorithm) && Arrays.equals(this.digest, other.digest) && this.label.equals(other.label) && this.expiryTimeMillis == other.expiryTimeMillis && this.tag.equals(tag); } @Override public int hashCode() { return Objects.hash(algorithm, Arrays.hashCode(digest), label, expiryTimeMillis, tag); } public static final @NonNull Creator<BlobHandle> CREATOR = new Creator<BlobHandle>() { @Override public @NonNull BlobHandle createFromParcel(@NonNull Parcel source) { Loading apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java +104 −2 Original line number Diff line number Diff line Loading @@ -45,6 +45,11 @@ import java.util.function.Consumer; */ @SystemService(Context.BLOB_STORE_SERVICE) public class BlobStoreManager { /** @hide */ public static final int COMMIT_RESULT_SUCCESS = 0; /** @hide */ public static final int COMMIT_RESULT_ERROR = 1; private final Context mContext; private final IBlobStoreManager mService; Loading Loading @@ -102,7 +107,28 @@ public class BlobStoreManager { */ public @NonNull Session openSession(@IntRange(from = 1) long sessionId) throws IOException { try { return new Session(mService.openSession(sessionId)); return new Session(mService.openSession(sessionId, mContext.getOpPackageName())); } catch (ParcelableException e) { e.maybeRethrow(IOException.class); throw new RuntimeException(e); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Delete an existing session and any data that was written to that session so far. * * @param sessionId a unique id obtained via {@link #createSession(BlobHandle)} that * represents a particular session. * * @throws IOException when there is an I/O error while deleting the session. * @throws SecurityException when the caller does not own the session, or * the session does not exist or is invalid. */ public void deleteSession(@IntRange(from = 1) long sessionId) throws IOException { try { mService.deleteSession(sessionId, mContext.getOpPackageName()); } catch (ParcelableException e) { e.maybeRethrow(IOException.class); throw new RuntimeException(e); Loading Loading @@ -142,6 +168,9 @@ public class BlobStoreManager { * <p> Any active leases will be automatically released when the blob's expiry time * ({@link BlobHandle#getExpiryTimeMillis()}) is elapsed. * * <p> This lease information is persisted and calling this more than once will result in * latest lease overriding any previous lease. * * @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to * acquire a lease for. * @param descriptionResId the resource id for a short description string that can be surfaced Loading Loading @@ -190,6 +219,9 @@ public class BlobStoreManager { * <p> Any active leases will be automatically released when the blob's expiry time * ({@link BlobHandle#getExpiryTimeMillis()}) is elapsed. * * <p> This lease information is persisted and calling this more than once will result in * latest lease overriding any previous lease. * * @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to * acquire a lease for. * @param descriptionResId the resource id for a short description string that can be surfaced Loading Loading @@ -279,7 +311,9 @@ public class BlobStoreManager { public @NonNull ParcelFileDescriptor openWrite(@BytesLong long offsetBytes, @BytesLong long lengthBytes) throws IOException { try { return mSession.openWrite(offsetBytes, lengthBytes); final ParcelFileDescriptor pfd = mSession.openWrite(offsetBytes, lengthBytes); pfd.seekTo(offsetBytes); return pfd; } catch (ParcelableException e) { e.maybeRethrow(IOException.class); throw new RuntimeException(e); Loading Loading @@ -375,6 +409,31 @@ public class BlobStoreManager { } } /** * Returns {@code true} if access has been allowed for a {@code packageName} using either * {@link #allowPackageAccess(String, byte[])}. * Otherwise, {@code false}. * * @param packageName the name of the package to check the access for. * @param certificate the input bytes representing a certificate of type * {@link android.content.pm.PackageManager#CERT_INPUT_SHA256}. * * @throws IOException when there is an I/O error while getting the access type. * @throws IllegalStateException when the caller tries to get access type from a session * which is closed or abandoned. */ public boolean isPackageAccessAllowed(@NonNull String packageName, @NonNull byte[] certificate) throws IOException { try { return mSession.isPackageAccessAllowed(packageName, certificate); } catch (ParcelableException e) { e.maybeRethrow(IOException.class); throw new RuntimeException(e); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Allow packages which are signed with the same certificate as the caller to access this * blob data once it is committed using a {@link BlobHandle} representing the blob. Loading @@ -398,6 +457,26 @@ public class BlobStoreManager { } } /** * Returns {@code true} if access has been allowed for packages signed with the same * certificate as the caller by using {@link #allowSameSignatureAccess()}. * Otherwise, {@code false}. * * @throws IOException when there is an I/O error while getting the access type. * @throws IllegalStateException when the caller tries to get access type from a session * which is closed or abandoned. */ public boolean isSameSignatureAccessAllowed() throws IOException { try { return mSession.isSameSignatureAccessAllowed(); } catch (ParcelableException e) { e.maybeRethrow(IOException.class); throw new RuntimeException(e); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Allow any app on the device to access this blob data once it is committed using * a {@link BlobHandle} representing the blob. Loading Loading @@ -426,6 +505,25 @@ public class BlobStoreManager { } } /** * Returns {@code true} if public access has been allowed by using * {@link #allowPublicAccess()}. Otherwise, {@code false}. * * @throws IOException when there is an I/O error while getting the access type. * @throws IllegalStateException when the caller tries to get access type from a session * which is closed or abandoned. */ public boolean isPublicAccessAllowed() throws IOException { try { return mSession.isPublicAccessAllowed(); } catch (ParcelableException e) { e.maybeRethrow(IOException.class); throw new RuntimeException(e); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Commit the file that was written so far to this session to the blob store maintained by * the system. Loading @@ -439,6 +537,10 @@ public class BlobStoreManager { * {@link BlobHandle#createWithSha256(byte[], CharSequence, long, String)} BlobHandle} * associated with this session. * * <p> Committing the same data more than once will result in replacing the corresponding * access mode (via calling one of {@link #allowPackageAccess(String, byte[])}, * {@link #allowSameSignatureAccess()}, etc) with the latest one. * * @param executor the executor on which result callback will be invoked. * @param resultCallback a callback to receive the commit result. when the result is * {@code 0}, it indicates success. Otherwise, failure. Loading apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl +2 −1 Original line number Diff line number Diff line Loading @@ -21,8 +21,9 @@ import android.app.blob.IBlobStoreSession; /** {@hide} */ interface IBlobStoreManager { long createSession(in BlobHandle handle, in String packageName); IBlobStoreSession openSession(long sessionId); IBlobStoreSession openSession(long sessionId, in String packageName); ParcelFileDescriptor openBlob(in BlobHandle handle, in String packageName); void deleteSession(long sessionId, in String packageName); void acquireLease(in BlobHandle handle, int descriptionResId, long leaseTimeout, in String packageName); Loading apex/blobstore/framework/java/android/app/blob/IBlobStoreSession.aidl +4 −0 Original line number Diff line number Diff line Loading @@ -26,6 +26,10 @@ interface IBlobStoreSession { void allowSameSignatureAccess(); void allowPublicAccess(); boolean isPackageAccessAllowed(in String packageName, in byte[] certificate); boolean isSameSignatureAccessAllowed(); boolean isPublicAccessAllowed(); long getSize(); void close(); void abandon(); Loading apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java 0 → 100644 +147 −0 Original line number Diff line number Diff line /* * Copyright 2020 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.blob; import android.annotation.IntDef; import android.annotation.NonNull; import android.content.Context; import android.content.pm.PackageManager; import android.util.ArraySet; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.Objects; /** * Class for representing how a blob can be shared. * * Note that this class is not thread-safe, callers need to take of synchronizing access. */ class BlobAccessMode { @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, value = { ACCESS_TYPE_PRIVATE, ACCESS_TYPE_PUBLIC, ACCESS_TYPE_SAME_SIGNATURE, ACCESS_TYPE_WHITELIST, }) @interface AccessType {} static final int ACCESS_TYPE_PRIVATE = 1 << 0; static final int ACCESS_TYPE_PUBLIC = 1 << 1; static final int ACCESS_TYPE_SAME_SIGNATURE = 1 << 2; static final int ACCESS_TYPE_WHITELIST = 1 << 3; private int mAccessType = ACCESS_TYPE_PRIVATE; private final ArraySet<PackageIdentifier> mWhitelistedPackages = new ArraySet<>(); void allow(BlobAccessMode other) { if ((other.mAccessType & ACCESS_TYPE_WHITELIST) != 0) { mWhitelistedPackages.addAll(other.mWhitelistedPackages); } mAccessType |= other.mAccessType; } void allowPublicAccess() { mAccessType |= ACCESS_TYPE_PUBLIC; } void allowSameSignatureAccess() { mAccessType |= ACCESS_TYPE_SAME_SIGNATURE; } void allowPackageAccess(@NonNull String packageName, @NonNull byte[] certificate) { mAccessType |= ACCESS_TYPE_WHITELIST; mWhitelistedPackages.add(PackageIdentifier.create(packageName, certificate)); } boolean isPublicAccessAllowed() { return (mAccessType & ACCESS_TYPE_PUBLIC) != 0; } boolean isSameSignatureAccessAllowed() { return (mAccessType & ACCESS_TYPE_SAME_SIGNATURE) != 0; } boolean isPackageAccessAllowed(@NonNull String packageName, @NonNull byte[] certificate) { if ((mAccessType & ACCESS_TYPE_WHITELIST) == 0) { return false; } return mWhitelistedPackages.contains(PackageIdentifier.create(packageName, certificate)); } boolean isAccessAllowedForCaller(Context context, @NonNull String callingPackage, @NonNull String committerPackage) { if ((mAccessType & ACCESS_TYPE_PUBLIC) != 0) { return true; } final PackageManager pm = context.getPackageManager(); if ((mAccessType & ACCESS_TYPE_SAME_SIGNATURE) != 0) { if (pm.checkSignatures(committerPackage, callingPackage) == PackageManager.SIGNATURE_MATCH) { return true; } } if ((mAccessType & ACCESS_TYPE_WHITELIST) != 0) { for (int i = 0; i < mWhitelistedPackages.size(); ++i) { final PackageIdentifier packageIdentifier = mWhitelistedPackages.valueAt(i); if (packageIdentifier.packageName.equals(callingPackage) && pm.hasSigningCertificate(callingPackage, packageIdentifier.certificate, PackageManager.CERT_INPUT_SHA256)) { return true; } } } return false; } private static final class PackageIdentifier { public final String packageName; public final byte[] certificate; private PackageIdentifier(@NonNull String packageName, @NonNull byte[] certificate) { this.packageName = packageName; this.certificate = certificate; } public static PackageIdentifier create(@NonNull String packageName, @NonNull byte[] certificate) { return new PackageIdentifier(packageName, certificate); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || !(obj instanceof PackageIdentifier)) { return false; } final PackageIdentifier other = (PackageIdentifier) obj; return this.packageName.equals(other.packageName) && Arrays.equals(this.certificate, other.certificate); } @Override public int hashCode() { return Objects.hash(packageName, Arrays.hashCode(certificate)); } } } Loading
apex/blobstore/framework/java/android/app/blob/BlobHandle.java +24 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,9 @@ import android.os.Parcelable; import com.android.internal.util.Preconditions; import java.util.Arrays; import java.util.Objects; /** * An identifier to represent a blob. */ Loading Loading @@ -173,6 +176,27 @@ public final class BlobHandle implements Parcelable { dest.writeString(tag); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || !(obj instanceof BlobHandle)) { return false; } final BlobHandle other = (BlobHandle) obj; return this.algorithm.equals(other.algorithm) && Arrays.equals(this.digest, other.digest) && this.label.equals(other.label) && this.expiryTimeMillis == other.expiryTimeMillis && this.tag.equals(tag); } @Override public int hashCode() { return Objects.hash(algorithm, Arrays.hashCode(digest), label, expiryTimeMillis, tag); } public static final @NonNull Creator<BlobHandle> CREATOR = new Creator<BlobHandle>() { @Override public @NonNull BlobHandle createFromParcel(@NonNull Parcel source) { Loading
apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java +104 −2 Original line number Diff line number Diff line Loading @@ -45,6 +45,11 @@ import java.util.function.Consumer; */ @SystemService(Context.BLOB_STORE_SERVICE) public class BlobStoreManager { /** @hide */ public static final int COMMIT_RESULT_SUCCESS = 0; /** @hide */ public static final int COMMIT_RESULT_ERROR = 1; private final Context mContext; private final IBlobStoreManager mService; Loading Loading @@ -102,7 +107,28 @@ public class BlobStoreManager { */ public @NonNull Session openSession(@IntRange(from = 1) long sessionId) throws IOException { try { return new Session(mService.openSession(sessionId)); return new Session(mService.openSession(sessionId, mContext.getOpPackageName())); } catch (ParcelableException e) { e.maybeRethrow(IOException.class); throw new RuntimeException(e); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Delete an existing session and any data that was written to that session so far. * * @param sessionId a unique id obtained via {@link #createSession(BlobHandle)} that * represents a particular session. * * @throws IOException when there is an I/O error while deleting the session. * @throws SecurityException when the caller does not own the session, or * the session does not exist or is invalid. */ public void deleteSession(@IntRange(from = 1) long sessionId) throws IOException { try { mService.deleteSession(sessionId, mContext.getOpPackageName()); } catch (ParcelableException e) { e.maybeRethrow(IOException.class); throw new RuntimeException(e); Loading Loading @@ -142,6 +168,9 @@ public class BlobStoreManager { * <p> Any active leases will be automatically released when the blob's expiry time * ({@link BlobHandle#getExpiryTimeMillis()}) is elapsed. * * <p> This lease information is persisted and calling this more than once will result in * latest lease overriding any previous lease. * * @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to * acquire a lease for. * @param descriptionResId the resource id for a short description string that can be surfaced Loading Loading @@ -190,6 +219,9 @@ public class BlobStoreManager { * <p> Any active leases will be automatically released when the blob's expiry time * ({@link BlobHandle#getExpiryTimeMillis()}) is elapsed. * * <p> This lease information is persisted and calling this more than once will result in * latest lease overriding any previous lease. * * @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to * acquire a lease for. * @param descriptionResId the resource id for a short description string that can be surfaced Loading Loading @@ -279,7 +311,9 @@ public class BlobStoreManager { public @NonNull ParcelFileDescriptor openWrite(@BytesLong long offsetBytes, @BytesLong long lengthBytes) throws IOException { try { return mSession.openWrite(offsetBytes, lengthBytes); final ParcelFileDescriptor pfd = mSession.openWrite(offsetBytes, lengthBytes); pfd.seekTo(offsetBytes); return pfd; } catch (ParcelableException e) { e.maybeRethrow(IOException.class); throw new RuntimeException(e); Loading Loading @@ -375,6 +409,31 @@ public class BlobStoreManager { } } /** * Returns {@code true} if access has been allowed for a {@code packageName} using either * {@link #allowPackageAccess(String, byte[])}. * Otherwise, {@code false}. * * @param packageName the name of the package to check the access for. * @param certificate the input bytes representing a certificate of type * {@link android.content.pm.PackageManager#CERT_INPUT_SHA256}. * * @throws IOException when there is an I/O error while getting the access type. * @throws IllegalStateException when the caller tries to get access type from a session * which is closed or abandoned. */ public boolean isPackageAccessAllowed(@NonNull String packageName, @NonNull byte[] certificate) throws IOException { try { return mSession.isPackageAccessAllowed(packageName, certificate); } catch (ParcelableException e) { e.maybeRethrow(IOException.class); throw new RuntimeException(e); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Allow packages which are signed with the same certificate as the caller to access this * blob data once it is committed using a {@link BlobHandle} representing the blob. Loading @@ -398,6 +457,26 @@ public class BlobStoreManager { } } /** * Returns {@code true} if access has been allowed for packages signed with the same * certificate as the caller by using {@link #allowSameSignatureAccess()}. * Otherwise, {@code false}. * * @throws IOException when there is an I/O error while getting the access type. * @throws IllegalStateException when the caller tries to get access type from a session * which is closed or abandoned. */ public boolean isSameSignatureAccessAllowed() throws IOException { try { return mSession.isSameSignatureAccessAllowed(); } catch (ParcelableException e) { e.maybeRethrow(IOException.class); throw new RuntimeException(e); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Allow any app on the device to access this blob data once it is committed using * a {@link BlobHandle} representing the blob. Loading Loading @@ -426,6 +505,25 @@ public class BlobStoreManager { } } /** * Returns {@code true} if public access has been allowed by using * {@link #allowPublicAccess()}. Otherwise, {@code false}. * * @throws IOException when there is an I/O error while getting the access type. * @throws IllegalStateException when the caller tries to get access type from a session * which is closed or abandoned. */ public boolean isPublicAccessAllowed() throws IOException { try { return mSession.isPublicAccessAllowed(); } catch (ParcelableException e) { e.maybeRethrow(IOException.class); throw new RuntimeException(e); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Commit the file that was written so far to this session to the blob store maintained by * the system. Loading @@ -439,6 +537,10 @@ public class BlobStoreManager { * {@link BlobHandle#createWithSha256(byte[], CharSequence, long, String)} BlobHandle} * associated with this session. * * <p> Committing the same data more than once will result in replacing the corresponding * access mode (via calling one of {@link #allowPackageAccess(String, byte[])}, * {@link #allowSameSignatureAccess()}, etc) with the latest one. * * @param executor the executor on which result callback will be invoked. * @param resultCallback a callback to receive the commit result. when the result is * {@code 0}, it indicates success. Otherwise, failure. Loading
apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl +2 −1 Original line number Diff line number Diff line Loading @@ -21,8 +21,9 @@ import android.app.blob.IBlobStoreSession; /** {@hide} */ interface IBlobStoreManager { long createSession(in BlobHandle handle, in String packageName); IBlobStoreSession openSession(long sessionId); IBlobStoreSession openSession(long sessionId, in String packageName); ParcelFileDescriptor openBlob(in BlobHandle handle, in String packageName); void deleteSession(long sessionId, in String packageName); void acquireLease(in BlobHandle handle, int descriptionResId, long leaseTimeout, in String packageName); Loading
apex/blobstore/framework/java/android/app/blob/IBlobStoreSession.aidl +4 −0 Original line number Diff line number Diff line Loading @@ -26,6 +26,10 @@ interface IBlobStoreSession { void allowSameSignatureAccess(); void allowPublicAccess(); boolean isPackageAccessAllowed(in String packageName, in byte[] certificate); boolean isSameSignatureAccessAllowed(); boolean isPublicAccessAllowed(); long getSize(); void close(); void abandon(); Loading
apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java 0 → 100644 +147 −0 Original line number Diff line number Diff line /* * Copyright 2020 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.blob; import android.annotation.IntDef; import android.annotation.NonNull; import android.content.Context; import android.content.pm.PackageManager; import android.util.ArraySet; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.Objects; /** * Class for representing how a blob can be shared. * * Note that this class is not thread-safe, callers need to take of synchronizing access. */ class BlobAccessMode { @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, value = { ACCESS_TYPE_PRIVATE, ACCESS_TYPE_PUBLIC, ACCESS_TYPE_SAME_SIGNATURE, ACCESS_TYPE_WHITELIST, }) @interface AccessType {} static final int ACCESS_TYPE_PRIVATE = 1 << 0; static final int ACCESS_TYPE_PUBLIC = 1 << 1; static final int ACCESS_TYPE_SAME_SIGNATURE = 1 << 2; static final int ACCESS_TYPE_WHITELIST = 1 << 3; private int mAccessType = ACCESS_TYPE_PRIVATE; private final ArraySet<PackageIdentifier> mWhitelistedPackages = new ArraySet<>(); void allow(BlobAccessMode other) { if ((other.mAccessType & ACCESS_TYPE_WHITELIST) != 0) { mWhitelistedPackages.addAll(other.mWhitelistedPackages); } mAccessType |= other.mAccessType; } void allowPublicAccess() { mAccessType |= ACCESS_TYPE_PUBLIC; } void allowSameSignatureAccess() { mAccessType |= ACCESS_TYPE_SAME_SIGNATURE; } void allowPackageAccess(@NonNull String packageName, @NonNull byte[] certificate) { mAccessType |= ACCESS_TYPE_WHITELIST; mWhitelistedPackages.add(PackageIdentifier.create(packageName, certificate)); } boolean isPublicAccessAllowed() { return (mAccessType & ACCESS_TYPE_PUBLIC) != 0; } boolean isSameSignatureAccessAllowed() { return (mAccessType & ACCESS_TYPE_SAME_SIGNATURE) != 0; } boolean isPackageAccessAllowed(@NonNull String packageName, @NonNull byte[] certificate) { if ((mAccessType & ACCESS_TYPE_WHITELIST) == 0) { return false; } return mWhitelistedPackages.contains(PackageIdentifier.create(packageName, certificate)); } boolean isAccessAllowedForCaller(Context context, @NonNull String callingPackage, @NonNull String committerPackage) { if ((mAccessType & ACCESS_TYPE_PUBLIC) != 0) { return true; } final PackageManager pm = context.getPackageManager(); if ((mAccessType & ACCESS_TYPE_SAME_SIGNATURE) != 0) { if (pm.checkSignatures(committerPackage, callingPackage) == PackageManager.SIGNATURE_MATCH) { return true; } } if ((mAccessType & ACCESS_TYPE_WHITELIST) != 0) { for (int i = 0; i < mWhitelistedPackages.size(); ++i) { final PackageIdentifier packageIdentifier = mWhitelistedPackages.valueAt(i); if (packageIdentifier.packageName.equals(callingPackage) && pm.hasSigningCertificate(callingPackage, packageIdentifier.certificate, PackageManager.CERT_INPUT_SHA256)) { return true; } } } return false; } private static final class PackageIdentifier { public final String packageName; public final byte[] certificate; private PackageIdentifier(@NonNull String packageName, @NonNull byte[] certificate) { this.packageName = packageName; this.certificate = certificate; } public static PackageIdentifier create(@NonNull String packageName, @NonNull byte[] certificate) { return new PackageIdentifier(packageName, certificate); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || !(obj instanceof PackageIdentifier)) { return false; } final PackageIdentifier other = (PackageIdentifier) obj; return this.packageName.equals(other.packageName) && Arrays.equals(this.certificate, other.certificate); } @Override public int hashCode() { return Objects.hash(packageName, Arrays.hashCode(certificate)); } } }