Home | History | Annotate | Download | only in apprestrictionenforcer
      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
    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
    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
    142     public void onResume() {
    143         super.onResume();
    144         loadRestrictions(getActivity());
    145     }
    146 
    147     @Override
    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
    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
    197     public void onNothingSelected(AdapterView<?> parent) {
    198         // Nothing to do
    199     }
    200 
    201     @Override
    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
    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      */
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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
    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 
    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      */
    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      */
    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      */
    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      */
    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 
    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 
    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      */
    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      */
    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      */
    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 
    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      */
    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