Move device admin viewer/adder to specialaccess package.

Bug: 110207366
Test: robo
Change-Id: I51552dcc5ec6525040cfde17087b7f3f6b21b581
This commit is contained in:
Fan Zhang
2018-06-14 12:52:06 -07:00
parent ed3a2bd2c3
commit ca6d86c5ab
12 changed files with 21 additions and 17 deletions

View File

@@ -0,0 +1,710 @@
/*
* Copyright (C) 2010 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.settings.applications.specialaccess.deviceadmin;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.AppOpsManager;
import android.app.Dialog;
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DeviceAdminReceiver;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
import android.util.EventLog;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.widget.AppSecurityPermissions;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.EventLogTags;
import com.android.settings.R;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.users.UserDialogs;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class DeviceAdminAdd extends Activity {
static final String TAG = "DeviceAdminAdd";
static final int DIALOG_WARNING = 1;
private static final int MAX_ADD_MSG_LINES_PORTRAIT = 5;
private static final int MAX_ADD_MSG_LINES_LANDSCAPE = 2;
private static final int MAX_ADD_MSG_LINES = 15;
/**
* Optional key to map to the package name of the Device Admin.
* Currently only used when uninstalling an active device admin.
*/
public static final String EXTRA_DEVICE_ADMIN_PACKAGE_NAME =
"android.app.extra.DEVICE_ADMIN_PACKAGE_NAME";
public static final String EXTRA_CALLED_FROM_SUPPORT_DIALOG =
"android.app.extra.CALLED_FROM_SUPPORT_DIALOG";
private final IBinder mToken = new Binder();
Handler mHandler;
DevicePolicyManager mDPM;
AppOpsManager mAppOps;
DeviceAdminInfo mDeviceAdmin;
CharSequence mAddMsgText;
String mProfileOwnerName;
ImageView mAdminIcon;
TextView mAdminName;
TextView mAdminDescription;
TextView mAddMsg;
TextView mProfileOwnerWarning;
ImageView mAddMsgExpander;
boolean mAddMsgEllipsized = true;
TextView mAdminWarning;
TextView mSupportMessage;
ViewGroup mAdminPolicies;
Button mActionButton;
Button mUninstallButton;
Button mCancelButton;
boolean mUninstalling = false;
boolean mAdding;
boolean mRefreshing;
boolean mWaitingForRemoveMsg;
boolean mAddingProfileOwner;
boolean mAdminPoliciesInitialized;
boolean mIsCalledFromSupportDialog = false;
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
mHandler = new Handler(getMainLooper());
mDPM = (DevicePolicyManager)getSystemService(Context.DEVICE_POLICY_SERVICE);
mAppOps = (AppOpsManager)getSystemService(Context.APP_OPS_SERVICE);
PackageManager packageManager = getPackageManager();
if ((getIntent().getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
Log.w(TAG, "Cannot start ADD_DEVICE_ADMIN as a new task");
finish();
return;
}
mIsCalledFromSupportDialog = getIntent().getBooleanExtra(
EXTRA_CALLED_FROM_SUPPORT_DIALOG, false);
String action = getIntent().getAction();
ComponentName who = (ComponentName)getIntent().getParcelableExtra(
DevicePolicyManager.EXTRA_DEVICE_ADMIN);
if (who == null) {
String packageName = getIntent().getStringExtra(EXTRA_DEVICE_ADMIN_PACKAGE_NAME);
Optional<ComponentName> installedAdmin = findAdminWithPackageName(packageName);
if (!installedAdmin.isPresent()) {
Log.w(TAG, "No component specified in " + action);
finish();
return;
}
who = installedAdmin.get();
mUninstalling = true;
}
if (action != null && action.equals(DevicePolicyManager.ACTION_SET_PROFILE_OWNER)) {
setResult(RESULT_CANCELED);
setFinishOnTouchOutside(true);
mAddingProfileOwner = true;
mProfileOwnerName =
getIntent().getStringExtra(DevicePolicyManager.EXTRA_PROFILE_OWNER_NAME);
String callingPackage = getCallingPackage();
if (callingPackage == null || !callingPackage.equals(who.getPackageName())) {
Log.e(TAG, "Unknown or incorrect caller");
finish();
return;
}
try {
PackageInfo packageInfo = packageManager.getPackageInfo(callingPackage, 0);
if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
Log.e(TAG, "Cannot set a non-system app as a profile owner");
finish();
return;
}
} catch (NameNotFoundException nnfe) {
Log.e(TAG, "Cannot find the package " + callingPackage);
finish();
return;
}
}
ActivityInfo ai;
try {
ai = packageManager.getReceiverInfo(who, PackageManager.GET_META_DATA);
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Unable to retrieve device policy " + who, e);
finish();
return;
}
// When activating, make sure the given component name is actually a valid device admin.
// No need to check this when deactivating, because it is safe to deactivate an active
// invalid device admin.
if (!mDPM.isAdminActive(who)) {
List<ResolveInfo> avail = packageManager.queryBroadcastReceivers(
new Intent(DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED),
PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS);
int count = avail == null ? 0 : avail.size();
boolean found = false;
for (int i=0; i<count; i++) {
ResolveInfo ri = avail.get(i);
if (ai.packageName.equals(ri.activityInfo.packageName)
&& ai.name.equals(ri.activityInfo.name)) {
try {
// We didn't retrieve the meta data for all possible matches, so
// need to use the activity info of this specific one that was retrieved.
ri.activityInfo = ai;
DeviceAdminInfo dpi = new DeviceAdminInfo(this, ri);
found = true;
} catch (XmlPullParserException e) {
Log.w(TAG, "Bad " + ri.activityInfo, e);
} catch (IOException e) {
Log.w(TAG, "Bad " + ri.activityInfo, e);
}
break;
}
}
if (!found) {
Log.w(TAG, "Request to add invalid device admin: " + who);
finish();
return;
}
}
ResolveInfo ri = new ResolveInfo();
ri.activityInfo = ai;
try {
mDeviceAdmin = new DeviceAdminInfo(this, ri);
} catch (XmlPullParserException e) {
Log.w(TAG, "Unable to retrieve device policy " + who, e);
finish();
return;
} catch (IOException e) {
Log.w(TAG, "Unable to retrieve device policy " + who, e);
finish();
return;
}
// This admin already exists, an we have two options at this point. If new policy
// bits are set, show the user the new list. If nothing has changed, simply return
// "OK" immediately.
if (DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN.equals(getIntent().getAction())) {
mRefreshing = false;
if (mDPM.isAdminActive(who)) {
if (mDPM.isRemovingAdmin(who, android.os.Process.myUserHandle().getIdentifier())) {
Log.w(TAG, "Requested admin is already being removed: " + who);
finish();
return;
}
ArrayList<DeviceAdminInfo.PolicyInfo> newPolicies = mDeviceAdmin.getUsedPolicies();
for (int i = 0; i < newPolicies.size(); i++) {
DeviceAdminInfo.PolicyInfo pi = newPolicies.get(i);
if (!mDPM.hasGrantedPolicy(who, pi.ident)) {
mRefreshing = true;
break;
}
}
if (!mRefreshing) {
// Nothing changed (or policies were removed) - return immediately
setResult(Activity.RESULT_OK);
finish();
return;
}
}
}
// If we're trying to add a profile owner and user setup hasn't completed yet, no
// need to prompt for permission. Just add and finish.
if (mAddingProfileOwner && !mDPM.hasUserSetupCompleted()) {
addAndFinish();
return;
}
mAddMsgText = getIntent().getCharSequenceExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION);
setContentView(R.layout.device_admin_add);
mAdminIcon = (ImageView)findViewById(R.id.admin_icon);
mAdminName = (TextView)findViewById(R.id.admin_name);
mAdminDescription = (TextView)findViewById(R.id.admin_description);
mProfileOwnerWarning = (TextView) findViewById(R.id.profile_owner_warning);
mAddMsg = (TextView)findViewById(R.id.add_msg);
mAddMsgExpander = (ImageView) findViewById(R.id.add_msg_expander);
final View.OnClickListener onClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
toggleMessageEllipsis(mAddMsg);
}
};
mAddMsgExpander.setOnClickListener(onClickListener);
mAddMsg.setOnClickListener(onClickListener);
// Determine whether the message can be collapsed - getLineCount() gives the correct
// number of lines only after a layout pass.
mAddMsg.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
final int maxLines = getEllipsizedLines();
// hide the icon if number of visible lines does not exceed maxLines
boolean hideMsgExpander = mAddMsg.getLineCount() <= maxLines;
mAddMsgExpander.setVisibility(hideMsgExpander ? View.GONE : View.VISIBLE);
if (hideMsgExpander) {
mAddMsg.setOnClickListener(null);
((View)mAddMsgExpander.getParent()).invalidate();
}
mAddMsg.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
// toggleMessageEllipsis also handles initial layout:
toggleMessageEllipsis(mAddMsg);
mAdminWarning = (TextView) findViewById(R.id.admin_warning);
mAdminPolicies = (ViewGroup) findViewById(R.id.admin_policies);
mSupportMessage = (TextView) findViewById(R.id.admin_support_message);
mCancelButton = (Button) findViewById(R.id.cancel_button);
mCancelButton.setFilterTouchesWhenObscured(true);
mCancelButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
EventLog.writeEvent(EventLogTags.EXP_DET_DEVICE_ADMIN_DECLINED_BY_USER,
mDeviceAdmin.getActivityInfo().applicationInfo.uid);
finish();
}
});
mUninstallButton = (Button) findViewById(R.id.uninstall_button);
mUninstallButton.setFilterTouchesWhenObscured(true);
mUninstallButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
EventLog.writeEvent(EventLogTags.EXP_DET_DEVICE_ADMIN_UNINSTALLED_BY_USER,
mDeviceAdmin.getActivityInfo().applicationInfo.uid);
mDPM.uninstallPackageWithActiveAdmins(mDeviceAdmin.getPackageName());
finish();
}
});
mActionButton = (Button) findViewById(R.id.action_button);
final View restrictedAction = findViewById(R.id.restricted_action);
restrictedAction.setFilterTouchesWhenObscured(true);
restrictedAction.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (!mActionButton.isEnabled()) {
showPolicyTransparencyDialogIfRequired();
return;
}
if (mAdding) {
addAndFinish();
} else if (isManagedProfile(mDeviceAdmin)
&& mDeviceAdmin.getComponent().equals(mDPM.getProfileOwner())) {
final int userId = UserHandle.myUserId();
UserDialogs.createRemoveDialog(DeviceAdminAdd.this, userId,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
UserManager um = UserManager.get(DeviceAdminAdd.this);
um.removeUser(userId);
finish();
}
}
).show();
} else if (mUninstalling) {
mDPM.uninstallPackageWithActiveAdmins(mDeviceAdmin.getPackageName());
finish();
} else if (!mWaitingForRemoveMsg) {
try {
// Don't allow the admin to put a dialog up in front
// of us while we interact with the user.
ActivityManager.getService().stopAppSwitches();
} catch (RemoteException e) {
}
mWaitingForRemoveMsg = true;
mDPM.getRemoveWarning(mDeviceAdmin.getComponent(),
new RemoteCallback(new RemoteCallback.OnResultListener() {
@Override
public void onResult(Bundle result) {
CharSequence msg = result != null
? result.getCharSequence(
DeviceAdminReceiver.EXTRA_DISABLE_WARNING)
: null;
continueRemoveAction(msg);
}
}, mHandler));
// Don't want to wait too long.
getWindow().getDecorView().getHandler().postDelayed(new Runnable() {
@Override public void run() {
continueRemoveAction(null);
}
}, 2*1000);
}
}
});
}
/**
* Shows a dialog to explain why the button is disabled if required.
*/
private void showPolicyTransparencyDialogIfRequired() {
if (isManagedProfile(mDeviceAdmin)
&& mDeviceAdmin.getComponent().equals(mDPM.getProfileOwner())) {
if (hasBaseCantRemoveProfileRestriction()) {
// If DISALLOW_REMOVE_MANAGED_PROFILE is set by the system, there's no
// point showing a dialog saying it's disabled by an admin.
return;
}
EnforcedAdmin enforcedAdmin = getAdminEnforcingCantRemoveProfile();
if (enforcedAdmin != null) {
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
DeviceAdminAdd.this,
enforcedAdmin);
}
}
}
void addAndFinish() {
try {
logSpecialPermissionChange(true, mDeviceAdmin.getComponent().getPackageName());
mDPM.setActiveAdmin(mDeviceAdmin.getComponent(), mRefreshing);
EventLog.writeEvent(EventLogTags.EXP_DET_DEVICE_ADMIN_ACTIVATED_BY_USER,
mDeviceAdmin.getActivityInfo().applicationInfo.uid);
setResult(Activity.RESULT_OK);
} catch (RuntimeException e) {
// Something bad happened... could be that it was
// already set, though.
Log.w(TAG, "Exception trying to activate admin "
+ mDeviceAdmin.getComponent(), e);
if (mDPM.isAdminActive(mDeviceAdmin.getComponent())) {
setResult(Activity.RESULT_OK);
}
}
if (mAddingProfileOwner) {
try {
mDPM.setProfileOwner(mDeviceAdmin.getComponent(),
mProfileOwnerName, UserHandle.myUserId());
} catch (RuntimeException re) {
setResult(Activity.RESULT_CANCELED);
}
}
finish();
}
void continueRemoveAction(CharSequence msg) {
if (!mWaitingForRemoveMsg) {
return;
}
mWaitingForRemoveMsg = false;
if (msg == null) {
try {
ActivityManager.getService().resumeAppSwitches();
} catch (RemoteException e) {
}
logSpecialPermissionChange(false, mDeviceAdmin.getComponent().getPackageName());
mDPM.removeActiveAdmin(mDeviceAdmin.getComponent());
finish();
} else {
try {
// Continue preventing anything from coming in front.
ActivityManager.getService().stopAppSwitches();
} catch (RemoteException e) {
}
Bundle args = new Bundle();
args.putCharSequence(
DeviceAdminReceiver.EXTRA_DISABLE_WARNING, msg);
showDialog(DIALOG_WARNING, args);
}
}
void logSpecialPermissionChange(boolean allow, String packageName) {
int logCategory = allow ? MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_ADMIN_ALLOW :
MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_ADMIN_DENY;
FeatureFactory.getFactory(this).getMetricsFeatureProvider().action(this, logCategory, packageName);
}
@Override
protected void onResume() {
super.onResume();
mActionButton.setEnabled(true);
updateInterface();
// As long as we are running, don't let anyone overlay stuff on top of the screen.
mAppOps.setUserRestriction(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, true, mToken);
mAppOps.setUserRestriction(AppOpsManager.OP_TOAST_WINDOW, true, mToken);
}
@Override
protected void onPause() {
super.onPause();
// This just greys out the button. The actual listener is attached to R.id.restricted_action
mActionButton.setEnabled(false);
mAppOps.setUserRestriction(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, false, mToken);
mAppOps.setUserRestriction(AppOpsManager.OP_TOAST_WINDOW, false, mToken);
try {
ActivityManager.getService().resumeAppSwitches();
} catch (RemoteException e) {
}
}
@Override
protected void onUserLeaveHint() {
super.onUserLeaveHint();
// In case this is triggered from support dialog, finish this activity once the user leaves
// so that this won't appear as a background next time support dialog is triggered. This
// is because the support dialog activity and this belong to the same task and we can't
// start this in new activity since we need to know the calling package in this activity.
if (mIsCalledFromSupportDialog) {
finish();
}
}
@Override
protected Dialog onCreateDialog(int id, Bundle args) {
switch (id) {
case DIALOG_WARNING: {
CharSequence msg = args.getCharSequence(DeviceAdminReceiver.EXTRA_DISABLE_WARNING);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(msg);
builder.setPositiveButton(R.string.dlg_ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
try {
ActivityManager.getService().resumeAppSwitches();
} catch (RemoteException e) {
}
mDPM.removeActiveAdmin(mDeviceAdmin.getComponent());
finish();
}
});
builder.setNegativeButton(R.string.dlg_cancel, null);
return builder.create();
}
default:
return super.onCreateDialog(id, args);
}
}
void updateInterface() {
findViewById(R.id.restricted_icon).setVisibility(View.GONE);
mAdminIcon.setImageDrawable(mDeviceAdmin.loadIcon(getPackageManager()));
mAdminName.setText(mDeviceAdmin.loadLabel(getPackageManager()));
try {
mAdminDescription.setText(
mDeviceAdmin.loadDescription(getPackageManager()));
mAdminDescription.setVisibility(View.VISIBLE);
} catch (Resources.NotFoundException e) {
mAdminDescription.setVisibility(View.GONE);
}
if (mAddingProfileOwner) {
mProfileOwnerWarning.setVisibility(View.VISIBLE);
}
if (mAddMsgText != null) {
mAddMsg.setText(mAddMsgText);
mAddMsg.setVisibility(View.VISIBLE);
} else {
mAddMsg.setVisibility(View.GONE);
mAddMsgExpander.setVisibility(View.GONE);
}
if (!mRefreshing && !mAddingProfileOwner
&& mDPM.isAdminActive(mDeviceAdmin.getComponent())) {
mAdding = false;
final boolean isProfileOwner =
mDeviceAdmin.getComponent().equals(mDPM.getProfileOwner());
final boolean isManagedProfile = isManagedProfile(mDeviceAdmin);
if (isProfileOwner && isManagedProfile) {
// Profile owner in a managed profile, user can remove profile to disable admin.
mAdminWarning.setText(R.string.admin_profile_owner_message);
mActionButton.setText(R.string.remove_managed_profile_label);
final EnforcedAdmin admin = getAdminEnforcingCantRemoveProfile();
final boolean hasBaseRestriction = hasBaseCantRemoveProfileRestriction();
if (admin != null && !hasBaseRestriction) {
findViewById(R.id.restricted_icon).setVisibility(View.VISIBLE);
}
mActionButton.setEnabled(admin == null && !hasBaseRestriction);
} else if (isProfileOwner || mDeviceAdmin.getComponent().equals(
mDPM.getDeviceOwnerComponentOnCallingUser())) {
// Profile owner in a user or device owner, user can't disable admin.
if (isProfileOwner) {
// Show profile owner in a user description.
mAdminWarning.setText(R.string.admin_profile_owner_user_message);
} else {
// Show device owner description.
mAdminWarning.setText(R.string.admin_device_owner_message);
}
mActionButton.setText(R.string.remove_device_admin);
mActionButton.setEnabled(false);
} else {
addDeviceAdminPolicies(false /* showDescription */);
mAdminWarning.setText(getString(R.string.device_admin_status,
mDeviceAdmin.getActivityInfo().applicationInfo.loadLabel(
getPackageManager())));
setTitle(R.string.active_device_admin_msg);
if (mUninstalling) {
mActionButton.setText(R.string.remove_and_uninstall_device_admin);
} else {
mActionButton.setText(R.string.remove_device_admin);
}
}
CharSequence supportMessage = mDPM.getLongSupportMessageForUser(
mDeviceAdmin.getComponent(), UserHandle.myUserId());
if (!TextUtils.isEmpty(supportMessage)) {
mSupportMessage.setText(supportMessage);
mSupportMessage.setVisibility(View.VISIBLE);
} else {
mSupportMessage.setVisibility(View.GONE);
}
} else {
addDeviceAdminPolicies(true /* showDescription */);
mAdminWarning.setText(getString(R.string.device_admin_warning,
mDeviceAdmin.getActivityInfo().applicationInfo.loadLabel(getPackageManager())));
if (mAddingProfileOwner) {
setTitle(getText(R.string.profile_owner_add_title));
} else {
setTitle(getText(R.string.add_device_admin_msg));
}
mActionButton.setText(getText(R.string.add_device_admin));
if (isAdminUninstallable()) {
mUninstallButton.setVisibility(View.VISIBLE);
}
mSupportMessage.setVisibility(View.GONE);
mAdding = true;
}
}
private EnforcedAdmin getAdminEnforcingCantRemoveProfile() {
// Removing a managed profile is disallowed if DISALLOW_REMOVE_MANAGED_PROFILE
// is set in the parent rather than the user itself.
return RestrictedLockUtils.checkIfRestrictionEnforced(this,
UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, getParentUserId());
}
private boolean hasBaseCantRemoveProfileRestriction() {
return RestrictedLockUtils.hasBaseUserRestriction(this,
UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, getParentUserId());
}
private int getParentUserId() {
return UserManager.get(this).getProfileParent(UserHandle.myUserId()).id;
}
private void addDeviceAdminPolicies(boolean showDescription) {
if (!mAdminPoliciesInitialized) {
boolean isAdminUser = UserManager.get(this).isAdminUser();
for (DeviceAdminInfo.PolicyInfo pi : mDeviceAdmin.getUsedPolicies()) {
int descriptionId = isAdminUser ? pi.description : pi.descriptionForSecondaryUsers;
int labelId = isAdminUser ? pi.label : pi.labelForSecondaryUsers;
View view = AppSecurityPermissions.getPermissionItemView(this, getText(labelId),
showDescription ? getText(descriptionId) : "", true);
mAdminPolicies.addView(view);
}
mAdminPoliciesInitialized = true;
}
}
void toggleMessageEllipsis(View v) {
TextView tv = (TextView) v;
mAddMsgEllipsized = ! mAddMsgEllipsized;
tv.setEllipsize(mAddMsgEllipsized ? TruncateAt.END : null);
tv.setMaxLines(mAddMsgEllipsized ? getEllipsizedLines() : MAX_ADD_MSG_LINES);
mAddMsgExpander.setImageResource(mAddMsgEllipsized ?
com.android.internal.R.drawable.expander_ic_minimized :
com.android.internal.R.drawable.expander_ic_maximized);
}
int getEllipsizedLines() {
Display d = ((WindowManager) getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
return d.getHeight() > d.getWidth() ?
MAX_ADD_MSG_LINES_PORTRAIT : MAX_ADD_MSG_LINES_LANDSCAPE;
}
/**
* @return true if adminInfo is running in a managed profile.
*/
private boolean isManagedProfile(DeviceAdminInfo adminInfo) {
UserManager um = UserManager.get(this);
UserInfo info = um.getUserInfo(
UserHandle.getUserId(adminInfo.getActivityInfo().applicationInfo.uid));
return info != null ? info.isManagedProfile() : false;
}
/**
* @return an {@link Optional} containing the admin with a given package name, if it exists,
* or {@link Optional#empty()} otherwise.
*/
private Optional<ComponentName> findAdminWithPackageName(String packageName) {
List<ComponentName> admins = mDPM.getActiveAdmins();
if (admins == null) {
return Optional.empty();
}
return admins.stream().filter(i -> i.getPackageName().equals(packageName)).findAny();
}
private boolean isAdminUninstallable() {
// System apps can't be uninstalled.
return !mDeviceAdmin.getActivityInfo().applicationInfo.isSystemApp();
}
}

View File

@@ -0,0 +1,463 @@
/*
* Copyright (C) 2010 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.settings.applications.specialaccess.deviceadmin;
import android.app.Activity;
import android.app.AppGlobals;
import android.app.ListFragment;
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DeviceAdminReceiver;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.Switch;
import android.widget.TextView;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.instrumentation.Instrumentable;
import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class DeviceAdminSettings extends ListFragment implements Instrumentable {
static final String TAG = "DeviceAdminSettings";
private VisibilityLoggerMixin mVisibilityLoggerMixin;
private DevicePolicyManager mDPM;
private UserManager mUm;
private static class DeviceAdminListItem implements Comparable<DeviceAdminListItem> {
public DeviceAdminInfo info;
// These aren't updated so they keep a stable sort order if user activates / de-activates
// an admin.
public String name;
public boolean active;
public int compareTo(DeviceAdminListItem other) {
// Sort active admins first, then by name.
if (this.active != other.active) {
return this.active ? -1 : 1;
}
return this.name.compareTo(other.name);
}
}
/**
* Internal collection of device admin info objects for all profiles associated with the current
* user.
*/
private final ArrayList<DeviceAdminListItem>
mAdmins = new ArrayList<DeviceAdminListItem>();
private String mDeviceOwnerPkg;
private SparseArray<ComponentName> mProfileOwnerComponents = new SparseArray<ComponentName>();
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// Refresh the list, if state change has been received. It could be that checkboxes
// need to be updated
if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(
intent.getAction())) {
updateList();
}
}
};
@Override
public int getMetricsCategory() {
return MetricsProto.MetricsEvent.DEVICE_ADMIN_SETTINGS;
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
mVisibilityLoggerMixin = new VisibilityLoggerMixin(getMetricsCategory(),
FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider());
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mDPM = (DevicePolicyManager) getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE);
mUm = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
return inflater.inflate(R.layout.device_admin_settings, container, false);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true);
Utils.forceCustomPadding(getListView(), true /* additive padding */);
getActivity().setTitle(R.string.manage_device_admin);
}
@Override
public void onResume() {
super.onResume();
final Activity activity = getActivity();
mVisibilityLoggerMixin.onResume();
IntentFilter filter = new IntentFilter();
filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
activity.registerReceiverAsUser(
mBroadcastReceiver, UserHandle.ALL, filter, null, null);
final ComponentName deviceOwnerComponent = mDPM.getDeviceOwnerComponentOnAnyUser();
mDeviceOwnerPkg =
deviceOwnerComponent != null ? deviceOwnerComponent.getPackageName() : null;
mProfileOwnerComponents.clear();
final List<UserHandle> profiles = mUm.getUserProfiles();
final int profilesSize = profiles.size();
for (int i = 0; i < profilesSize; ++i) {
final int profileId = profiles.get(i).getIdentifier();
mProfileOwnerComponents.put(profileId, mDPM.getProfileOwnerAsUser(profileId));
}
updateList();
}
@Override
public void onPause() {
final Activity activity = getActivity();
activity.unregisterReceiver(mBroadcastReceiver);
mVisibilityLoggerMixin.onPause();
super.onPause();
}
/**
* Update the internal collection of available admins for all profiles associated with the
* current user.
*/
void updateList() {
mAdmins.clear();
final List<UserHandle> profiles = mUm.getUserProfiles();
final int profilesSize = profiles.size();
for (int i = 0; i < profilesSize; ++i) {
final int profileId = profiles.get(i).getIdentifier();
updateAvailableAdminsForProfile(profileId);
}
Collections.sort(mAdmins);
getListView().setAdapter(new PolicyListAdapter());
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
Object o = l.getAdapter().getItem(position);
DeviceAdminInfo dpi = (DeviceAdminInfo) o;
final UserHandle user = new UserHandle(getUserId(dpi));
final Activity activity = getActivity();
Intent intent = new Intent(activity, DeviceAdminAdd.class);
intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, dpi.getComponent());
activity.startActivityAsUser(intent, user);
}
static class ViewHolder {
ImageView icon;
TextView name;
Switch checkbox;
TextView description;
}
class PolicyListAdapter extends BaseAdapter {
final LayoutInflater mInflater;
PolicyListAdapter() {
mInflater = (LayoutInflater)
getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public int getCount() {
return mAdmins.size();
}
/**
* The item for the given position in the list.
*
* @return DeviceAdminInfo object for actual device admins.
*/
@Override
public Object getItem(int position) {
return ((DeviceAdminListItem) (mAdmins.get(position))).info;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public boolean areAllItemsEnabled() {
return false;
}
/**
* See {@link #getItemViewType} for the view types.
*/
@Override
public int getViewTypeCount() {
return 1;
}
/**
* Returns 0 for all types.
*/
@Override
public int getItemViewType(int position) {
return 0;
}
@Override
public boolean isEnabled(int position) {
Object o = getItem(position);
return isEnabled(o);
}
private boolean isEnabled(Object o) {
DeviceAdminInfo info = (DeviceAdminInfo) o;
// Disable item if admin is being removed
if (isRemovingAdmin(info)) {
return false;
}
return true;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Object o = getItem(position);
if (convertView == null) {
convertView = newDeviceAdminView(parent);
}
bindView(convertView, (DeviceAdminInfo) o);
return convertView;
}
private View newDeviceAdminView(ViewGroup parent) {
View v = mInflater.inflate(R.layout.device_admin_item, parent, false);
ViewHolder h = new ViewHolder();
h.icon = v.findViewById(R.id.icon);
h.name = v.findViewById(R.id.name);
h.checkbox = v.findViewById(R.id.checkbox);
h.description = v.findViewById(R.id.description);
v.setTag(h);
return v;
}
private void bindView(View view, DeviceAdminInfo item) {
final Activity activity = getActivity();
ViewHolder vh = (ViewHolder) view.getTag();
Drawable activityIcon = item.loadIcon(activity.getPackageManager());
Drawable badgedIcon = activity.getPackageManager().getUserBadgedIcon(
activityIcon, new UserHandle(getUserId(item)));
vh.icon.setImageDrawable(badgedIcon);
vh.name.setText(item.loadLabel(activity.getPackageManager()));
vh.checkbox.setChecked(isActiveAdmin(item));
final boolean enabled = isEnabled(item);
try {
vh.description.setText(item.loadDescription(activity.getPackageManager()));
} catch (Resources.NotFoundException e) {
}
vh.checkbox.setEnabled(enabled);
vh.name.setEnabled(enabled);
vh.description.setEnabled(enabled);
vh.icon.setEnabled(enabled);
}
}
private boolean isDeviceOwner(DeviceAdminInfo item) {
return getUserId(item) == UserHandle.myUserId()
&& item.getPackageName().equals(mDeviceOwnerPkg);
}
private boolean isProfileOwner(DeviceAdminInfo item) {
ComponentName profileOwner = mProfileOwnerComponents.get(getUserId(item));
return item.getComponent().equals(profileOwner);
}
private boolean isActiveAdmin(DeviceAdminInfo item) {
return mDPM.isAdminActiveAsUser(item.getComponent(), getUserId(item));
}
private boolean isRemovingAdmin(DeviceAdminInfo item) {
return mDPM.isRemovingAdmin(item.getComponent(), getUserId(item));
}
/**
* Add device admins to the internal collection that belong to a profile.
*
* @param profileId the profile identifier.
*/
private void updateAvailableAdminsForProfile(final int profileId) {
// We are adding the union of two sets 'A' and 'B' of device admins to mAvailableAdmins.
// Set 'A' is the set of active admins for the profile whereas set 'B' is the set of
// listeners to DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED for the profile.
// Add all of set 'A' to mAvailableAdmins.
List<ComponentName> activeAdminsListForProfile = mDPM.getActiveAdminsAsUser(profileId);
addActiveAdminsForProfile(activeAdminsListForProfile, profileId);
// Collect set 'B' and add B-A to mAvailableAdmins.
addDeviceAdminBroadcastReceiversForProfile(activeAdminsListForProfile, profileId);
}
/**
* Add a profile's device admins that are receivers of
* {@code DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED} to the internal collection if they
* haven't been added yet.
*
* @param alreadyAddedComponents the set of active admin component names. Receivers of
* {@code DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED} whose component is in this
* set are not added to the internal collection again.
* @param profileId the identifier of the profile
*/
private void addDeviceAdminBroadcastReceiversForProfile(
Collection<ComponentName> alreadyAddedComponents, final int profileId) {
final PackageManager pm = getActivity().getPackageManager();
List<ResolveInfo> enabledForProfile = pm.queryBroadcastReceiversAsUser(
new Intent(DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED),
PackageManager.GET_META_DATA | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
profileId);
if (enabledForProfile == null) {
enabledForProfile = Collections.emptyList();
}
final int n = enabledForProfile.size();
for (int i = 0; i < n; ++i) {
ResolveInfo resolveInfo = enabledForProfile.get(i);
ComponentName riComponentName =
new ComponentName(resolveInfo.activityInfo.packageName,
resolveInfo.activityInfo.name);
if (alreadyAddedComponents == null
|| !alreadyAddedComponents.contains(riComponentName)) {
DeviceAdminInfo deviceAdminInfo = createDeviceAdminInfo(resolveInfo.activityInfo);
// add only visible ones (note: active admins are added regardless of visibility)
if (deviceAdminInfo != null && deviceAdminInfo.isVisible()) {
if (!deviceAdminInfo.getActivityInfo().applicationInfo.isInternal()) {
continue;
}
DeviceAdminListItem item = new DeviceAdminListItem();
item.info = deviceAdminInfo;
item.name = deviceAdminInfo.loadLabel(pm).toString();
// Active ones already added.
item.active = false;
mAdmins.add(item);
}
}
}
}
/**
* Add a {@link DeviceAdminInfo} object to the internal collection of available admins for all
* active admin components associated with a profile.
*
* @param profileId a profile identifier.
*/
private void addActiveAdminsForProfile(final List<ComponentName> activeAdmins,
final int profileId) {
if (activeAdmins != null) {
final PackageManager packageManager = getActivity().getPackageManager();
final IPackageManager iPackageManager = AppGlobals.getPackageManager();
final int n = activeAdmins.size();
for (int i = 0; i < n; ++i) {
final ComponentName activeAdmin = activeAdmins.get(i);
final ActivityInfo ai;
try {
ai = iPackageManager.getReceiverInfo(activeAdmin,
PackageManager.GET_META_DATA |
PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS |
PackageManager.MATCH_DIRECT_BOOT_UNAWARE |
PackageManager.MATCH_DIRECT_BOOT_AWARE, profileId);
} catch (RemoteException e) {
Log.w(TAG, "Unable to load component: " + activeAdmin);
continue;
}
final DeviceAdminInfo deviceAdminInfo = createDeviceAdminInfo(ai);
if (deviceAdminInfo == null) {
continue;
}
// Don't do the applicationInfo.isInternal() check here; if an active
// admin is already on SD card, just show it.
final DeviceAdminListItem item = new DeviceAdminListItem();
item.info = deviceAdminInfo;
item.name = deviceAdminInfo.loadLabel(packageManager).toString();
item.active = true;
mAdmins.add(item);
}
}
}
/**
* Creates a device admin info object for the resolved intent that points to the component of
* the device admin.
*
* @param ai ActivityInfo for the admin component.
* @return new {@link DeviceAdminInfo} object or null if there was an error.
*/
private DeviceAdminInfo createDeviceAdminInfo(ActivityInfo ai) {
try {
return new DeviceAdminInfo(getActivity(), ai);
} catch (XmlPullParserException|IOException e) {
Log.w(TAG, "Skipping " + ai, e);
}
return null;
}
/**
* Extracts the user id from a device admin info object.
* @param adminInfo the device administrator info.
* @return identifier of the user associated with the device admin.
*/
private int getUserId(DeviceAdminInfo adminInfo) {
return UserHandle.getUserId(adminInfo.getActivityInfo().applicationInfo.uid);
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.settings.applications.specialaccess;
package com.android.settings.applications.specialaccess.deviceadmin;
import android.content.Context;