1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings; 18 19 import org.xmlpull.v1.XmlPullParserException; 20 21 import android.app.Activity; 22 import android.app.AlertDialog; 23 import android.app.ListFragment; 24 import android.app.admin.DeviceAdminInfo; 25 import android.app.admin.DeviceAdminReceiver; 26 import android.app.admin.DevicePolicyManager; 27 import android.content.BroadcastReceiver; 28 import android.content.ComponentName; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.IntentFilter; 32 import android.content.pm.PackageManager; 33 import android.content.pm.ResolveInfo; 34 import android.content.res.Resources; 35 import android.content.res.TypedArray; 36 import android.graphics.drawable.Drawable; 37 import android.os.Bundle; 38 import android.os.UserHandle; 39 import android.os.UserManager; 40 import android.util.Log; 41 import android.util.SparseArray; 42 import android.view.LayoutInflater; 43 import android.view.View; 44 import android.view.ViewGroup; 45 import android.widget.BaseAdapter; 46 import android.widget.CheckBox; 47 import android.widget.ImageView; 48 import android.widget.ListView; 49 import android.widget.TextView; 50 51 import java.io.IOException; 52 import java.util.ArrayList; 53 import java.util.Collection; 54 import java.util.Collections; 55 import java.util.List; 56 57 public class DeviceAdminSettings extends ListFragment { 58 static final String TAG = "DeviceAdminSettings"; 59 60 private DevicePolicyManager mDPM; 61 private UserManager mUm; 62 63 /** 64 * Internal collection of device admin info objects for all profiles associated with the current 65 * user. 66 */ 67 private final SparseArray<ArrayList<DeviceAdminInfo>> 68 mAdminsByProfile = new SparseArray<ArrayList<DeviceAdminInfo>>(); 69 70 private String mDeviceOwnerPkg; 71 private SparseArray<ComponentName> mProfileOwnerComponents = new SparseArray<ComponentName>(); 72 73 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 74 @Override 75 public void onReceive(Context context, Intent intent) { 76 // Refresh the list, if state change has been received. It could be that checkboxes 77 // need to be updated 78 if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals( 79 intent.getAction())) { 80 updateList(); 81 } 82 } 83 }; 84 85 @Override 86 public void onCreate(Bundle icicle) { 87 super.onCreate(icicle); 88 } 89 90 @Override 91 public View onCreateView(LayoutInflater inflater, ViewGroup container, 92 Bundle savedInstanceState) { 93 mDPM = (DevicePolicyManager) getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE); 94 mUm = (UserManager) getActivity().getSystemService(Context.USER_SERVICE); 95 return inflater.inflate(R.layout.device_admin_settings, container, false); 96 } 97 98 @Override 99 public void onActivityCreated(Bundle savedInstanceState) { 100 super.onActivityCreated(savedInstanceState); 101 102 Utils.forceCustomPadding(getListView(), true /* additive padding */); 103 } 104 105 @Override 106 public void onResume() { 107 super.onResume(); 108 IntentFilter filter = new IntentFilter(); 109 filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); 110 getActivity().registerReceiverAsUser( 111 mBroadcastReceiver, UserHandle.ALL, filter, null, null); 112 mDeviceOwnerPkg = mDPM.getDeviceOwner(); 113 if (mDeviceOwnerPkg != null && !mDPM.isDeviceOwner(mDeviceOwnerPkg)) { 114 mDeviceOwnerPkg = null; 115 } 116 mProfileOwnerComponents.clear(); 117 final List<UserHandle> profiles = mUm.getUserProfiles(); 118 final int profilesSize = profiles.size(); 119 for (int i = 0; i < profilesSize; ++i) { 120 final int profileId = profiles.get(i).getIdentifier(); 121 mProfileOwnerComponents.put(profileId, mDPM.getProfileOwnerAsUser(profileId)); 122 } 123 updateList(); 124 } 125 126 @Override 127 public void onPause() { 128 getActivity().unregisterReceiver(mBroadcastReceiver); 129 super.onPause(); 130 } 131 132 /** 133 * Update the internal collection of available admins for all profiles associated with the 134 * current user. 135 */ 136 void updateList() { 137 mAdminsByProfile.clear(); 138 139 final List<UserHandle> profiles = mUm.getUserProfiles(); 140 final int profilesSize = profiles.size(); 141 for (int i = 0; i < profilesSize; ++i) { 142 final int profileId = profiles.get(i).getIdentifier(); 143 updateAvailableAdminsForProfile(profileId); 144 } 145 146 getListView().setAdapter(new PolicyListAdapter()); 147 } 148 149 @Override 150 public void onListItemClick(ListView l, View v, int position, long id) { 151 Object o = l.getAdapter().getItem(position); 152 if (!(o instanceof DeviceAdminInfo)) { 153 // race conditions may cause this 154 return; 155 } 156 DeviceAdminInfo dpi = (DeviceAdminInfo) o; 157 final Activity activity = getActivity(); 158 final int userId = getUserId(dpi); 159 if (userId == UserHandle.myUserId() || !isProfileOwner(dpi)) { 160 Intent intent = new Intent(); 161 intent.setClass(activity, DeviceAdminAdd.class); 162 intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, dpi.getComponent()); 163 activity.startActivityAsUser(intent, new UserHandle(userId)); 164 } else { 165 AlertDialog.Builder builder = new AlertDialog.Builder(activity); 166 builder.setMessage(getString(R.string.managed_profile_device_admin_info, 167 dpi.loadLabel(activity.getPackageManager()))); 168 builder.setPositiveButton(android.R.string.ok, null); 169 builder.create().show(); 170 } 171 } 172 173 static class ViewHolder { 174 ImageView icon; 175 TextView name; 176 CheckBox checkbox; 177 TextView description; 178 } 179 180 class PolicyListAdapter extends BaseAdapter { 181 final LayoutInflater mInflater; 182 183 PolicyListAdapter() { 184 mInflater = (LayoutInflater) 185 getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); 186 } 187 188 @Override 189 public boolean hasStableIds() { 190 return false; 191 } 192 193 @Override 194 public int getCount() { 195 int adminCount = 0; 196 final int profileCount = mAdminsByProfile.size(); 197 for (int i = 0; i < profileCount; ++i) { 198 adminCount += mAdminsByProfile.valueAt(i).size(); 199 } 200 // Add 'profileCount' for title items. 201 return adminCount + profileCount; 202 } 203 204 /** 205 * The item for the given position in the list. 206 * 207 * @return a String object for title items and a DeviceAdminInfo object for actual device 208 * admins. 209 */ 210 @Override 211 public Object getItem(int position) { 212 if (position < 0) { 213 throw new ArrayIndexOutOfBoundsException(); 214 } 215 // The position of the item in the list of admins. 216 // We start from the given position and discount the length of the upper lists until we 217 // get the one for the right profile 218 int adminPosition = position; 219 final int n = mAdminsByProfile.size(); 220 int i = 0; 221 for (; i < n; ++i) { 222 // The elements in that section including the title item (that's why adding one). 223 final int listSize = mAdminsByProfile.valueAt(i).size() + 1; 224 if (adminPosition < listSize) { 225 break; 226 } 227 adminPosition -= listSize; 228 } 229 if (i == n) { 230 throw new ArrayIndexOutOfBoundsException(); 231 } 232 // If countdown == 0 that means the title item 233 if (adminPosition == 0) { 234 Resources res = getActivity().getResources(); 235 if (mAdminsByProfile.keyAt(i) == UserHandle.myUserId()) { 236 return res.getString(R.string.personal_device_admin_title); 237 } else { 238 return res.getString(R.string.managed_device_admin_title); 239 } 240 } else { 241 // Subtracting one for the title. 242 return mAdminsByProfile.valueAt(i).get(adminPosition - 1); 243 } 244 } 245 246 @Override 247 public long getItemId(int position) { 248 return position; 249 } 250 251 @Override 252 public boolean areAllItemsEnabled() { 253 return false; 254 } 255 256 /** 257 * See {@link #getItemViewType} for the view types. 258 */ 259 @Override 260 public int getViewTypeCount() { 261 return 2; 262 } 263 264 /** 265 * Returns 1 for title items and 0 for anything else. 266 */ 267 @Override 268 public int getItemViewType(int position) { 269 Object o = getItem(position); 270 return (o instanceof String) ? 1 : 0; 271 } 272 273 @Override 274 public boolean isEnabled(int position) { 275 Object o = getItem(position); 276 return isEnabled(o); 277 } 278 279 private boolean isEnabled(Object o) { 280 if (!(o instanceof DeviceAdminInfo)) { 281 // Title item 282 return false; 283 } 284 DeviceAdminInfo info = (DeviceAdminInfo) o; 285 if (isActiveAdmin(info) && getUserId(info) == UserHandle.myUserId() 286 && (isDeviceOwner(info) || isProfileOwner(info))) { 287 return false; 288 } 289 // Disable item if admin is being removed 290 if (isRemovingAdmin(info)) { 291 return false; 292 } 293 return true; 294 } 295 296 @Override 297 public View getView(int position, View convertView, ViewGroup parent) { 298 Object o = getItem(position); 299 if (o instanceof DeviceAdminInfo) { 300 if (convertView == null) { 301 convertView = newDeviceAdminView(parent); 302 } 303 bindView(convertView, (DeviceAdminInfo) o); 304 } else { 305 if (convertView == null) { 306 convertView = newTitleView(parent); 307 } 308 final TextView title = (TextView) convertView.findViewById(android.R.id.title); 309 title.setText((String)o); 310 } 311 return convertView; 312 } 313 314 private View newDeviceAdminView(ViewGroup parent) { 315 View v = mInflater.inflate(R.layout.device_admin_item, parent, false); 316 ViewHolder h = new ViewHolder(); 317 h.icon = (ImageView)v.findViewById(R.id.icon); 318 h.name = (TextView)v.findViewById(R.id.name); 319 h.checkbox = (CheckBox)v.findViewById(R.id.checkbox); 320 h.description = (TextView)v.findViewById(R.id.description); 321 v.setTag(h); 322 return v; 323 } 324 325 private View newTitleView(ViewGroup parent) { 326 final TypedArray a = mInflater.getContext().obtainStyledAttributes(null, 327 com.android.internal.R.styleable.Preference, 328 com.android.internal.R.attr.preferenceCategoryStyle, 0); 329 final int resId = a.getResourceId(com.android.internal.R.styleable.Preference_layout, 330 0); 331 return mInflater.inflate(resId, parent, false); 332 } 333 334 private void bindView(View view, DeviceAdminInfo item) { 335 final Activity activity = getActivity(); 336 ViewHolder vh = (ViewHolder) view.getTag(); 337 Drawable activityIcon = item.loadIcon(activity.getPackageManager()); 338 Drawable badgedIcon = activity.getPackageManager().getUserBadgedIcon( 339 activityIcon, new UserHandle(getUserId(item))); 340 vh.icon.setImageDrawable(badgedIcon); 341 vh.name.setText(item.loadLabel(activity.getPackageManager())); 342 vh.checkbox.setChecked(isActiveAdmin(item)); 343 final boolean enabled = isEnabled(item); 344 try { 345 vh.description.setText(item.loadDescription(activity.getPackageManager())); 346 } catch (Resources.NotFoundException e) { 347 } 348 vh.checkbox.setEnabled(enabled); 349 vh.name.setEnabled(enabled); 350 vh.description.setEnabled(enabled); 351 vh.icon.setEnabled(enabled); 352 } 353 } 354 355 private boolean isDeviceOwner(DeviceAdminInfo item) { 356 return getUserId(item) == UserHandle.myUserId() 357 && item.getPackageName().equals(mDeviceOwnerPkg); 358 } 359 360 private boolean isProfileOwner(DeviceAdminInfo item) { 361 ComponentName profileOwner = mProfileOwnerComponents.get(getUserId(item)); 362 return item.getComponent().equals(profileOwner); 363 } 364 365 private boolean isActiveAdmin(DeviceAdminInfo item) { 366 return mDPM.isAdminActiveAsUser(item.getComponent(), getUserId(item)); 367 } 368 369 private boolean isRemovingAdmin(DeviceAdminInfo item) { 370 return mDPM.isRemovingAdmin(item.getComponent(), getUserId(item)); 371 } 372 373 /** 374 * Add device admins to the internal collection that belong to a profile. 375 * 376 * @param profileId the profile identifier. 377 */ 378 private void updateAvailableAdminsForProfile(final int profileId) { 379 // We are adding the union of two sets 'A' and 'B' of device admins to mAvailableAdmins. 380 // Set 'A' is the set of active admins for the profile whereas set 'B' is the set of 381 // listeners to DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED for the profile. 382 383 // Add all of set 'A' to mAvailableAdmins. 384 List<ComponentName> activeAdminsListForProfile = mDPM.getActiveAdminsAsUser(profileId); 385 addActiveAdminsForProfile(activeAdminsListForProfile, profileId); 386 387 // Collect set 'B' and add B-A to mAvailableAdmins. 388 addDeviceAdminBroadcastReceiversForProfile(activeAdminsListForProfile, profileId); 389 } 390 391 /** 392 * Add a profile's device admins that are receivers of 393 * {@code DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED} to the internal collection if they 394 * haven't been added yet. 395 * 396 * @param alreadyAddedComponents the set of active admin component names. Receivers of 397 * {@code DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED} whose component is in this 398 * set are not added to the internal collection again. 399 * @param profileId the identifier of the profile 400 */ 401 private void addDeviceAdminBroadcastReceiversForProfile( 402 Collection<ComponentName> alreadyAddedComponents, final int profileId) { 403 final PackageManager pm = getActivity().getPackageManager(); 404 List<ResolveInfo> enabledForProfile = pm.queryBroadcastReceivers( 405 new Intent(DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED), 406 PackageManager.GET_META_DATA | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, 407 profileId); 408 if (enabledForProfile == null) { 409 enabledForProfile = Collections.emptyList(); 410 } 411 final int n = enabledForProfile.size(); 412 ArrayList<DeviceAdminInfo> deviceAdmins = mAdminsByProfile.get(profileId); 413 if (deviceAdmins == null) { 414 deviceAdmins = new ArrayList<DeviceAdminInfo>(n); 415 } 416 for (int i = 0; i < n; ++i) { 417 ResolveInfo resolveInfo = enabledForProfile.get(i); 418 ComponentName riComponentName = 419 new ComponentName(resolveInfo.activityInfo.packageName, 420 resolveInfo.activityInfo.name); 421 if (alreadyAddedComponents == null 422 || !alreadyAddedComponents.contains(riComponentName)) { 423 DeviceAdminInfo deviceAdminInfo = createDeviceAdminInfo(resolveInfo); 424 // add only visible ones (note: active admins are added regardless of visibility) 425 if (deviceAdminInfo != null && deviceAdminInfo.isVisible()) { 426 deviceAdmins.add(deviceAdminInfo); 427 } 428 } 429 } 430 if (!deviceAdmins.isEmpty()) { 431 mAdminsByProfile.put(profileId, deviceAdmins); 432 } 433 } 434 435 /** 436 * Add a {@link DeviceAdminInfo} object to the internal collection of available admins for all 437 * active admin components associated with a profile. 438 * 439 * @param profileId a profile identifier. 440 */ 441 private void addActiveAdminsForProfile(final List<ComponentName> activeAdmins, 442 final int profileId) { 443 if (activeAdmins != null) { 444 final PackageManager packageManager = getActivity().getPackageManager(); 445 final int n = activeAdmins.size(); 446 ArrayList<DeviceAdminInfo> deviceAdmins = new ArrayList<DeviceAdminInfo>(n); 447 for (int i = 0; i < n; ++i) { 448 ComponentName activeAdmin = activeAdmins.get(i); 449 List<ResolveInfo> resolved = packageManager.queryBroadcastReceivers( 450 new Intent().setComponent(activeAdmin), PackageManager.GET_META_DATA 451 | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, profileId); 452 if (resolved != null) { 453 final int resolvedMax = resolved.size(); 454 for (int j = 0; j < resolvedMax; ++j) { 455 DeviceAdminInfo deviceAdminInfo = createDeviceAdminInfo(resolved.get(j)); 456 if (deviceAdminInfo != null) { 457 deviceAdmins.add(deviceAdminInfo); 458 } 459 } 460 } 461 } 462 if (!deviceAdmins.isEmpty()) { 463 mAdminsByProfile.put(profileId, deviceAdmins); 464 } 465 } 466 } 467 468 /** 469 * Creates a device admin info object for the resolved intent that points to the component of 470 * the device admin. 471 * 472 * @param resolved resolved intent. 473 * @return new {@link DeviceAdminInfo} object or null if there was an error. 474 */ 475 private DeviceAdminInfo createDeviceAdminInfo(ResolveInfo resolved) { 476 try { 477 return new DeviceAdminInfo(getActivity(), resolved); 478 } catch (XmlPullParserException e) { 479 Log.w(TAG, "Skipping " + resolved.activityInfo, e); 480 } catch (IOException e) { 481 Log.w(TAG, "Skipping " + resolved.activityInfo, e); 482 } 483 return null; 484 } 485 486 /** 487 * Extracts the user id from a device admin info object. 488 * @param adminInfo the device administrator info. 489 * @return identifier of the user associated with the device admin. 490 */ 491 private int getUserId(DeviceAdminInfo adminInfo) { 492 return UserHandle.getUserId(adminInfo.getActivityInfo().applicationInfo.uid); 493 } 494 } 495