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
17package com.android.tv.settings.users;
18
19import android.app.Activity;
20import android.app.Fragment;
21import android.content.BroadcastReceiver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.RestrictionEntry;
25import android.os.Bundle;
26import android.os.UserHandle;
27import android.os.UserManager;
28import android.text.TextUtils;
29import android.util.Log;
30
31import com.android.tv.settings.R;
32import com.android.tv.settings.dialog.DialogFragment;
33import com.android.tv.settings.dialog.DialogFragment.Action;
34
35import java.util.ArrayList;
36import java.util.Arrays;
37import java.util.HashSet;
38import java.util.List;
39
40/**
41 * Handles all aspects of DialogFragment Actions for setting individual app restrictions.
42 */
43class AppRestrictionsManager implements Action.Listener {
44
45    interface Listener {
46        void onRestrictionActionsLoaded(String packageName, ArrayList<Action> actions);
47    }
48
49    private static final boolean DEBUG = false;
50    private static final String TAG = "RestrictedProfile";
51
52    private static final String EXTRA_PACKAGE_NAME = "AppRestrictionsManager.package_name";
53    private static final String EXTRA_RESTRICTIONS = "AppRestrictionsManager.restrictions";
54    private static final String EXTRA_USER_HANDLE = "user_handle";
55    private static final String EXTRA_ENABLED = "enabled";
56    private static final String EXTRA_RESTRICTION_KEY = "restriction_key";
57    private static final String EXTRA_CHOICE_VALUE = "choice_value";
58    private static final String ACTION_CUSTOM_CONFIGURATION = "action_custom_configuration";
59    private static final String ACTION_TRUE = "action_true";
60    private static final String ACTION_FALSE = "action_false";
61    private static final String ACTION_CHANGE_RESTRICTION = "action_change_restriction";
62    private static final String ACTION_CHOICE = "action_choice";
63    private static final String ACTION_MULTI_CHOICE = "action_multi_choice";
64    private static final int MUTUALLY_EXCLUSIVE_CHOICE_CHECK_SET_ID = 1;
65
66    private final Fragment mFragment;
67    private final UserManager mUserManager;
68    private final UserHandle mUserHandle;
69    private final String mPackageName;
70    private final Listener mListener;
71    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
72        private static final String CUSTOM_RESTRICTIONS_INTENT = Intent.EXTRA_RESTRICTIONS_INTENT;
73
74        @Override
75        public void onReceive(Context context, Intent intent) {
76            Bundle results = getResultExtras(true);
77            mRestrictions = results.getParcelableArrayList(Intent.EXTRA_RESTRICTIONS_LIST);
78            mRestrictionsIntent = results.getParcelable(CUSTOM_RESTRICTIONS_INTENT);
79
80            if (mRestrictions != null && mRestrictionsIntent == null) {
81                saveRestrictions();
82            } else if (mRestrictionsIntent != null) {
83                getRestrictionActions();
84            }
85        }
86    };
87
88    private ArrayList<RestrictionEntry> mRestrictions;
89    private Intent mRestrictionsIntent;
90    private DialogFragment mCurrentDialogFragment;
91
92    /**
93     * @param fragment THe fragment for which DialogFragment actions are being managed.
94     * @param userHandle the user whose app restrictions are being set.
95     * @param userManager the user manager for setting app restrictions.
96     * @param packageName settings' package name (for special case restrictions).
97     * @param listener a listener for when restriction actions are ready to be displayed.
98     */
99    AppRestrictionsManager(Fragment fragment, UserHandle userHandle, UserManager userManager,
100            String packageName, Listener listener) {
101        mFragment = fragment;
102        mUserHandle = userHandle;
103        mUserManager = userManager;
104        mPackageName = packageName;
105        mListener = listener;
106    }
107
108    void loadRestrictionActions() {
109        if (mPackageName.equals(mFragment.getActivity().getPackageName())) {
110            // Settings, fake it by using user restrictions
111            mRestrictions = RestrictionUtils.getRestrictions(mFragment.getActivity(), mUserHandle);
112            mRestrictionsIntent = null;
113            getRestrictionActions();
114        } else {
115            requestRestrictionsForApp();
116        }
117    }
118
119    @Override
120    public void onActionClicked(Action action) {
121        String restrictionEntryKey = action.getIntent().getStringExtra(EXTRA_RESTRICTION_KEY);
122        RestrictionEntry entry = restrictionEntryKey != null ? findRestriction(restrictionEntryKey)
123                : null;
124
125        if (ACTION_CUSTOM_CONFIGURATION.equals(action.getKey())) {
126            mFragment.startActivityForResult(action.getIntent(), 1);
127        } else if (ACTION_CHANGE_RESTRICTION.equals(action.getKey())) {
128            ArrayList<Action> actions = new ArrayList<>();
129            switch (entry.getType()) {
130                case RestrictionEntry.TYPE_BOOLEAN:
131                    actions.add(new Action.Builder()
132                            .key(ACTION_TRUE)
133                            .title(Boolean.toString(true))
134                            .checked(entry.getSelectedState())
135                            .checkSetId(MUTUALLY_EXCLUSIVE_CHOICE_CHECK_SET_ID)
136                            .intent(action.getIntent())
137                            .build());
138                    actions.add(new Action.Builder()
139                            .key(ACTION_FALSE)
140                            .title(Boolean.toString(false))
141                            .checked(!entry.getSelectedState())
142                            .checkSetId(MUTUALLY_EXCLUSIVE_CHOICE_CHECK_SET_ID)
143                            .intent(action.getIntent())
144                            .build());
145                    break;
146                case RestrictionEntry.TYPE_CHOICE:
147                case RestrictionEntry.TYPE_CHOICE_LEVEL:
148                {
149                    String value = entry.getSelectedString();
150                    if (value == null) {
151                        value = entry.getDescription();
152                    }
153                    String[] choiceEntries = entry.getChoiceEntries();
154                    String[] choiceValues = entry.getChoiceValues();
155                    boolean useValue = (choiceEntries == null
156                            || choiceEntries.length != choiceValues.length);
157                    for (int i = 0; i < choiceValues.length; i++) {
158                        String choiceValue = choiceValues[i];
159                        String title = useValue ? choiceValue : choiceEntries[i];
160                        Intent intent = new Intent(action.getIntent());
161                        intent.putExtra(EXTRA_CHOICE_VALUE, choiceValue);
162                        actions.add(new Action.Builder()
163                                .key(ACTION_CHOICE)
164                                .title(title)
165                                .checked(choiceValue.equals(value))
166                                .checkSetId(MUTUALLY_EXCLUSIVE_CHOICE_CHECK_SET_ID)
167                                .intent(intent)
168                                .build());
169                    }
170                }
171                    break;
172                case RestrictionEntry.TYPE_MULTI_SELECT:
173                {
174                    List<String> selectedChoiceValues = Arrays.asList(
175                            entry.getAllSelectedStrings());
176                    String[] choiceEntries = entry.getChoiceEntries();
177                    String[] choiceValues = entry.getChoiceValues();
178                    boolean useValues = (choiceEntries == null
179                            || choiceEntries.length != choiceValues.length);
180                    for (int i = 0; i < choiceValues.length; i++) {
181                        String choiceValue = choiceValues[i];
182                        String title = useValues ? choiceValue : choiceEntries[i];
183                        Intent intent = new Intent(action.getIntent());
184                        intent.putExtra(EXTRA_CHOICE_VALUE, choiceValue);
185                        actions.add(new Action.Builder()
186                                .key(ACTION_MULTI_CHOICE)
187                                .title(title)
188                                .checked(selectedChoiceValues.contains(choiceValue))
189                                .intent(intent)
190                                .build());
191                    }
192                }
193                    break;
194                case RestrictionEntry.TYPE_NULL:
195                default:
196            }
197
198            if (!actions.isEmpty()) {
199                mCurrentDialogFragment = new DialogFragment.Builder()
200                        .title(entry.getTitle())
201                        .description(entry.getDescription())
202                        .iconResourceId(RestrictedProfileDialogFragment.getIconResource())
203                        .iconBackgroundColor(
204                                mFragment.getActivity().getResources()
205                                        .getColor(R.color.icon_background))
206                        .actions(actions).build();
207                mCurrentDialogFragment.setListener(this);
208                DialogFragment.add(mFragment.getFragmentManager(), mCurrentDialogFragment);
209
210            }
211        } else if (ACTION_TRUE.equals(action.getKey())) {
212            entry.setSelectedState(true);
213            saveRestrictions();
214        } else if (ACTION_FALSE.equals(action.getKey())) {
215            entry.setSelectedState(false);
216            saveRestrictions();
217        } else if (ACTION_CHOICE.equals(action.getKey())) {
218            entry.setSelectedString(getChoiceValue(action));
219            saveRestrictions();
220        } else if (ACTION_MULTI_CHOICE.equals(action.getKey())) {
221
222            action.setChecked(!action.isChecked());
223            if (mCurrentDialogFragment != null) {
224                mCurrentDialogFragment.setActions(mCurrentDialogFragment.getActions());
225            }
226            HashSet<String> selectedChoiceValues = new HashSet<>();
227            selectedChoiceValues.addAll(Arrays.asList(entry.getAllSelectedStrings()));
228
229            if (action.isChecked()) {
230                selectedChoiceValues.add(getChoiceValue(action));
231            } else {
232                selectedChoiceValues.remove(getChoiceValue(action));
233            }
234            entry.setAllSelectedStrings(
235                    selectedChoiceValues.toArray(new String[selectedChoiceValues.size()]));
236            saveRestrictions();
237        }
238    }
239
240    void onActivityResult(int requestCode, int resultCode, Intent data) {
241        if (resultCode == Activity.RESULT_OK) {
242            ArrayList<RestrictionEntry> list =
243                    data.getParcelableArrayListExtra(Intent.EXTRA_RESTRICTIONS_LIST);
244            Bundle bundle = data.getBundleExtra(Intent.EXTRA_RESTRICTIONS_BUNDLE);
245            if (list != null) {
246                // If there's a valid result, persist it to the user manager.
247                mUserManager.setApplicationRestrictions(mPackageName,
248                        RestrictionUtils.restrictionsToBundle(list), mUserHandle);
249            } else if (bundle != null) {
250                // If there's a valid result, persist it to the user manager.
251                mUserManager.setApplicationRestrictions(mPackageName, bundle, mUserHandle);
252            }
253            loadRestrictionActions();
254        }
255    }
256
257    private RestrictionEntry findRestriction(String key) {
258        for (RestrictionEntry entry : mRestrictions) {
259            if (entry.getKey().equals(key)) {
260                return entry;
261            }
262        }
263        Log.wtf(TAG, "Couldn't find the restriction to set!");
264        return null;
265    }
266
267    private String getChoiceValue(Action action) {
268        return action.getIntent().getStringExtra(EXTRA_CHOICE_VALUE);
269    }
270
271    /**
272     * Send a broadcast to the app to query its restrictions
273     */
274    private void requestRestrictionsForApp() {
275        Bundle oldEntries = mUserManager.getApplicationRestrictions(mPackageName, mUserHandle);
276        Intent intent = new Intent(Intent.ACTION_GET_RESTRICTION_ENTRIES);
277        intent.setPackage(mPackageName);
278        intent.putExtra(Intent.EXTRA_RESTRICTIONS_BUNDLE, oldEntries);
279        intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
280        mFragment.getActivity().sendOrderedBroadcast(intent, null, mBroadcastReceiver, null,
281                Activity.RESULT_OK, null, null);
282    }
283
284    private void getRestrictionActions() {
285        ArrayList<Action> actions = new ArrayList<>();
286
287        if (mRestrictionsIntent != null) {
288            actions.add(new Action.Builder()
289                    .key(ACTION_CUSTOM_CONFIGURATION)
290                    .title(mFragment.getActivity().getString(
291                            R.string.restricted_profile_customize_restrictions))
292                    .intent(mRestrictionsIntent)
293                    .hasNext(true)
294                    .build());
295        }
296
297        if (mRestrictions != null) {
298            for (RestrictionEntry entry : mRestrictions) {
299                Intent data = new Intent().putExtra(EXTRA_RESTRICTION_KEY, entry.getKey());
300                switch (entry.getType()) {
301                    case RestrictionEntry.TYPE_BOOLEAN:
302                        actions.add(new Action.Builder()
303                                .key(ACTION_CHANGE_RESTRICTION)
304                                .title(entry.getTitle())
305                                .description(mFragment.getActivity().getString(
306                                        R.string.restriction_description, entry.getDescription(),
307                                        Boolean.toString(entry.getSelectedState())))
308                                .hasNext(mRestrictionsIntent == null)
309                                .infoOnly(mRestrictionsIntent != null)
310                                .intent(data)
311                                .multilineDescription(true)
312                                .build());
313                        break;
314                    case RestrictionEntry.TYPE_CHOICE:
315                    case RestrictionEntry.TYPE_CHOICE_LEVEL:
316                        String value = entry.getSelectedString();
317                        if (value == null) {
318                            value = entry.getDescription();
319                        }
320                        actions.add(new Action.Builder()
321                                .key(ACTION_CHANGE_RESTRICTION)
322                                .title(entry.getTitle())
323                                .description(mFragment.getActivity().getString(
324                                        R.string.restriction_description,
325                                        entry.getDescription(), findInArray(
326                                                entry.getChoiceEntries(), entry.getChoiceValues(),
327                                                value)))
328                                .hasNext(mRestrictionsIntent == null)
329                                .infoOnly(mRestrictionsIntent != null)
330                                .intent(data)
331                                .multilineDescription(true)
332                                .build());
333                        break;
334                    case RestrictionEntry.TYPE_MULTI_SELECT:
335                        actions.add(new Action.Builder()
336                                .key(ACTION_CHANGE_RESTRICTION)
337                                .title(entry.getTitle())
338                                .description(mFragment.getActivity().getString(
339                                        R.string.restriction_description,
340                                        entry.getDescription(), TextUtils.join(
341                                                ", ", findSelectedStrings(entry.getChoiceEntries(),
342                                                        entry.getChoiceValues(),
343                                                        entry.getAllSelectedStrings()))))
344                                .hasNext(mRestrictionsIntent == null)
345                                .infoOnly(mRestrictionsIntent != null)
346                                .intent(data)
347                                .multilineDescription(true)
348                                .build());
349                        break;
350                    case RestrictionEntry.TYPE_NULL:
351                    default:
352                }
353            }
354        }
355
356        mListener.onRestrictionActionsLoaded(mPackageName, actions);
357    }
358
359    private String findInArray(String[] choiceEntries, String[] choiceValues,
360            String selectedString) {
361        if (choiceEntries == null || choiceValues.length != choiceEntries.length) {
362            return selectedString;
363        }
364        for (int i = 0; i < choiceValues.length; i++) {
365            if (choiceValues[i].equals(selectedString)) {
366                return choiceEntries[i];
367            }
368        }
369        return selectedString;
370    }
371
372    private String[] findSelectedStrings(String[] choiceEntries, String[] choiceValues,
373            String[] selectedStrings) {
374        if (choiceEntries == null || choiceValues.length != choiceEntries.length) {
375            return selectedStrings;
376        }
377        String[] selectedStringsMapped = new String[selectedStrings.length];
378        for (int i = 0; i < selectedStrings.length; i++) {
379            selectedStringsMapped[i] = selectedStrings[i];
380            for (int j = 0; j < choiceValues.length; j++) {
381                if (choiceValues[j].equals(selectedStrings[i])) {
382                    selectedStringsMapped[i] = choiceEntries[j];
383                }
384            }
385        }
386        return selectedStringsMapped;
387    }
388
389    private void saveRestrictions() {
390        getRestrictionActions();
391        if (mPackageName.equals(mFragment.getActivity().getPackageName())) {
392            RestrictionUtils.setRestrictions(mFragment.getActivity(), mRestrictions, mUserHandle);
393        } else {
394            Bundle bundle = RestrictionUtils.restrictionsToBundle(mRestrictions);
395            mUserManager.setApplicationRestrictions(mPackageName, bundle, mUserHandle);
396        }
397    }
398}
399