blob: ade0ea40e157640d5d65f90159293621599274b0 [file] [log] [blame]
/*
* Copyright (C) 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 android.location.util.identity;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Binder;
import android.os.Process;
import android.os.UserHandle;
import android.os.WorkSource;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.HexDump;
import java.util.Objects;
/**
* Identifying information on a caller.
*
* @hide
*/
public final class CallerIdentity {
/**
* Construct a CallerIdentity for test purposes.
*/
@VisibleForTesting
public static CallerIdentity forTest(int uid, int pid, String packageName,
@Nullable String attributionTag) {
return forTest(uid, pid, packageName, attributionTag, null);
}
/**
* Construct a CallerIdentity for test purposes.
*/
@VisibleForTesting
public static CallerIdentity forTest(int uid, int pid, String packageName,
@Nullable String attributionTag, @Nullable String listenerId) {
return new CallerIdentity(uid, pid, packageName, attributionTag, listenerId);
}
/**
* Returns a CallerIdentity with PID and listener ID information stripped. This is mostly
* useful for aggregating information for debug purposes, and should not be used in any API with
* security requirements.
*/
public static CallerIdentity forAggregation(CallerIdentity callerIdentity) {
if (callerIdentity.getPid() == 0 && callerIdentity.getListenerId() == null) {
return callerIdentity;
}
return new CallerIdentity(callerIdentity.getUid(), 0, callerIdentity.getPackageName(),
callerIdentity.getAttributionTag(), null);
}
/**
* Creates a CallerIdentity for the current process and context.
*/
public static CallerIdentity fromContext(Context context) {
return new CallerIdentity(Process.myUid(), Process.myPid(), context.getPackageName(),
context.getAttributionTag(), null);
}
/**
* Creates a CallerIdentity from the current binder identity, using the given package and
* feature id. The package will be checked to enforce it belongs to the calling uid, and a
* security exception will be thrown if it is invalid.
*/
public static CallerIdentity fromBinder(Context context, String packageName,
@Nullable String attributionTag) {
return fromBinder(context, packageName, attributionTag, null);
}
/**
* Creates a CallerIdentity from the current binder identity, using the given package, feature
* id, and listener id. The package will be checked to enforce it belongs to the calling uid,
* and a security exception will be thrown if it is invalid.
*/
public static CallerIdentity fromBinder(Context context, String packageName,
@Nullable String attributionTag, @Nullable String listenerId) {
int uid = Binder.getCallingUid();
if (!ArrayUtils.contains(context.getPackageManager().getPackagesForUid(uid), packageName)) {
throw new SecurityException("invalid package \"" + packageName + "\" for uid " + uid);
}
return fromBinderUnsafe(packageName, attributionTag, listenerId);
}
/**
* Creates a CallerIdentity from the current binder identity, using the given package and
* feature id. The package will not be checked to enforce that it belongs to the calling uid -
* this method should only be used if the package will be validated by some other means, such as
* an appops call.
*/
public static CallerIdentity fromBinderUnsafe(String packageName,
@Nullable String attributionTag) {
return fromBinderUnsafe(packageName, attributionTag, null);
}
/**
* Creates a CallerIdentity from the current binder identity, using the given package, feature
* id, and listener id. The package will not be checked to enforce that it belongs to the
* calling uid - this method should only be used if the package will be validated by some other
* means, such as an appops call.
*/
public static CallerIdentity fromBinderUnsafe(String packageName,
@Nullable String attributionTag, @Nullable String listenerId) {
return new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(),
packageName, attributionTag, listenerId);
}
private final int mUid;
private final int mPid;
private final String mPackageName;
private final @Nullable String mAttributionTag;
private final @Nullable String mListenerId;
private CallerIdentity(int uid, int pid, String packageName,
@Nullable String attributionTag, @Nullable String listenerId) {
this.mUid = uid;
this.mPid = pid;
this.mPackageName = Objects.requireNonNull(packageName);
this.mAttributionTag = attributionTag;
this.mListenerId = listenerId;
}
/** The calling UID. */
public int getUid() {
return mUid;
}
/** The calling PID. */
public int getPid() {
return mPid;
}
/** The calling user. */
public int getUserId() {
return UserHandle.getUserId(mUid);
}
/** The calling package name. */
public String getPackageName() {
return mPackageName;
}
/** The calling attribution tag. */
public String getAttributionTag() {
return mAttributionTag;
}
/**
* The calling listener id. A null listener id will match any other listener id for the purposes
* of {@link #equals(Object)}.
*/
public String getListenerId() {
return mListenerId;
}
/** Returns true if this represents a system server identity. */
public boolean isSystemServer() {
return mUid == Process.SYSTEM_UID;
}
/**
* Adds this identity to the worksource supplied, or if not worksource is supplied, creates a
* new worksource representing this identity.
*/
public WorkSource addToWorkSource(@Nullable WorkSource workSource) {
if (workSource == null) {
return new WorkSource(mUid, mPackageName);
} else {
workSource.add(mUid, mPackageName);
return workSource;
}
}
@Override
public String toString() {
int length = 10 + mPackageName.length();
if (mAttributionTag != null) {
length += mAttributionTag.length();
}
StringBuilder builder = new StringBuilder(length);
builder.append(mUid).append("/").append(mPackageName);
if (mAttributionTag != null) {
builder.append("[");
if (mAttributionTag.startsWith(mPackageName)) {
builder.append(mAttributionTag.substring(mPackageName.length()));
} else {
builder.append(mAttributionTag);
}
builder.append("]");
}
if (mListenerId != null) {
builder.append("/").append(HexDump.toHexString(mListenerId.hashCode()));
}
return builder.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof CallerIdentity)) {
return false;
}
CallerIdentity that = (CallerIdentity) o;
return mUid == that.mUid
&& mPid == that.mPid
&& mPackageName.equals(that.mPackageName)
&& Objects.equals(mAttributionTag, that.mAttributionTag)
&& (mListenerId == null || that.mListenerId == null || mListenerId.equals(
that.mListenerId));
}
@Override
public int hashCode() {
return Objects.hash(mUid, mPid, mPackageName, mAttributionTag);
}
}