Merge changes I6a3f56bd,Ib5f39eb1 into tm-dev
* changes: Batch log access requests from same client LogAccessDialogActivity improvements
This commit is contained in:
committed by
Android (Google) Code Review
commit
1999fa1097
@@ -42,31 +42,4 @@ oneway interface ILogcatManagerService {
|
||||
* @param fd The FD (Socket) of client who makes the request.
|
||||
*/
|
||||
void finishThread(in int uid, in int gid, in int pid, in int fd);
|
||||
|
||||
|
||||
/**
|
||||
* The function is called by UX component to notify
|
||||
* LogcatManagerService that the user approved
|
||||
* the privileged log data access.
|
||||
*
|
||||
* @param uid The UID of client who makes the request.
|
||||
* @param gid The GID of client who makes the request.
|
||||
* @param pid The PID of client who makes the request.
|
||||
* @param fd The FD (Socket) of client who makes the request.
|
||||
*/
|
||||
void approve(in int uid, in int gid, in int pid, in int fd);
|
||||
|
||||
|
||||
/**
|
||||
* The function is called by UX component to notify
|
||||
* LogcatManagerService that the user declined
|
||||
* the privileged log data access.
|
||||
*
|
||||
* @param uid The UID of client who makes the request.
|
||||
* @param gid The GID of client who makes the request.
|
||||
* @param pid The PID of client who makes the request.
|
||||
* @param fd The FD (Socket) of client who makes the request.
|
||||
*/
|
||||
void decline(in int uid, in int gid, in int pid, in int fd);
|
||||
}
|
||||
|
||||
|
||||
@@ -6776,9 +6776,8 @@
|
||||
</activity>
|
||||
|
||||
<activity android:name="com.android.server.logcat.LogAccessDialogActivity"
|
||||
android:theme="@style/Theme.DeviceDefault.Dialog.Alert.DayNight"
|
||||
android:theme="@style/Theme.Translucent.NoTitleBar"
|
||||
android:excludeFromRecents="true"
|
||||
android:label="@string/log_access_confirmation_title"
|
||||
android:exported="false">
|
||||
</activity>
|
||||
|
||||
|
||||
@@ -16,26 +16,28 @@
|
||||
|
||||
package com.android.server.logcat;
|
||||
|
||||
import android.annotation.StyleRes;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.UserHandle;
|
||||
import android.os.logcat.ILogcatManagerService;
|
||||
import android.util.Slog;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.InflateException;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.server.LocalServices;
|
||||
|
||||
/**
|
||||
* Dialog responsible for obtaining user consent per-use log access
|
||||
@@ -43,61 +45,61 @@ import com.android.internal.R;
|
||||
public class LogAccessDialogActivity extends Activity implements
|
||||
View.OnClickListener {
|
||||
private static final String TAG = LogAccessDialogActivity.class.getSimpleName();
|
||||
private Context mContext;
|
||||
|
||||
private final ILogcatManagerService mLogcatManagerService =
|
||||
ILogcatManagerService.Stub.asInterface(ServiceManager.getService("logcat"));
|
||||
private static final int DIALOG_TIME_OUT = Build.IS_DEBUGGABLE ? 60000 : 300000;
|
||||
private static final int MSG_DISMISS_DIALOG = 0;
|
||||
|
||||
private final LogcatManagerService.LogcatManagerServiceInternal mLogcatManagerInternal =
|
||||
LocalServices.getService(LogcatManagerService.LogcatManagerServiceInternal.class);
|
||||
|
||||
private String mPackageName;
|
||||
|
||||
private int mUid;
|
||||
private int mGid;
|
||||
private int mPid;
|
||||
private int mFd;
|
||||
|
||||
private String mAlertTitle;
|
||||
private AlertDialog.Builder mAlertDialog;
|
||||
private AlertDialog mAlert;
|
||||
private View mAlertView;
|
||||
|
||||
private static final int DIALOG_TIME_OUT = Build.IS_DEBUGGABLE ? 60000 : 300000;
|
||||
private static final int MSG_DISMISS_DIALOG = 0;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
try {
|
||||
mContext = this;
|
||||
|
||||
// retrieve Intent extra information
|
||||
Intent intent = getIntent();
|
||||
getIntentInfo(intent);
|
||||
|
||||
// retrieve the title string from passed intent extra
|
||||
mAlertTitle = getTitleString(mContext, mPackageName, mUid);
|
||||
|
||||
// creaet View
|
||||
mAlertView = createView();
|
||||
|
||||
// create AlertDialog
|
||||
mAlertDialog = new AlertDialog.Builder(this);
|
||||
mAlertDialog.setView(mAlertView);
|
||||
|
||||
// show Alert
|
||||
mAlert = mAlertDialog.create();
|
||||
mAlert.show();
|
||||
|
||||
// set Alert Timeout
|
||||
mHandler.sendEmptyMessageDelayed(MSG_DISMISS_DIALOG, DIALOG_TIME_OUT);
|
||||
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
Slog.e(TAG, "onCreate failed, declining the logd access", e);
|
||||
mLogcatManagerService.decline(mUid, mGid, mPid, mFd);
|
||||
} catch (RemoteException ex) {
|
||||
Slog.e(TAG, "Fails to call remote functions", ex);
|
||||
}
|
||||
// retrieve Intent extra information
|
||||
if (!readIntentInfo(getIntent())) {
|
||||
Slog.e(TAG, "Invalid Intent extras, finishing");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// retrieve the title string from passed intent extra
|
||||
try {
|
||||
mAlertTitle = getTitleString(this, mPackageName, mUid);
|
||||
} catch (NameNotFoundException e) {
|
||||
Slog.e(TAG, "Unable to fetch label of package " + mPackageName, e);
|
||||
declineLogAccess();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// create View
|
||||
boolean isDarkTheme = (getResources().getConfiguration().uiMode
|
||||
& Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
|
||||
int themeId = isDarkTheme ? android.R.style.Theme_DeviceDefault_Dialog_Alert :
|
||||
android.R.style.Theme_DeviceDefault_Light_Dialog_Alert;
|
||||
mAlertView = createView(themeId);
|
||||
|
||||
// create AlertDialog
|
||||
mAlertDialog = new AlertDialog.Builder(this, themeId);
|
||||
mAlertDialog.setView(mAlertView);
|
||||
mAlertDialog.setOnCancelListener(dialog -> declineLogAccess());
|
||||
mAlertDialog.setOnDismissListener(dialog -> finish());
|
||||
|
||||
// show Alert
|
||||
mAlert = mAlertDialog.create();
|
||||
mAlert.show();
|
||||
|
||||
// set Alert Timeout
|
||||
mHandler.sendEmptyMessageDelayed(MSG_DISMISS_DIALOG, DIALOG_TIME_OUT);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -109,21 +111,26 @@ public class LogAccessDialogActivity extends Activity implements
|
||||
mAlert = null;
|
||||
}
|
||||
|
||||
private void getIntentInfo(Intent intent) throws Exception {
|
||||
|
||||
private boolean readIntentInfo(Intent intent) {
|
||||
if (intent == null) {
|
||||
throw new NullPointerException("Intent is null");
|
||||
Slog.e(TAG, "Intent is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
|
||||
if (mPackageName == null || mPackageName.length() == 0) {
|
||||
throw new NullPointerException("Package Name is null");
|
||||
Slog.e(TAG, "Missing package name extra");
|
||||
return false;
|
||||
}
|
||||
|
||||
mUid = intent.getIntExtra("com.android.server.logcat.uid", 0);
|
||||
mGid = intent.getIntExtra("com.android.server.logcat.gid", 0);
|
||||
mPid = intent.getIntExtra("com.android.server.logcat.pid", 0);
|
||||
mFd = intent.getIntExtra("com.android.server.logcat.fd", 0);
|
||||
if (!intent.hasExtra(Intent.EXTRA_UID)) {
|
||||
Slog.e(TAG, "Missing EXTRA_UID");
|
||||
return false;
|
||||
}
|
||||
|
||||
mUid = intent.getIntExtra(Intent.EXTRA_UID, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private Handler mHandler = new Handler() {
|
||||
@@ -133,11 +140,7 @@ public class LogAccessDialogActivity extends Activity implements
|
||||
if (mAlert != null) {
|
||||
mAlert.dismiss();
|
||||
mAlert = null;
|
||||
try {
|
||||
mLogcatManagerService.decline(mUid, mGid, mPid, mFd);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Fails to call remote functions", e);
|
||||
}
|
||||
declineLogAccess();
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -148,25 +151,15 @@ public class LogAccessDialogActivity extends Activity implements
|
||||
};
|
||||
|
||||
private String getTitleString(Context context, String callingPackage, int uid)
|
||||
throws Exception {
|
||||
|
||||
throws NameNotFoundException {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
if (pm == null) {
|
||||
throw new NullPointerException("PackageManager is null");
|
||||
}
|
||||
|
||||
CharSequence appLabel = pm.getApplicationInfoAsUser(callingPackage,
|
||||
PackageManager.MATCH_DIRECT_BOOT_AUTO,
|
||||
UserHandle.getUserId(uid)).loadLabel(pm);
|
||||
if (appLabel == null || appLabel.length() == 0) {
|
||||
throw new NameNotFoundException("Application Label is null");
|
||||
}
|
||||
|
||||
String titleString = context.getString(
|
||||
com.android.internal.R.string.log_access_confirmation_title, appLabel);
|
||||
if (titleString == null || titleString.length() == 0) {
|
||||
throw new NullPointerException("Title is null");
|
||||
}
|
||||
|
||||
return titleString;
|
||||
}
|
||||
@@ -176,9 +169,9 @@ public class LogAccessDialogActivity extends Activity implements
|
||||
* If we cannot retrieve the package name, it returns null and we decline the full device log
|
||||
* access
|
||||
*/
|
||||
private View createView() throws Exception {
|
||||
|
||||
final View view = getLayoutInflater().inflate(
|
||||
private View createView(@StyleRes int themeId) {
|
||||
Context themedContext = new ContextThemeWrapper(getApplicationContext(), themeId);
|
||||
final View view = LayoutInflater.from(themedContext).inflate(
|
||||
R.layout.log_access_user_consent_dialog_permission, null /*root*/);
|
||||
|
||||
if (view == null) {
|
||||
@@ -202,21 +195,17 @@ public class LogAccessDialogActivity extends Activity implements
|
||||
public void onClick(View view) {
|
||||
switch (view.getId()) {
|
||||
case R.id.log_access_dialog_allow_button:
|
||||
try {
|
||||
mLogcatManagerService.approve(mUid, mGid, mPid, mFd);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Fails to call remote functions", e);
|
||||
}
|
||||
mLogcatManagerInternal.approveAccessForClient(mUid, mPackageName);
|
||||
finish();
|
||||
break;
|
||||
case R.id.log_access_dialog_deny_button:
|
||||
try {
|
||||
mLogcatManagerService.decline(mUid, mGid, mPid, mFd);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Fails to call remote functions", e);
|
||||
}
|
||||
declineLogAccess();
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void declineLogAccess() {
|
||||
mLogcatManagerInternal.declineAccessForClient(mUid, mPackageName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,90 +16,322 @@
|
||||
|
||||
package com.android.server.logcat;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManagerInternal;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.ILogd;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserHandle;
|
||||
import android.os.logcat.ILogcatManagerService;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.SystemService;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
|
||||
/**
|
||||
* Service responsible for managing the access to Logcat.
|
||||
*/
|
||||
public final class LogcatManagerService extends SystemService {
|
||||
|
||||
private static final String TAG = "LogcatManagerService";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
/** How long to wait for the user to approve/decline before declining automatically */
|
||||
@VisibleForTesting
|
||||
static final int PENDING_CONFIRMATION_TIMEOUT_MILLIS = Build.IS_DEBUGGABLE ? 70000 : 400000;
|
||||
|
||||
/**
|
||||
* How long an approved / declined status is valid for.
|
||||
*
|
||||
* After a client has been approved/declined log access, if they try to access logs again within
|
||||
* this timeout, the new request will be automatically approved/declined.
|
||||
* Only after this timeout expires will a new request generate another prompt to the user.
|
||||
**/
|
||||
@VisibleForTesting
|
||||
static final int STATUS_EXPIRATION_TIMEOUT_MILLIS = 60 * 1000;
|
||||
|
||||
private static final int MSG_LOG_ACCESS_REQUESTED = 0;
|
||||
private static final int MSG_APPROVE_LOG_ACCESS = 1;
|
||||
private static final int MSG_DECLINE_LOG_ACCESS = 2;
|
||||
private static final int MSG_LOG_ACCESS_FINISHED = 3;
|
||||
private static final int MSG_PENDING_TIMEOUT = 4;
|
||||
private static final int MSG_LOG_ACCESS_STATUS_EXPIRED = 5;
|
||||
|
||||
private static final int STATUS_NEW_REQUEST = 0;
|
||||
private static final int STATUS_PENDING = 1;
|
||||
private static final int STATUS_APPROVED = 2;
|
||||
private static final int STATUS_DECLINED = 3;
|
||||
|
||||
@IntDef(prefix = {"STATUS_"}, value = {
|
||||
STATUS_NEW_REQUEST,
|
||||
STATUS_PENDING,
|
||||
STATUS_APPROVED,
|
||||
STATUS_DECLINED,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface LogAccessRequestStatus {
|
||||
}
|
||||
|
||||
private final Context mContext;
|
||||
private final Injector mInjector;
|
||||
private final Supplier<Long> mClock;
|
||||
private final BinderService mBinderService;
|
||||
private final ExecutorService mThreadExecutor;
|
||||
private ILogd mLogdService;
|
||||
private @NonNull ActivityManager mActivityManager;
|
||||
private final LogcatManagerServiceInternal mLocalService;
|
||||
private final Handler mHandler;
|
||||
private ActivityManagerInternal mActivityManagerInternal;
|
||||
private static final int MAX_UID_IMPORTANCE_COUNT_LISTENER = 2;
|
||||
private static final String TARGET_PACKAGE_NAME = "android";
|
||||
private static final String TARGET_ACTIVITY_NAME =
|
||||
"com.android.server.logcat.LogAccessDialogActivity";
|
||||
private static final String EXTRA_UID = "com.android.server.logcat.uid";
|
||||
private static final String EXTRA_GID = "com.android.server.logcat.gid";
|
||||
private static final String EXTRA_PID = "com.android.server.logcat.pid";
|
||||
private static final String EXTRA_FD = "com.android.server.logcat.fd";
|
||||
private ILogd mLogdService;
|
||||
|
||||
private static final class LogAccessClient {
|
||||
final int mUid;
|
||||
@NonNull
|
||||
final String mPackageName;
|
||||
|
||||
LogAccessClient(int uid, @NonNull String packageName) {
|
||||
mUid = uid;
|
||||
mPackageName = packageName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof LogAccessClient)) return false;
|
||||
LogAccessClient that = (LogAccessClient) o;
|
||||
return mUid == that.mUid && Objects.equals(mPackageName, that.mPackageName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mUid, mPackageName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LogAccessClient{"
|
||||
+ "mUid=" + mUid
|
||||
+ ", mPackageName=" + mPackageName
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
||||
private static final class LogAccessRequest {
|
||||
final int mUid;
|
||||
final int mGid;
|
||||
final int mPid;
|
||||
final int mFd;
|
||||
|
||||
private LogAccessRequest(int uid, int gid, int pid, int fd) {
|
||||
mUid = uid;
|
||||
mGid = gid;
|
||||
mPid = pid;
|
||||
mFd = fd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof LogAccessRequest)) return false;
|
||||
LogAccessRequest that = (LogAccessRequest) o;
|
||||
return mUid == that.mUid && mGid == that.mGid && mPid == that.mPid && mFd == that.mFd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mUid, mGid, mPid, mFd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LogAccessRequest{"
|
||||
+ "mUid=" + mUid
|
||||
+ ", mGid=" + mGid
|
||||
+ ", mPid=" + mPid
|
||||
+ ", mFd=" + mFd
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
||||
private static final class LogAccessStatus {
|
||||
@LogAccessRequestStatus
|
||||
int mStatus = STATUS_NEW_REQUEST;
|
||||
final List<LogAccessRequest> mPendingRequests = new ArrayList<>();
|
||||
}
|
||||
|
||||
private final Map<LogAccessClient, LogAccessStatus> mLogAccessStatus = new ArrayMap<>();
|
||||
private final Map<LogAccessClient, Integer> mActiveLogAccessCount = new ArrayMap<>();
|
||||
|
||||
private final class BinderService extends ILogcatManagerService.Stub {
|
||||
@Override
|
||||
public void startThread(int uid, int gid, int pid, int fd) {
|
||||
mThreadExecutor.execute(new LogdMonitor(uid, gid, pid, fd, true));
|
||||
final LogAccessRequest logAccessRequest = new LogAccessRequest(uid, gid, pid, fd);
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "New log access request: " + logAccessRequest);
|
||||
}
|
||||
final Message msg = mHandler.obtainMessage(MSG_LOG_ACCESS_REQUESTED, logAccessRequest);
|
||||
mHandler.sendMessageAtTime(msg, mClock.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finishThread(int uid, int gid, int pid, int fd) {
|
||||
// TODO This thread will be used to notify the AppOpsManager that
|
||||
// the logd data access is finished.
|
||||
mThreadExecutor.execute(new LogdMonitor(uid, gid, pid, fd, false));
|
||||
final LogAccessRequest logAccessRequest = new LogAccessRequest(uid, gid, pid, fd);
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Log access finished: " + logAccessRequest);
|
||||
}
|
||||
final Message msg = mHandler.obtainMessage(MSG_LOG_ACCESS_FINISHED, logAccessRequest);
|
||||
mHandler.sendMessageAtTime(msg, mClock.get());
|
||||
}
|
||||
}
|
||||
|
||||
final class LogcatManagerServiceInternal {
|
||||
public void approveAccessForClient(int uid, @NonNull String packageName) {
|
||||
final LogAccessClient client = new LogAccessClient(uid, packageName);
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Approving log access for client: " + client);
|
||||
}
|
||||
final Message msg = mHandler.obtainMessage(MSG_APPROVE_LOG_ACCESS, client);
|
||||
mHandler.sendMessageAtTime(msg, mClock.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void approve(int uid, int gid, int pid, int fd) {
|
||||
try {
|
||||
Slog.d(TAG, "Allow logd access for uid: " + uid);
|
||||
getLogdService().approve(uid, gid, pid, fd);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Fails to call remote functions", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decline(int uid, int gid, int pid, int fd) {
|
||||
try {
|
||||
Slog.d(TAG, "Decline logd access for uid: " + uid);
|
||||
getLogdService().decline(uid, gid, pid, fd);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Fails to call remote functions", e);
|
||||
public void declineAccessForClient(int uid, @NonNull String packageName) {
|
||||
final LogAccessClient client = new LogAccessClient(uid, packageName);
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Declining log access for client: " + client);
|
||||
}
|
||||
final Message msg = mHandler.obtainMessage(MSG_DECLINE_LOG_ACCESS, client);
|
||||
mHandler.sendMessageAtTime(msg, mClock.get());
|
||||
}
|
||||
}
|
||||
|
||||
private ILogd getLogdService() {
|
||||
synchronized (LogcatManagerService.this) {
|
||||
if (mLogdService == null) {
|
||||
LogcatManagerService.this.addLogdService();
|
||||
}
|
||||
return mLogdService;
|
||||
if (mLogdService == null) {
|
||||
mLogdService = mInjector.getLogdService();
|
||||
}
|
||||
return mLogdService;
|
||||
}
|
||||
|
||||
private static class LogAccessRequestHandler extends Handler {
|
||||
private final LogcatManagerService mService;
|
||||
|
||||
LogAccessRequestHandler(Looper looper, LogcatManagerService service) {
|
||||
super(looper);
|
||||
mService = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_LOG_ACCESS_REQUESTED: {
|
||||
LogAccessRequest request = (LogAccessRequest) msg.obj;
|
||||
mService.onLogAccessRequested(request);
|
||||
break;
|
||||
}
|
||||
case MSG_APPROVE_LOG_ACCESS: {
|
||||
LogAccessClient client = (LogAccessClient) msg.obj;
|
||||
mService.onAccessApprovedForClient(client);
|
||||
break;
|
||||
}
|
||||
case MSG_DECLINE_LOG_ACCESS: {
|
||||
LogAccessClient client = (LogAccessClient) msg.obj;
|
||||
mService.onAccessDeclinedForClient(client);
|
||||
break;
|
||||
}
|
||||
case MSG_LOG_ACCESS_FINISHED: {
|
||||
LogAccessRequest request = (LogAccessRequest) msg.obj;
|
||||
mService.onLogAccessFinished(request);
|
||||
break;
|
||||
}
|
||||
case MSG_PENDING_TIMEOUT: {
|
||||
LogAccessClient client = (LogAccessClient) msg.obj;
|
||||
mService.onPendingTimeoutExpired(client);
|
||||
break;
|
||||
}
|
||||
case MSG_LOG_ACCESS_STATUS_EXPIRED: {
|
||||
LogAccessClient client = (LogAccessClient) msg.obj;
|
||||
mService.onAccessStatusExpired(client);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class Injector {
|
||||
protected Supplier<Long> createClock() {
|
||||
return SystemClock::uptimeMillis;
|
||||
}
|
||||
|
||||
protected Looper getLooper() {
|
||||
return Looper.getMainLooper();
|
||||
}
|
||||
|
||||
protected ILogd getLogdService() {
|
||||
return ILogd.Stub.asInterface(ServiceManager.getService("logd"));
|
||||
}
|
||||
}
|
||||
|
||||
public LogcatManagerService(Context context) {
|
||||
this(context, new Injector());
|
||||
}
|
||||
|
||||
public LogcatManagerService(Context context, Injector injector) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mInjector = injector;
|
||||
mClock = injector.createClock();
|
||||
mBinderService = new BinderService();
|
||||
mLocalService = new LogcatManagerServiceInternal();
|
||||
mHandler = new LogAccessRequestHandler(injector.getLooper(), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
try {
|
||||
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
|
||||
publishBinderService("logcat", mBinderService);
|
||||
publishLocalService(LogcatManagerServiceInternal.class, mLocalService);
|
||||
} catch (Throwable t) {
|
||||
Slog.e(TAG, "Could not start the LogcatManagerService.", t);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
LogcatManagerServiceInternal getLocalService() {
|
||||
return mLocalService;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
ILogcatManagerService getBinderService() {
|
||||
return mBinderService;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private LogAccessClient getClientForRequest(LogAccessRequest request) {
|
||||
final String packageName = getPackageName(request);
|
||||
if (packageName == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new LogAccessClient(request.mUid, packageName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,12 +339,9 @@ public final class LogcatManagerService extends SystemService {
|
||||
* If we cannot retrieve the package name, it returns null and we decline the full device log
|
||||
* access
|
||||
*/
|
||||
private String getPackageName(int uid, int gid, int pid, int fd) {
|
||||
|
||||
final ActivityManagerInternal activityManagerInternal =
|
||||
LocalServices.getService(ActivityManagerInternal.class);
|
||||
if (activityManagerInternal != null) {
|
||||
String packageName = activityManagerInternal.getPackageNameByPid(pid);
|
||||
private String getPackageName(LogAccessRequest request) {
|
||||
if (mActivityManagerInternal != null) {
|
||||
String packageName = mActivityManagerInternal.getPackageNameByPid(request.mPid);
|
||||
if (packageName != null) {
|
||||
return packageName;
|
||||
}
|
||||
@@ -125,7 +354,7 @@ public final class LogcatManagerService extends SystemService {
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] packageNames = pm.getPackagesForUid(uid);
|
||||
String[] packageNames = pm.getPackagesForUid(request.mUid);
|
||||
|
||||
if (ArrayUtils.isEmpty(packageNames)) {
|
||||
// Decline the logd access if the app name is unknown
|
||||
@@ -142,127 +371,164 @@ public final class LogcatManagerService extends SystemService {
|
||||
}
|
||||
|
||||
return firstPackageName;
|
||||
|
||||
}
|
||||
|
||||
private void declineLogdAccess(int uid, int gid, int pid, int fd) {
|
||||
void onLogAccessRequested(LogAccessRequest request) {
|
||||
final LogAccessClient client = getClientForRequest(request);
|
||||
if (client == null) {
|
||||
declineRequest(request);
|
||||
return;
|
||||
}
|
||||
|
||||
LogAccessStatus logAccessStatus = mLogAccessStatus.get(client);
|
||||
if (logAccessStatus == null) {
|
||||
logAccessStatus = new LogAccessStatus();
|
||||
mLogAccessStatus.put(client, logAccessStatus);
|
||||
}
|
||||
|
||||
switch (logAccessStatus.mStatus) {
|
||||
case STATUS_NEW_REQUEST:
|
||||
logAccessStatus.mPendingRequests.add(request);
|
||||
processNewLogAccessRequest(client);
|
||||
break;
|
||||
case STATUS_PENDING:
|
||||
logAccessStatus.mPendingRequests.add(request);
|
||||
return;
|
||||
case STATUS_APPROVED:
|
||||
approveRequest(client, request);
|
||||
break;
|
||||
case STATUS_DECLINED:
|
||||
declineRequest(request);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldShowConfirmationDialog(LogAccessClient client) {
|
||||
// If the process is foreground, show a dialog for user consent
|
||||
final int procState = mActivityManagerInternal.getUidProcessState(client.mUid);
|
||||
return procState == ActivityManager.PROCESS_STATE_TOP;
|
||||
}
|
||||
|
||||
private void processNewLogAccessRequest(LogAccessClient client) {
|
||||
boolean isInstrumented = mActivityManagerInternal.isUidCurrentlyInstrumented(client.mUid);
|
||||
|
||||
// The instrumented apks only run for testing, so we don't check user permission.
|
||||
if (isInstrumented) {
|
||||
onAccessApprovedForClient(client);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!shouldShowConfirmationDialog(client)) {
|
||||
onAccessDeclinedForClient(client);
|
||||
return;
|
||||
}
|
||||
|
||||
final LogAccessStatus logAccessStatus = mLogAccessStatus.get(client);
|
||||
logAccessStatus.mStatus = STATUS_PENDING;
|
||||
|
||||
mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_PENDING_TIMEOUT, client),
|
||||
mClock.get() + PENDING_CONFIRMATION_TIMEOUT_MILLIS);
|
||||
final Intent mIntent = createIntent(client);
|
||||
mContext.startActivityAsUser(mIntent, UserHandle.SYSTEM);
|
||||
}
|
||||
|
||||
void onAccessApprovedForClient(LogAccessClient client) {
|
||||
scheduleStatusExpiry(client);
|
||||
|
||||
LogAccessStatus logAccessStatus = mLogAccessStatus.get(client);
|
||||
if (logAccessStatus != null) {
|
||||
for (LogAccessRequest request : logAccessStatus.mPendingRequests) {
|
||||
approveRequest(client, request);
|
||||
}
|
||||
logAccessStatus.mStatus = STATUS_APPROVED;
|
||||
logAccessStatus.mPendingRequests.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void onAccessDeclinedForClient(LogAccessClient client) {
|
||||
scheduleStatusExpiry(client);
|
||||
|
||||
LogAccessStatus logAccessStatus = mLogAccessStatus.get(client);
|
||||
if (logAccessStatus != null) {
|
||||
for (LogAccessRequest request : logAccessStatus.mPendingRequests) {
|
||||
declineRequest(request);
|
||||
}
|
||||
logAccessStatus.mStatus = STATUS_DECLINED;
|
||||
logAccessStatus.mPendingRequests.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleStatusExpiry(LogAccessClient client) {
|
||||
mHandler.removeMessages(MSG_PENDING_TIMEOUT, client);
|
||||
mHandler.removeMessages(MSG_LOG_ACCESS_STATUS_EXPIRED, client);
|
||||
mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_LOG_ACCESS_STATUS_EXPIRED, client),
|
||||
mClock.get() + STATUS_EXPIRATION_TIMEOUT_MILLIS);
|
||||
}
|
||||
|
||||
void onPendingTimeoutExpired(LogAccessClient client) {
|
||||
final LogAccessStatus logAccessStatus = mLogAccessStatus.get(client);
|
||||
if (logAccessStatus != null && logAccessStatus.mStatus == STATUS_PENDING) {
|
||||
onAccessDeclinedForClient(client);
|
||||
}
|
||||
}
|
||||
|
||||
void onAccessStatusExpired(LogAccessClient client) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Log access status expired for " + client);
|
||||
}
|
||||
mLogAccessStatus.remove(client);
|
||||
}
|
||||
|
||||
void onLogAccessFinished(LogAccessRequest request) {
|
||||
final LogAccessClient client = getClientForRequest(request);
|
||||
final int activeCount = mActiveLogAccessCount.getOrDefault(client, 1) - 1;
|
||||
|
||||
if (activeCount == 0) {
|
||||
mActiveLogAccessCount.remove(client);
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Client is no longer accessing logs: " + client);
|
||||
}
|
||||
// TODO This will be used to notify the AppOpsManager that the logd data access
|
||||
// is finished.
|
||||
} else {
|
||||
mActiveLogAccessCount.put(client, activeCount);
|
||||
}
|
||||
}
|
||||
|
||||
private void approveRequest(LogAccessClient client, LogAccessRequest request) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Approving log access: " + request);
|
||||
}
|
||||
try {
|
||||
getLogdService().decline(uid, gid, pid, fd);
|
||||
getLogdService().approve(request.mUid, request.mGid, request.mPid, request.mFd);
|
||||
Integer activeCount = mActiveLogAccessCount.getOrDefault(client, 0);
|
||||
mActiveLogAccessCount.put(client, activeCount + 1);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Fails to call remote functions", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getClientInfo(int uid, int gid, int pid, int fd) {
|
||||
return "UID=" + Integer.toString(uid) + " GID=" + Integer.toString(gid) + " PID="
|
||||
+ Integer.toString(pid) + " FD=" + Integer.toString(fd);
|
||||
}
|
||||
|
||||
private class LogdMonitor implements Runnable {
|
||||
|
||||
private final int mUid;
|
||||
private final int mGid;
|
||||
private final int mPid;
|
||||
private final int mFd;
|
||||
private final boolean mStart;
|
||||
|
||||
/**
|
||||
* For starting a thread, the start value is true.
|
||||
* For finishing a thread, the start value is false.
|
||||
*/
|
||||
LogdMonitor(int uid, int gid, int pid, int fd, boolean start) {
|
||||
mUid = uid;
|
||||
mGid = gid;
|
||||
mPid = pid;
|
||||
mFd = fd;
|
||||
mStart = start;
|
||||
private void declineRequest(LogAccessRequest request) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Declining log access: " + request);
|
||||
}
|
||||
|
||||
/**
|
||||
* LogdMonitor generates a prompt for users.
|
||||
* The users decide whether the logd access is allowed.
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
if (mLogdService == null) {
|
||||
LogcatManagerService.this.addLogdService();
|
||||
}
|
||||
|
||||
if (mStart) {
|
||||
|
||||
ActivityManagerInternal ami = LocalServices.getService(
|
||||
ActivityManagerInternal.class);
|
||||
boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(mUid);
|
||||
|
||||
// The instrumented apks only run for testing, so we don't check user permission.
|
||||
if (isCallerInstrumented) {
|
||||
try {
|
||||
getLogdService().approve(mUid, mGid, mPid, mFd);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Fails to call remote functions", e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final int procState = LocalServices.getService(ActivityManagerInternal.class)
|
||||
.getUidProcessState(mUid);
|
||||
// If the process is foreground and we can retrieve the package name, show a dialog
|
||||
// for user consent
|
||||
if (procState == ActivityManager.PROCESS_STATE_TOP) {
|
||||
String packageName = getPackageName(mUid, mGid, mPid, mFd);
|
||||
if (packageName != null) {
|
||||
final Intent mIntent = createIntent(packageName, mUid, mGid, mPid, mFd);
|
||||
mContext.startActivityAsUser(mIntent, UserHandle.SYSTEM);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the process is background or cannot retrieve the package name,
|
||||
* decline the logd access.
|
||||
**/
|
||||
declineLogdAccess(mUid, mGid, mPid, mFd);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public LogcatManagerService(Context context) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mBinderService = new BinderService();
|
||||
mThreadExecutor = Executors.newCachedThreadPool();
|
||||
mActivityManager = context.getSystemService(ActivityManager.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
try {
|
||||
publishBinderService("logcat", mBinderService);
|
||||
} catch (Throwable t) {
|
||||
Slog.e(TAG, "Could not start the LogcatManagerService.", t);
|
||||
getLogdService().decline(request.mUid, request.mGid, request.mPid, request.mFd);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Fails to call remote functions", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void addLogdService() {
|
||||
mLogdService = ILogd.Stub.asInterface(ServiceManager.getService("logd"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Intent for LogAccessDialogActivity.
|
||||
*/
|
||||
public Intent createIntent(String targetPackageName, int uid, int gid, int pid, int fd) {
|
||||
final Intent intent = new Intent();
|
||||
public Intent createIntent(LogAccessClient client) {
|
||||
final Intent intent = new Intent(mContext, LogAccessDialogActivity.class);
|
||||
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
|
||||
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, targetPackageName);
|
||||
intent.putExtra(EXTRA_UID, uid);
|
||||
intent.putExtra(EXTRA_GID, gid);
|
||||
intent.putExtra(EXTRA_PID, pid);
|
||||
intent.putExtra(EXTRA_FD, fd);
|
||||
|
||||
intent.setComponent(new ComponentName(TARGET_PACKAGE_NAME, TARGET_ACTIVITY_NAME));
|
||||
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, client.mPackageName);
|
||||
intent.putExtra(Intent.EXTRA_UID, client.mUid);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.logcat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManagerInternal;
|
||||
import android.content.ContextWrapper;
|
||||
import android.os.ILogd;
|
||||
import android.os.Looper;
|
||||
import android.os.UserHandle;
|
||||
import android.os.test.TestLooper;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.logcat.LogcatManagerService.Injector;
|
||||
import com.android.server.testutils.OffsettableClock;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Tests for {@link com.android.server.logcat.LogcatManagerService}.
|
||||
*
|
||||
* Build/Install/Run:
|
||||
* atest FrameworksServicesTests:LogcatManagerServiceTest
|
||||
*/
|
||||
@SuppressWarnings("GuardedBy")
|
||||
public class LogcatManagerServiceTest {
|
||||
private static final String APP1_PACKAGE_NAME = "app1";
|
||||
private static final int APP1_UID = 10001;
|
||||
private static final int APP1_GID = 10001;
|
||||
private static final int APP1_PID = 10001;
|
||||
private static final String APP2_PACKAGE_NAME = "app2";
|
||||
private static final int APP2_UID = 10002;
|
||||
private static final int APP2_GID = 10002;
|
||||
private static final int APP2_PID = 10002;
|
||||
private static final int FD1 = 10;
|
||||
private static final int FD2 = 11;
|
||||
|
||||
@Mock
|
||||
private ActivityManagerInternal mActivityManagerInternalMock;
|
||||
@Mock
|
||||
private ILogd mLogdMock;
|
||||
|
||||
private LogcatManagerService mService;
|
||||
private LogcatManagerService.LogcatManagerServiceInternal mLocalService;
|
||||
private ContextWrapper mContextSpy;
|
||||
private OffsettableClock mClock;
|
||||
private TestLooper mTestLooper;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);
|
||||
mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
|
||||
mClock = new OffsettableClock.Stopped();
|
||||
mTestLooper = new TestLooper(mClock::now);
|
||||
|
||||
when(mActivityManagerInternalMock.getPackageNameByPid(APP1_PID)).thenReturn(
|
||||
APP1_PACKAGE_NAME);
|
||||
when(mActivityManagerInternalMock.getPackageNameByPid(APP2_PID)).thenReturn(
|
||||
APP2_PACKAGE_NAME);
|
||||
|
||||
mService = new LogcatManagerService(mContextSpy, new Injector() {
|
||||
@Override
|
||||
protected Supplier<Long> createClock() {
|
||||
return mClock::now;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Looper getLooper() {
|
||||
return mTestLooper.getLooper();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ILogd getLogdService() {
|
||||
return mLogdMock;
|
||||
}
|
||||
});
|
||||
mLocalService = mService.getLocalService();
|
||||
mService.onStart();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
LocalServices.removeServiceForTest(ActivityManagerInternal.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a mock and registers it to {@link LocalServices}.
|
||||
*/
|
||||
private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
|
||||
LocalServices.removeServiceForTest(clazz);
|
||||
LocalServices.addService(clazz, mock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_RequestFromBackground_DeclinedWithoutPrompt() throws Exception {
|
||||
when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn(
|
||||
ActivityManager.PROCESS_STATE_RECEIVER);
|
||||
mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mLogdMock).decline(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
verify(mLogdMock, never()).approve(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
verify(mContextSpy, never()).startActivityAsUser(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_RequestFromForegroundService_DeclinedWithoutPrompt() throws Exception {
|
||||
when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn(
|
||||
ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
|
||||
mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mLogdMock).decline(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
verify(mLogdMock, never()).approve(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
verify(mContextSpy, never()).startActivityAsUser(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_RequestFromTop_ShowsPrompt() throws Exception {
|
||||
when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn(
|
||||
ActivityManager.PROCESS_STATE_TOP);
|
||||
mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mLogdMock, never()).approve(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
verify(mLogdMock, never()).decline(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
verify(mContextSpy, times(1)).startActivityAsUser(any(), eq(UserHandle.SYSTEM));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_RequestFromTop_NoInteractionWithPrompt_DeclinesAfterTimeout()
|
||||
throws Exception {
|
||||
when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn(
|
||||
ActivityManager.PROCESS_STATE_TOP);
|
||||
mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
advanceTime(LogcatManagerService.PENDING_CONFIRMATION_TIMEOUT_MILLIS);
|
||||
|
||||
verify(mLogdMock, never()).approve(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
verify(mLogdMock).decline(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_RequestFromTop_Approved() throws Exception {
|
||||
when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn(
|
||||
ActivityManager.PROCESS_STATE_TOP);
|
||||
mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
mTestLooper.dispatchAll();
|
||||
verify(mContextSpy, times(1)).startActivityAsUser(any(), eq(UserHandle.SYSTEM));
|
||||
|
||||
mLocalService.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mLogdMock, times(1)).approve(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
verify(mLogdMock, never()).decline(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_RequestFromTop_Declined() throws Exception {
|
||||
when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn(
|
||||
ActivityManager.PROCESS_STATE_TOP);
|
||||
mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
mTestLooper.dispatchAll();
|
||||
verify(mContextSpy, times(1)).startActivityAsUser(any(), eq(UserHandle.SYSTEM));
|
||||
|
||||
mLocalService.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mLogdMock, never()).approve(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
verify(mLogdMock, times(1)).decline(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_RequestFromTop_MultipleRequestsApprovedTogether() throws Exception {
|
||||
when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn(
|
||||
ActivityManager.PROCESS_STATE_TOP);
|
||||
mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD2);
|
||||
mTestLooper.dispatchAll();
|
||||
verify(mContextSpy, times(1)).startActivityAsUser(any(), eq(UserHandle.SYSTEM));
|
||||
verify(mLogdMock, never()).approve(eq(APP1_UID), eq(APP1_GID), eq(APP1_PID), anyInt());
|
||||
verify(mLogdMock, never()).decline(eq(APP1_UID), eq(APP1_GID), eq(APP1_PID), anyInt());
|
||||
|
||||
mLocalService.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mLogdMock, times(1)).approve(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
verify(mLogdMock, times(1)).approve(APP1_UID, APP1_GID, APP1_PID, FD2);
|
||||
verify(mLogdMock, never()).decline(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
verify(mLogdMock, never()).decline(APP1_UID, APP1_GID, APP1_PID, FD2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_RequestFromTop_MultipleRequestsDeclinedTogether() throws Exception {
|
||||
when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn(
|
||||
ActivityManager.PROCESS_STATE_TOP);
|
||||
mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD2);
|
||||
mTestLooper.dispatchAll();
|
||||
verify(mContextSpy, times(1)).startActivityAsUser(any(), eq(UserHandle.SYSTEM));
|
||||
verify(mLogdMock, never()).approve(eq(APP1_UID), eq(APP1_GID), eq(APP1_PID), anyInt());
|
||||
verify(mLogdMock, never()).decline(eq(APP1_UID), eq(APP1_GID), eq(APP1_PID), anyInt());
|
||||
|
||||
mLocalService.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mLogdMock, times(1)).decline(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
verify(mLogdMock, times(1)).decline(APP1_UID, APP1_GID, APP1_PID, FD2);
|
||||
verify(mLogdMock, never()).approve(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
verify(mLogdMock, never()).approve(APP1_UID, APP1_GID, APP1_PID, FD2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_RequestFromTop_Approved_DoesNotShowPromptAgain() throws Exception {
|
||||
when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn(
|
||||
ActivityManager.PROCESS_STATE_TOP);
|
||||
mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
mTestLooper.dispatchAll();
|
||||
mLocalService.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD2);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mContextSpy, times(1)).startActivityAsUser(any(), eq(UserHandle.SYSTEM));
|
||||
verify(mLogdMock, times(1)).approve(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
verify(mLogdMock, times(1)).approve(APP1_UID, APP1_GID, APP1_PID, FD2);
|
||||
verify(mLogdMock, never()).decline(APP1_UID, APP1_GID, APP1_PID, FD2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_RequestFromTop_Declined_DoesNotShowPromptAgain() throws Exception {
|
||||
when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn(
|
||||
ActivityManager.PROCESS_STATE_TOP);
|
||||
mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
mTestLooper.dispatchAll();
|
||||
mLocalService.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD2);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mContextSpy, times(1)).startActivityAsUser(any(), eq(UserHandle.SYSTEM));
|
||||
verify(mLogdMock, times(1)).decline(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
verify(mLogdMock, times(1)).decline(APP1_UID, APP1_GID, APP1_PID, FD2);
|
||||
verify(mLogdMock, never()).approve(APP1_UID, APP1_GID, APP1_PID, FD2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_RequestFromTop_Approved_ShowsPromptForDifferentClient() throws Exception {
|
||||
when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn(
|
||||
ActivityManager.PROCESS_STATE_TOP);
|
||||
when(mActivityManagerInternalMock.getUidProcessState(APP2_UID)).thenReturn(
|
||||
ActivityManager.PROCESS_STATE_TOP);
|
||||
mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
mTestLooper.dispatchAll();
|
||||
mLocalService.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
mService.getBinderService().startThread(APP2_UID, APP2_GID, APP2_PID, FD2);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mContextSpy, times(2)).startActivityAsUser(any(), eq(UserHandle.SYSTEM));
|
||||
verify(mLogdMock, never()).decline(APP2_UID, APP2_GID, APP2_PID, FD2);
|
||||
verify(mLogdMock, never()).approve(APP2_UID, APP2_GID, APP2_PID, FD2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_RequestFromTop_Approved_ShowPromptAgainAfterTimeout() throws Exception {
|
||||
when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn(
|
||||
ActivityManager.PROCESS_STATE_TOP);
|
||||
mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
mTestLooper.dispatchAll();
|
||||
mLocalService.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
advanceTime(LogcatManagerService.STATUS_EXPIRATION_TIMEOUT_MILLIS);
|
||||
|
||||
mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mContextSpy, times(2)).startActivityAsUser(any(), eq(UserHandle.SYSTEM));
|
||||
}
|
||||
|
||||
private void advanceTime(long timeMs) {
|
||||
mClock.fastForward(timeMs);
|
||||
mTestLooper.dispatchAll();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user