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