1 /* 2 * Copyright (C) 2014 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.example.android.apprestrictionenforcer; 18 19 import android.app.Activity; 20 import android.app.admin.DevicePolicyManager; 21 import android.content.Context; 22 import android.content.RestrictionEntry; 23 import android.content.RestrictionsManager; 24 import android.content.SharedPreferences; 25 import android.os.Build; 26 import android.os.Bundle; 27 import android.os.Parcelable; 28 import android.support.annotation.NonNull; 29 import android.support.annotation.Nullable; 30 import android.support.v4.app.Fragment; 31 import android.text.Editable; 32 import android.text.TextUtils; 33 import android.text.TextWatcher; 34 import android.view.LayoutInflater; 35 import android.view.View; 36 import android.view.ViewGroup; 37 import android.widget.AdapterView; 38 import android.widget.ArrayAdapter; 39 import android.widget.Button; 40 import android.widget.CompoundButton; 41 import android.widget.EditText; 42 import android.widget.LinearLayout; 43 import android.widget.Spinner; 44 import android.widget.Switch; 45 import android.widget.TextView; 46 import android.widget.Toast; 47 48 import java.util.ArrayList; 49 import java.util.Arrays; 50 import java.util.HashMap; 51 import java.util.List; 52 import java.util.Map; 53 54 /** 55 * This fragment provides UI and functionality to set restrictions on the AppRestrictionSchema 56 * sample. 57 */ 58 public class AppRestrictionEnforcerFragment extends Fragment implements 59 CompoundButton.OnCheckedChangeListener, AdapterView.OnItemSelectedListener, 60 View.OnClickListener, ItemAddFragment.OnItemAddedListener { 61 62 /** 63 * Key for {@link SharedPreferences} 64 */ 65 private static final String PREFS_KEY = "AppRestrictionEnforcerFragment"; 66 67 /** 68 * Key for the boolean restriction in AppRestrictionSchema. 69 */ 70 private static final String RESTRICTION_KEY_SAY_HELLO = "can_say_hello"; 71 72 /** 73 * Key for the string restriction in AppRestrictionSchema. 74 */ 75 private static final String RESTRICTION_KEY_MESSAGE = "message"; 76 77 /** 78 * Key for the integer restriction in AppRestrictionSchema. 79 */ 80 private static final String RESTRICTION_KEY_NUMBER = "number"; 81 82 /** 83 * Key for the choice restriction in AppRestrictionSchema. 84 */ 85 private static final String RESTRICTION_KEY_RANK = "rank"; 86 87 /** 88 * Key for the multi-select restriction in AppRestrictionSchema. 89 */ 90 private static final String RESTRICTION_KEY_APPROVALS = "approvals"; 91 92 /** 93 * Key for the bundle array restriction in AppRestrictionSchema. 94 */ 95 private static final String RESTRICTION_KEY_ITEMS = "items"; 96 private static final String RESTRICTION_KEY_ITEM_KEY = "key"; 97 private static final String RESTRICTION_KEY_ITEM_VALUE = "value"; 98 99 private static final String DELIMETER = ","; 100 private static final String SEPARATOR = ":"; 101 102 private static final boolean BUNDLE_SUPPORTED = Build.VERSION.SDK_INT >= 23; 103 104 /** 105 * Current status of the restrictions. 106 */ 107 private Bundle mCurrentRestrictions = new Bundle(); 108 109 // UI Components 110 private Switch mSwitchSayHello; 111 private EditText mEditMessage; 112 private EditText mEditNumber; 113 private Spinner mSpinnerRank; 114 private LinearLayout mLayoutApprovals; 115 private LinearLayout mLayoutItems; 116 117 @Override onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)118 public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, 119 @Nullable Bundle savedInstanceState) { 120 return inflater.inflate(R.layout.fragment_app_restriction_enforcer, container, false); 121 } 122 123 @Override onViewCreated(View view, @Nullable Bundle savedInstanceState)124 public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 125 // Retain references for the UI elements 126 mSwitchSayHello = (Switch) view.findViewById(R.id.say_hello); 127 mEditMessage = (EditText) view.findViewById(R.id.message); 128 mEditNumber = (EditText) view.findViewById(R.id.number); 129 mSpinnerRank = (Spinner) view.findViewById(R.id.rank); 130 mLayoutApprovals = (LinearLayout) view.findViewById(R.id.approvals); 131 mLayoutItems = (LinearLayout) view.findViewById(R.id.items); 132 view.findViewById(R.id.item_add).setOnClickListener(this); 133 View bundleArrayLayout = view.findViewById(R.id.bundle_array_layout); 134 if (BUNDLE_SUPPORTED) { 135 bundleArrayLayout.setVisibility(View.VISIBLE); 136 } else { 137 bundleArrayLayout.setVisibility(View.GONE); 138 } 139 } 140 141 @Override onResume()142 public void onResume() { 143 super.onResume(); 144 loadRestrictions(getActivity()); 145 } 146 147 @Override onCheckedChanged(CompoundButton compoundButton, boolean checked)148 public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { 149 switch (compoundButton.getId()) { 150 case R.id.say_hello: { 151 saveCanSayHello(getActivity(), checked); 152 break; 153 } 154 case R.id.approval: { 155 if (checked) { 156 addApproval(getActivity(), (String) compoundButton.getTag()); 157 } else { 158 removeApproval(getActivity(), (String) compoundButton.getTag()); 159 } 160 break; 161 } 162 } 163 } 164 165 private TextWatcher mWatcherMessage = new EasyTextWatcher() { 166 @Override 167 public void afterTextChanged(Editable s) { 168 saveMessage(getActivity(), s.toString()); 169 } 170 }; 171 172 private TextWatcher mWatcherNumber = new EasyTextWatcher() { 173 @Override 174 public void afterTextChanged(Editable s) { 175 try { 176 String string = s.toString(); 177 if (!TextUtils.isEmpty(string)) { 178 saveNumber(getActivity(), Integer.parseInt(string)); 179 } 180 } catch (NumberFormatException e) { 181 Toast.makeText(getActivity(), "Not an integer!", Toast.LENGTH_SHORT).show(); 182 } 183 } 184 }; 185 186 @Override onItemSelected(AdapterView<?> parent, View view, int position, long id)187 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { 188 switch (parent.getId()) { 189 case R.id.rank: { 190 saveRank(getActivity(), (String) parent.getAdapter().getItem(position)); 191 break; 192 } 193 } 194 } 195 196 @Override onNothingSelected(AdapterView<?> parent)197 public void onNothingSelected(AdapterView<?> parent) { 198 // Nothing to do 199 } 200 201 @Override onClick(View v)202 public void onClick(View v) { 203 switch (v.getId()) { 204 case R.id.item_add: 205 new ItemAddFragment().show(getChildFragmentManager(), "dialog"); 206 break; 207 case R.id.item_remove: 208 String key = (String) v.getTag(); 209 removeItem(key); 210 mLayoutItems.removeView((View) v.getParent()); 211 break; 212 } 213 } 214 215 @Override onItemAdded(String key, String value)216 public void onItemAdded(String key, String value) { 217 key = TextUtils.replace(key, 218 new String[]{DELIMETER, SEPARATOR}, new String[]{"", ""}).toString(); 219 value = TextUtils.replace(value, 220 new String[]{DELIMETER, SEPARATOR}, new String[]{"", ""}).toString(); 221 Parcelable[] parcelables = mCurrentRestrictions.getParcelableArray(RESTRICTION_KEY_ITEMS); 222 Map<String, String> items = new HashMap<>(); 223 if (parcelables != null) { 224 for (Parcelable parcelable : parcelables) { 225 Bundle bundle = (Bundle) parcelable; 226 items.put(bundle.getString(RESTRICTION_KEY_ITEM_KEY), 227 bundle.getString(RESTRICTION_KEY_ITEM_VALUE)); 228 } 229 } 230 items.put(key, value); 231 insertItemRow(LayoutInflater.from(getActivity()), key, value); 232 saveItems(getActivity(), items); 233 } 234 235 /** 236 * Loads the restrictions for the AppRestrictionSchema sample. 237 * 238 * @param activity The activity 239 */ loadRestrictions(Activity activity)240 private void loadRestrictions(Activity activity) { 241 RestrictionsManager manager = 242 (RestrictionsManager) activity.getSystemService(Context.RESTRICTIONS_SERVICE); 243 List<RestrictionEntry> restrictions = 244 manager.getManifestRestrictions(Constants.PACKAGE_NAME_APP_RESTRICTION_SCHEMA); 245 SharedPreferences prefs = activity.getSharedPreferences(PREFS_KEY, Context.MODE_PRIVATE); 246 for (RestrictionEntry restriction : restrictions) { 247 String key = restriction.getKey(); 248 if (RESTRICTION_KEY_SAY_HELLO.equals(key)) { 249 updateCanSayHello(prefs.getBoolean(RESTRICTION_KEY_SAY_HELLO, 250 restriction.getSelectedState())); 251 } else if (RESTRICTION_KEY_MESSAGE.equals(key)) { 252 updateMessage(prefs.getString(RESTRICTION_KEY_MESSAGE, 253 restriction.getSelectedString())); 254 } else if (RESTRICTION_KEY_NUMBER.equals(key)) { 255 updateNumber(prefs.getInt(RESTRICTION_KEY_NUMBER, 256 restriction.getIntValue())); 257 } else if (RESTRICTION_KEY_RANK.equals(key)) { 258 updateRank(activity, restriction.getChoiceValues(), 259 prefs.getString(RESTRICTION_KEY_RANK, restriction.getSelectedString())); 260 } else if (RESTRICTION_KEY_APPROVALS.equals(key)) { 261 updateApprovals(activity, restriction.getChoiceValues(), 262 TextUtils.split(prefs.getString(RESTRICTION_KEY_APPROVALS, 263 TextUtils.join(DELIMETER, 264 restriction.getAllSelectedStrings())), 265 DELIMETER)); 266 } else if (BUNDLE_SUPPORTED && RESTRICTION_KEY_ITEMS.equals(key)) { 267 String itemsString = prefs.getString(RESTRICTION_KEY_ITEMS, ""); 268 HashMap<String, String> items = new HashMap<>(); 269 for (String itemString : TextUtils.split(itemsString, DELIMETER)) { 270 String[] strings = itemString.split(SEPARATOR, 2); 271 items.put(strings[0], strings[1]); 272 } 273 updateItems(activity, items); 274 } 275 } 276 } 277 updateCanSayHello(boolean canSayHello)278 private void updateCanSayHello(boolean canSayHello) { 279 mCurrentRestrictions.putBoolean(RESTRICTION_KEY_SAY_HELLO, canSayHello); 280 mSwitchSayHello.setOnCheckedChangeListener(null); 281 mSwitchSayHello.setChecked(canSayHello); 282 mSwitchSayHello.setOnCheckedChangeListener(this); 283 } 284 updateMessage(String message)285 private void updateMessage(String message) { 286 mCurrentRestrictions.putString(RESTRICTION_KEY_MESSAGE, message); 287 mEditMessage.removeTextChangedListener(mWatcherMessage); 288 mEditMessage.setText(message); 289 mEditMessage.addTextChangedListener(mWatcherMessage); 290 } 291 updateNumber(int number)292 private void updateNumber(int number) { 293 mCurrentRestrictions.putInt(RESTRICTION_KEY_NUMBER, number); 294 mEditNumber.removeTextChangedListener(mWatcherNumber); 295 mEditNumber.setText(String.valueOf(number)); 296 mEditNumber.addTextChangedListener(mWatcherNumber); 297 } 298 updateRank(Context context, String[] ranks, String selectedRank)299 private void updateRank(Context context, String[] ranks, String selectedRank) { 300 mCurrentRestrictions.putString(RESTRICTION_KEY_RANK, selectedRank); 301 mSpinnerRank.setAdapter(new ArrayAdapter<>(context, 302 android.R.layout.simple_spinner_dropdown_item, ranks)); 303 mSpinnerRank.setSelection(search(ranks, selectedRank)); 304 mSpinnerRank.setOnItemSelectedListener(this); 305 } 306 updateApprovals(Context context, String[] approvals, String[] selectedApprovals)307 private void updateApprovals(Context context, String[] approvals, 308 String[] selectedApprovals) { 309 mCurrentRestrictions.putStringArray(RESTRICTION_KEY_APPROVALS, selectedApprovals); 310 mLayoutApprovals.removeAllViews(); 311 for (String approval : approvals) { 312 Switch sw = new Switch(context); 313 sw.setText(approval); 314 sw.setTag(approval); 315 sw.setChecked(Arrays.asList(selectedApprovals).contains(approval)); 316 sw.setOnCheckedChangeListener(this); 317 sw.setId(R.id.approval); 318 mLayoutApprovals.addView(sw); 319 } 320 } 321 updateItems(Context context, Map<String, String> items)322 private void updateItems(Context context, Map<String, String> items) { 323 if (!BUNDLE_SUPPORTED) { 324 return; 325 } 326 mCurrentRestrictions.putParcelableArray(RESTRICTION_KEY_ITEMS, convertToBundles(items)); 327 LayoutInflater inflater = LayoutInflater.from(context); 328 mLayoutItems.removeAllViews(); 329 for (String key : items.keySet()) { 330 insertItemRow(inflater, key, items.get(key)); 331 } 332 } 333 insertItemRow(LayoutInflater inflater, String key, String value)334 private void insertItemRow(LayoutInflater inflater, String key, String value) { 335 View view = inflater.inflate(R.layout.item, mLayoutItems, false); 336 TextView textView = (TextView) view.findViewById(R.id.item_text); 337 textView.setText(getString(R.string.item, key, value)); 338 Button remove = (Button) view.findViewById(R.id.item_remove); 339 remove.setTag(key); 340 remove.setOnClickListener(this); 341 mLayoutItems.addView(view); 342 } 343 344 @NonNull convertToBundles(Map<String, String> items)345 private Bundle[] convertToBundles(Map<String, String> items) { 346 Bundle[] bundles = new Bundle[items.size()]; 347 int i = 0; 348 for (String key : items.keySet()) { 349 Bundle bundle = new Bundle(); 350 bundle.putString(RESTRICTION_KEY_ITEM_KEY, key); 351 bundle.putString(RESTRICTION_KEY_ITEM_VALUE, items.get(key)); 352 bundles[i++] = bundle; 353 } 354 return bundles; 355 } 356 removeItem(String key)357 private void removeItem(String key) { 358 Parcelable[] parcelables = mCurrentRestrictions.getParcelableArray(RESTRICTION_KEY_ITEMS); 359 if (parcelables != null) { 360 Map<String, String> items = new HashMap<>(); 361 for (Parcelable parcelable : parcelables) { 362 Bundle bundle = (Bundle) parcelable; 363 if (!key.equals(bundle.getString(RESTRICTION_KEY_ITEM_KEY))) { 364 items.put(bundle.getString(RESTRICTION_KEY_ITEM_KEY), 365 bundle.getString(RESTRICTION_KEY_ITEM_VALUE)); 366 } 367 } 368 saveItems(getActivity(), items); 369 } 370 } 371 372 /** 373 * Saves the value for the "cay_say_hello" restriction of AppRestrictionSchema. 374 * 375 * @param activity The activity 376 * @param allow The value to be set for the restriction. 377 */ saveCanSayHello(Activity activity, boolean allow)378 private void saveCanSayHello(Activity activity, boolean allow) { 379 mCurrentRestrictions.putBoolean(RESTRICTION_KEY_SAY_HELLO, allow); 380 saveRestrictions(activity); 381 // Note that the owner app needs to remember the restrictions on its own. 382 editPreferences(activity).putBoolean(RESTRICTION_KEY_SAY_HELLO, allow).apply(); 383 } 384 385 /** 386 * Saves the value for the "message" restriction of AppRestrictionSchema. 387 * 388 * @param activity The activity 389 * @param message The value to be set for the restriction. 390 */ saveMessage(Activity activity, String message)391 private void saveMessage(Activity activity, String message) { 392 mCurrentRestrictions.putString(RESTRICTION_KEY_MESSAGE, message); 393 saveRestrictions(activity); 394 editPreferences(activity).putString(RESTRICTION_KEY_MESSAGE, message).apply(); 395 } 396 397 /** 398 * Saves the value for the "number" restriction of AppRestrictionSchema. 399 * 400 * @param activity The activity 401 * @param number The value to be set for the restriction. 402 */ saveNumber(Activity activity, int number)403 private void saveNumber(Activity activity, int number) { 404 mCurrentRestrictions.putInt(RESTRICTION_KEY_NUMBER, number); 405 saveRestrictions(activity); 406 editPreferences(activity).putInt(RESTRICTION_KEY_NUMBER, number).apply(); 407 } 408 409 /** 410 * Saves the value for the "rank" restriction of AppRestrictionSchema. 411 * 412 * @param activity The activity 413 * @param rank The value to be set for the restriction. 414 */ saveRank(Activity activity, String rank)415 private void saveRank(Activity activity, String rank) { 416 mCurrentRestrictions.putString(RESTRICTION_KEY_RANK, rank); 417 saveRestrictions(activity); 418 editPreferences(activity).putString(RESTRICTION_KEY_RANK, rank).apply(); 419 } 420 addApproval(Activity activity, String approval)421 private void addApproval(Activity activity, String approval) { 422 List<String> approvals = new ArrayList<>(Arrays.asList( 423 mCurrentRestrictions.getStringArray(RESTRICTION_KEY_APPROVALS))); 424 if (approvals.contains(approval)) { 425 return; 426 } 427 approvals.add(approval); 428 saveApprovals(activity, approvals.toArray(new String[approvals.size()])); 429 } 430 removeApproval(Activity activity, String approval)431 private void removeApproval(Activity activity, String approval) { 432 List<String> approvals = new ArrayList<>(Arrays.asList( 433 mCurrentRestrictions.getStringArray(RESTRICTION_KEY_APPROVALS))); 434 if (!approval.contains(approval)) { 435 return; 436 } 437 approvals.remove(approval); 438 saveApprovals(activity, approvals.toArray(new String[approvals.size()])); 439 } 440 441 /** 442 * Saves the value for the "approvals" restriction of AppRestrictionSchema. 443 * 444 * @param activity The activity 445 * @param approvals The value to be set for the restriction. 446 */ saveApprovals(Activity activity, String[] approvals)447 private void saveApprovals(Activity activity, String[] approvals) { 448 mCurrentRestrictions.putStringArray(RESTRICTION_KEY_APPROVALS, approvals); 449 saveRestrictions(activity); 450 editPreferences(activity).putString(RESTRICTION_KEY_APPROVALS, 451 TextUtils.join(DELIMETER, approvals)).apply(); 452 } 453 454 /** 455 * Saves the value for the "items" restriction of AppRestrictionSchema. 456 * 457 * @param activity The activity. 458 * @param items The values. 459 */ saveItems(Activity activity, Map<String, String> items)460 private void saveItems(Activity activity, Map<String, String> items) { 461 if (!BUNDLE_SUPPORTED) { 462 return; 463 } 464 mCurrentRestrictions.putParcelableArray(RESTRICTION_KEY_ITEMS, convertToBundles(items)); 465 saveRestrictions(activity); 466 StringBuilder builder = new StringBuilder(); 467 boolean first = true; 468 for (String key : items.keySet()) { 469 if (first) { 470 first = false; 471 } else { 472 builder.append(DELIMETER); 473 } 474 builder.append(key); 475 builder.append(SEPARATOR); 476 builder.append(items.get(key)); 477 } 478 editPreferences(activity).putString(RESTRICTION_KEY_ITEMS, builder.toString()).apply(); 479 } 480 481 /** 482 * Saves all the restrictions. 483 * 484 * @param activity The activity. 485 */ saveRestrictions(Activity activity)486 private void saveRestrictions(Activity activity) { 487 DevicePolicyManager devicePolicyManager 488 = (DevicePolicyManager) activity.getSystemService(Context.DEVICE_POLICY_SERVICE); 489 devicePolicyManager.setApplicationRestrictions( 490 EnforcerDeviceAdminReceiver.getComponentName(activity), 491 Constants.PACKAGE_NAME_APP_RESTRICTION_SCHEMA, mCurrentRestrictions); 492 } 493 editPreferences(Activity activity)494 private SharedPreferences.Editor editPreferences(Activity activity) { 495 return activity.getSharedPreferences(PREFS_KEY, Context.MODE_PRIVATE).edit(); 496 } 497 498 /** 499 * Sequential search 500 * 501 * @param array The string array 502 * @param s The string to search for 503 * @return Index if found. -1 if not found. 504 */ search(String[] array, String s)505 private int search(String[] array, String s) { 506 for (int i = 0; i < array.length; ++i) { 507 if (s.equals(array[i])) { 508 return i; 509 } 510 } 511 return -1; 512 } 513 514 } 515