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.android.tv.settings.users;
18 
19 import com.android.tv.settings.R;
20 import com.android.tv.settings.dialog.DialogFragment;
21 import com.android.tv.settings.dialog.DialogFragment.Action;
22 import com.android.tv.settings.users.AppRestrictionsManager.Listener;
23 
24 import android.app.Activity;
25 import android.app.Fragment;
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.content.RestrictionEntry;
31 import android.content.pm.ApplicationInfo;
32 import android.content.pm.IPackageManager;
33 import android.content.pm.PackageManager;
34 import android.content.pm.UserInfo;
35 import android.content.pm.PackageManager.NameNotFoundException;
36 import android.net.Uri;
37 import android.os.AsyncTask;
38 import android.os.Bundle;
39 import android.os.RemoteException;
40 import android.os.ServiceManager;
41 import android.os.UserHandle;
42 import android.os.UserManager;
43 import android.util.Log;
44 
45 import java.util.ArrayList;
46 import java.util.HashMap;
47 import java.util.Map;
48 
49 /**
50  * DialogFragment that configures the app restrictions for a given user.
51  */
52 public class UserAppRestrictionsDialogFragment extends DialogFragment implements Action.Listener,
53         AppLoadingTask.Listener {
54 
55     private static final boolean DEBUG = false;
56     private static final String TAG = "RestrictedProfile";
57 
58     /** Key for extra passed in from calling fragment for the userId of the user being edited */
59     public static final String EXTRA_USER_ID = "user_id";
60 
61     /** Key for extra passed in from calling fragment to indicate if this is a newly created user */
62     public static final String EXTRA_NEW_USER = "new_user";
63 
64     private static final String EXTRA_CONTENT_TITLE = "title";
65     private static final String EXTRA_CONTENT_BREADCRUMB = "breadcrumb";
66     private static final String EXTRA_CONTENT_DESCRIPTION = "description";
67     private static final String EXTRA_CONTENT_ICON_RESOURCE_ID = "iconResourceId";
68     private static final String EXTRA_CONTENT_ICON_URI = "iconUri";
69     private static final String EXTRA_CONTENT_ICON_BITMAP = "iconBitmap";
70     private static final String EXTRA_CONTENT_ICON_BACKGROUND = "iconBackground";
71 
72     private static final String EXTRA_PACKAGE_NAME = "packageName";
73     private static final String EXTRA_CAN_CONFIGURE_RESTRICTIONS = "canConfigureRestrictions";
74     private static final String EXTRA_CAN_SEE_RESTRICTED_ACCOUNTS = "canSeeRectrictedAccounts";
75     private static final String EXTRA_IS_ALLOWED = "isAllowed";
76     private static final String EXTRA_CAN_BE_ENABLED_DISABLED = "canBeEnabledDisabled";
77     private static final String EXTRA_CONTROLLING_APP = "controllingApp";
78 
79     private static final String ACTION_ALLOW = "allow";
80     private static final String ACTION_DISALLOW = "disallow";
81     private static final String ACTION_CONFIGURE = "configure";
82     private static final String ACTION_CUSTOMIZE_RESTRICTIONS = "customizeRestriction";
83 
84     private static final int CHECK_SET_ID = 1;
85 
newInstance(Context context, int userId, boolean newUser)86     public static UserAppRestrictionsDialogFragment newInstance(Context context, int userId,
87             boolean newUser) {
88         UserAppRestrictionsDialogFragment fragment = new UserAppRestrictionsDialogFragment();
89         Bundle args = new Bundle();
90         args.putString(EXTRA_CONTENT_TITLE,
91                 context.getString(R.string.restricted_profile_configure_apps_title));
92         args.putInt(EXTRA_CONTENT_ICON_RESOURCE_ID, R.drawable.ic_settings_launcher_icon);
93         args.putInt(EXTRA_CONTENT_ICON_BACKGROUND,
94                 context.getResources().getColor(R.color.icon_background));
95         args.putInt(EXTRA_USER_ID, userId);
96         args.putBoolean(EXTRA_NEW_USER, newUser);
97         fragment.setArguments(args);
98         return fragment;
99     }
100 
101     private UserManager mUserManager;
102     private IPackageManager mIPm;
103     private AppLoadingTask mAppLoadingTask;
104     private final HashMap<String, Boolean> mSelectedPackages = new HashMap<String, Boolean>();
105     private UserHandle mUser;
106     private boolean mNewUser;
107     private boolean mAppListChanged;
108     private AppRestrictionsManager mAppRestrictionsManager;
109 
110     private BroadcastReceiver mUserBackgrounding = new BroadcastReceiver() {
111         @Override
112         public void onReceive(Context context, Intent intent) {
113             // Update the user's app selection right away without waiting for a pause
114             // onPause() might come in too late, causing apps to disappear after broadcasts
115             // have been scheduled during user startup.
116             if (mAppListChanged) {
117                 if (DEBUG) {
118                     Log.d(TAG, "User backgrounding, update app list");
119                 }
120                 applyUserAppsStates(mSelectedPackages, getActions(), mIPm, mUser.getIdentifier());
121                 if (DEBUG) {
122                     Log.d(TAG, "User backgrounding, done updating app list");
123                 }
124             }
125         }
126     };
127     private BroadcastReceiver mPackageObserver = new BroadcastReceiver() {
128         @Override
129         public void onReceive(Context context, Intent intent) {
130             onPackageChanged(intent);
131         }
132 
133         private void onPackageChanged(Intent intent) {
134             String action = intent.getAction();
135             String packageName = intent.getData().getSchemeSpecificPart();
136             // Package added, check if the preference needs to be enabled
137             ArrayList<Action> matchingActions = findActionsWithPackageName(getActions(),
138                     packageName);
139             for (Action matchingAction : matchingActions) {
140                 if ((Intent.ACTION_PACKAGE_ADDED.equals(action) && matchingAction.isChecked()) || (
141                         Intent.ACTION_PACKAGE_REMOVED.equals(action)
142                         && !matchingAction.isChecked())) {
143                     matchingAction.setEnabled(true);
144                 }
145             }
146         }
147     };
148 
149     @Override
onCreate(Bundle icicle)150     public void onCreate(Bundle icicle) {
151         super.onCreate(icicle);
152         mUserManager = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
153         setActions(new ArrayList<Action>());
154         setListener(this);
155         if (icicle != null) {
156             mUser = new UserHandle(icicle.getInt(EXTRA_USER_ID));
157         } else {
158             Bundle args = getArguments();
159             if (args != null) {
160                 if (args.containsKey(EXTRA_USER_ID)) {
161                     mUser = new UserHandle(args.getInt(EXTRA_USER_ID));
162                 }
163                 mNewUser = args.getBoolean(EXTRA_NEW_USER, false);
164             }
165         }
166 
167         if (mUser == null) {
168             mUser = android.os.Process.myUserHandle();
169         }
170 
171         mIPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
172     }
173 
174     @Override
onResume()175     public void onResume() {
176         super.onResume();
177 
178         getActivity().registerReceiver(mUserBackgrounding,
179                 new IntentFilter(Intent.ACTION_USER_BACKGROUND));
180         IntentFilter packageFilter = new IntentFilter();
181         packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
182         packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
183         packageFilter.addDataScheme("package");
184         getActivity().registerReceiver(mPackageObserver, packageFilter);
185 
186         if (mAppLoadingTask == null) {
187             mAppListChanged = false;
188             mAppLoadingTask = new AppLoadingTask(getActivity(), mUser.getIdentifier(), mNewUser,
189                     mIPm, this);
190             mAppLoadingTask.execute((Void[]) null);
191         }
192     }
193 
194     @Override
onPause()195     public void onPause() {
196         super.onPause();
197         mNewUser = false;
198         getActivity().unregisterReceiver(mUserBackgrounding);
199         getActivity().unregisterReceiver(mPackageObserver);
200         if (mAppListChanged) {
201             new Thread() {
202                 public void run() {
203                     applyUserAppsStates(mSelectedPackages, getActions(), mIPm,
204                             mUser.getIdentifier());
205                 }
206             }.start();
207         }
208     }
209 
210     @Override
onActionClicked(Action action)211     public void onActionClicked(Action action) {
212         String packageName = action.getIntent().getStringExtra(EXTRA_PACKAGE_NAME);
213         if (ACTION_CONFIGURE.equals(action.getKey())) {
214             boolean isAllowed = action.getIntent().getBooleanExtra(EXTRA_IS_ALLOWED, false);
215             boolean canBeEnabledDisabled = action.getIntent().getBooleanExtra(
216                     EXTRA_CAN_BE_ENABLED_DISABLED, false);
217             String controllingActivity = action.getIntent().getStringExtra(EXTRA_CONTROLLING_APP);
218             final ArrayList<Action> initialAllowDisallowActions = new ArrayList<Action>();
219             if (controllingActivity != null) {
220                 initialAllowDisallowActions.add(new Action.Builder()
221                         .title(getString(isAllowed ? R.string.restricted_profile_allowed
222                                 : R.string.restricted_profile_not_allowed))
223                         .description(getString(R.string.user_restrictions_controlled_by,
224                                 controllingActivity))
225                         .checked(isAllowed)
226                         .infoOnly(true)
227                         .build());
228             } else if (!canBeEnabledDisabled) {
229                 initialAllowDisallowActions.add(new Action.Builder()
230                         .title(getString(isAllowed ? R.string.restricted_profile_allowed
231                                 : R.string.restricted_profile_not_allowed))
232                         .checked(isAllowed)
233                         .infoOnly(true)
234                         .build());
235             } else {
236                 boolean canSeeRestrictedAccounts = action.getIntent().getBooleanExtra(
237                         EXTRA_CAN_SEE_RESTRICTED_ACCOUNTS, true);
238                 initialAllowDisallowActions.add(new Action.Builder().key(ACTION_DISALLOW)
239                         .title(getString(R.string.restricted_profile_not_allowed))
240                         .intent(action.getIntent())
241                         .checked(!isAllowed)
242                         .checkSetId(CHECK_SET_ID).build());
243                 initialAllowDisallowActions.add(new Action.Builder().key(ACTION_ALLOW)
244                         .title(getString(R.string.restricted_profile_allowed))
245                         .description(canSeeRestrictedAccounts ? getString(
246                                 R.string.app_sees_restricted_accounts)
247                                 : null)
248                         .intent(action.getIntent())
249                         .checkSetId(CHECK_SET_ID)
250                         .checked(isAllowed)
251                         .build());
252             }
253 
254             final DialogFragment dialogFragment = new DialogFragment.Builder()
255                     .title(action.getTitle()).iconUri(action.getIconUri())
256                     .actions(initialAllowDisallowActions).build();
257 
258             boolean canConfigureRestrictions = action.getIntent().getBooleanExtra(
259                     EXTRA_CAN_CONFIGURE_RESTRICTIONS, false);
260             if (canConfigureRestrictions) {
261                 mAppRestrictionsManager = new AppRestrictionsManager(this, mUser,
262                         mUserManager, packageName, new AppRestrictionsManager.Listener() {
263                             @Override
264                             public void onRestrictionActionsLoaded(String packageName,
265                                     ArrayList<Action> restrictionActions) {
266                                 ArrayList<Action> oldActions = dialogFragment.getActions();
267                                 ArrayList<Action> newActions = new ArrayList<Action>();
268                                 if (oldActions != null && oldActions.size()
269                                         >= initialAllowDisallowActions.size()) {
270                                     for (int i = 0, size = initialAllowDisallowActions.size();
271                                             i < size; i++) {
272                                         newActions.add(oldActions.get(i));
273                                     }
274                                 } else {
275                                     newActions.addAll(initialAllowDisallowActions);
276                                 }
277                                 newActions.addAll(restrictionActions);
278                                 dialogFragment.setActions(newActions);
279                             }
280                         });
281                 mAppRestrictionsManager.loadRestrictionActions();
282             }
283 
284             dialogFragment.setListener(this);
285             DialogFragment.add(getFragmentManager(), dialogFragment);
286         } else if (ACTION_ALLOW.equals(action.getKey())) {
287             setEnabled(packageName, true);
288         } else if (ACTION_DISALLOW.equals(action.getKey())) {
289             setEnabled(packageName, false);
290         } else if (mAppRestrictionsManager != null) {
291             mAppRestrictionsManager.onActionClicked(action);
292         }
293     }
294 
295     @Override
onActivityResult(int requestCode, int resultCode, Intent data)296     public void onActivityResult(int requestCode, int resultCode, Intent data) {
297         if (mAppRestrictionsManager != null) {
298             mAppRestrictionsManager.onActivityResult(requestCode, resultCode, data);
299         }
300     }
301 
setEnabled(String packageName, boolean enabled)302     private void setEnabled(String packageName, boolean enabled) {
303         onPackageEnableChanged(packageName, enabled);
304         for (Action action : getActions()) {
305             String actionPackageName = action.getIntent().getStringExtra(EXTRA_PACKAGE_NAME);
306             if (actionPackageName.equals(packageName)) {
307                 action.setChecked(enabled);
308                 action.setDescription(getString(enabled ? R.string.restricted_profile_allowed
309                         : R.string.restricted_profile_not_allowed));
310                 action.getIntent().putExtra(EXTRA_IS_ALLOWED, enabled);
311             }
312         }
313         onActionsLoaded(getActions());
314     }
315 
316     @Override
onPackageEnableChanged(String packageName, boolean enabled)317     public void onPackageEnableChanged(String packageName, boolean enabled) {
318         mSelectedPackages.put(packageName, enabled);
319         mAppListChanged = true;
320         if (getActivity() instanceof AppLoadingTask.Listener) {
321             ((AppLoadingTask.Listener) getActivity()).onPackageEnableChanged(packageName, enabled);
322         }
323     }
324 
325     @Override
onActionsLoaded(ArrayList<Action> actions)326     public void onActionsLoaded(ArrayList<Action> actions) {
327         setActions(actions);
328         if (getActivity() instanceof AppLoadingTask.Listener) {
329             ((AppLoadingTask.Listener) getActivity()).onActionsLoaded(actions);
330         }
331     }
332 
createAction(Context context, String packageName, String title, Uri iconUri, boolean canBeEnabledDisabled, boolean isAllowed, boolean hasCustomizableRestrictions, boolean canSeeRestrictedAccounts, boolean availableForRestrictedProfile, String controllingActivity)333     static Action createAction(Context context, String packageName, String title, Uri iconUri,
334             boolean canBeEnabledDisabled, boolean isAllowed, boolean hasCustomizableRestrictions,
335             boolean canSeeRestrictedAccounts, boolean availableForRestrictedProfile,
336             String controllingActivity) {
337         String description = context.getString(
338                 availableForRestrictedProfile ? isAllowed ? R.string.restricted_profile_allowed
339                         : R.string.restricted_profile_not_allowed
340                         : R.string.app_not_supported_in_limited);
341 
342         Intent intent = new Intent().putExtra(EXTRA_IS_ALLOWED, isAllowed)
343                 .putExtra(EXTRA_CAN_CONFIGURE_RESTRICTIONS,
344                         (hasCustomizableRestrictions && (controllingActivity == null)))
345                 .putExtra(EXTRA_CAN_BE_ENABLED_DISABLED, canBeEnabledDisabled)
346                 .putExtra(EXTRA_PACKAGE_NAME, packageName)
347                 .putExtra(EXTRA_CONTROLLING_APP, controllingActivity)
348                 .putExtra(EXTRA_CAN_SEE_RESTRICTED_ACCOUNTS, canSeeRestrictedAccounts);
349 
350         if (DEBUG) {
351             Log.d(TAG, "Icon uri: " + (iconUri != null ? iconUri.toString() : "null"));
352         }
353         return new Action.Builder()
354                 .key(ACTION_CONFIGURE)
355                 .title(title)
356                 .description(description)
357                 .enabled(availableForRestrictedProfile)
358                 .iconUri(iconUri)
359                 .checked(isAllowed)
360                 .intent(intent)
361                 .build();
362     }
363 
applyUserAppsStates(HashMap<String, Boolean> selectedPackages, ArrayList<Action> actions, IPackageManager ipm, int userId)364     static void applyUserAppsStates(HashMap<String, Boolean> selectedPackages,
365             ArrayList<Action> actions, IPackageManager ipm, int userId) {
366         for (Map.Entry<String, Boolean> entry : selectedPackages.entrySet()) {
367             String packageName = entry.getKey();
368             boolean enabled = entry.getValue();
369             if (applyUserAppState(ipm, userId, packageName, enabled)) {
370                 disableActionForPackage(actions, packageName);
371             }
372         }
373     }
374 
applyUserAppState(IPackageManager ipm, int userId, String packageName, boolean enabled)375     static boolean applyUserAppState(IPackageManager ipm, int userId, String packageName,
376             boolean enabled) {
377         boolean disableActionForPackage = false;
378         if (enabled) {
379             // Enable selected apps
380             try {
381                 ApplicationInfo info = ipm.getApplicationInfo(packageName,
382                         PackageManager.GET_UNINSTALLED_PACKAGES, userId);
383                 if (info == null || info.enabled == false
384                         || (info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
385                     ipm.installExistingPackageAsUser(packageName, userId);
386                     if (DEBUG) {
387                         Log.d(TAG, "Installing " + packageName);
388                     }
389                 }
390                 if (info != null && (info.flags & ApplicationInfo.FLAG_HIDDEN) != 0
391                         && (info.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
392                     disableActionForPackage = true;
393                     ipm.setApplicationHiddenSettingAsUser(packageName, false, userId);
394                     if (DEBUG) {
395                         Log.d(TAG, "Unhiding " + packageName);
396                     }
397                 }
398             } catch (RemoteException re) {
399                 Log.e(TAG, "Caught exception while installing " + packageName + "!", re);
400             }
401         } else {
402             // Blacklist all other apps, system or downloaded
403             try {
404                 ApplicationInfo info = ipm.getApplicationInfo(packageName, 0, userId);
405                 if (info != null) {
406                     ipm.deletePackageAsUser(packageName, null, userId,
407                             PackageManager.DELETE_SYSTEM_APP);
408                     if (DEBUG) {
409                         Log.d(TAG, "Uninstalling " + packageName);
410                     }
411                 }
412             } catch (RemoteException re) {
413                 Log.e(TAG, "Caught exception while uninstalling " + packageName + "!", re);
414             }
415         }
416         return disableActionForPackage;
417     }
418 
disableActionForPackage(ArrayList<Action> actions, String packageName)419     private static void disableActionForPackage(ArrayList<Action> actions, String packageName) {
420         ArrayList<Action> matchingActions = findActionsWithPackageName(actions, packageName);
421         for(Action matchingAction : matchingActions) {
422             matchingAction.setEnabled(false);
423         }
424     }
425 
findActionsWithPackageName(ArrayList<Action> actions, String packageName)426     private static ArrayList<Action> findActionsWithPackageName(ArrayList<Action> actions,
427             String packageName) {
428         ArrayList<Action> matchingActions = new ArrayList<Action>();
429         if (packageName != null) {
430             for (Action action : actions) {
431                 if (packageName.equals(action.getIntent().getStringExtra(EXTRA_PACKAGE_NAME))) {
432                     matchingActions.add(action);
433                 }
434             }
435         }
436         return matchingActions;
437     }
438 }
439