/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.permissioncontroller.role.ui;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProviders;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.permission.utils.Utils;
import com.android.permissioncontroller.role.utils.RoleUiBehaviorUtils;
import com.android.role.controller.model.Role;
import com.android.role.controller.model.Roles;
import java.util.List;
import java.util.Objects;
/**
* Child fragment for a default app.
*
* Must be added as a child fragment and its parent fragment must be a
* {@link PreferenceFragmentCompat} that implements {@link Parent}.
*
* @param type of the parent fragment
*/
public class DefaultAppChildFragment extends Fragment
implements DefaultAppConfirmationDialogFragment.Listener,
Preference.OnPreferenceClickListener {
private static final String PREFERENCE_KEY_NONE = DefaultAppChildFragment.class.getName()
+ ".preference.NONE";
private static final String PREFERENCE_KEY_DESCRIPTION = DefaultAppChildFragment.class.getName()
+ ".preference.DESCRIPTION";
@NonNull
private String mRoleName;
@NonNull
private UserHandle mUser;
@NonNull
private Role mRole;
@NonNull
private DefaultAppViewModel mViewModel;
/**
* Create a new instance of this fragment.
*
* @param roleName the name of the role for the default app
* @param user the user for the default app
*
* @return a new instance of this fragment
*/
@NonNull
public static DefaultAppChildFragment newInstance(@NonNull String roleName,
@NonNull UserHandle user) {
DefaultAppChildFragment fragment = new DefaultAppChildFragment();
Bundle arguments = new Bundle();
arguments.putString(Intent.EXTRA_ROLE_NAME, roleName);
arguments.putParcelable(Intent.EXTRA_USER, user);
fragment.setArguments(arguments);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle arguments = getArguments();
mRoleName = arguments.getString(Intent.EXTRA_ROLE_NAME);
mUser = arguments.getParcelable(Intent.EXTRA_USER);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
PF preferenceFragment = requirePreferenceFragment();
Activity activity = requireActivity();
mRole = Roles.get(activity).get(mRoleName);
preferenceFragment.setTitle(getString(mRole.getLabelResource()));
mViewModel = ViewModelProviders.of(this, new DefaultAppViewModel.Factory(mRole, mUser,
activity.getApplication())).get(DefaultAppViewModel.class);
mViewModel.getRoleLiveData().observe(this, this::onRoleChanged);
mViewModel.getManageRoleHolderStateLiveData().observe(this,
this::onManageRoleHolderStateChanged);
}
private void onRoleChanged(
@NonNull List> qualifyingApplications) {
PF preferenceFragment = requirePreferenceFragment();
PreferenceManager preferenceManager = preferenceFragment.getPreferenceManager();
Context context = preferenceManager.getContext();
PreferenceScreen preferenceScreen = preferenceFragment.getPreferenceScreen();
Preference oldDescriptionPreference = null;
ArrayMap oldPreferences = new ArrayMap<>();
if (preferenceScreen == null) {
preferenceScreen = preferenceManager.createPreferenceScreen(context);
preferenceFragment.setPreferenceScreen(preferenceScreen);
} else {
oldDescriptionPreference = preferenceScreen.findPreference(PREFERENCE_KEY_DESCRIPTION);
if (oldDescriptionPreference != null) {
preferenceScreen.removePreference(oldDescriptionPreference);
oldDescriptionPreference.setOrder(Preference.DEFAULT_ORDER);
}
for (int i = preferenceScreen.getPreferenceCount() - 1; i >= 0; --i) {
Preference preference = preferenceScreen.getPreference(i);
preferenceScreen.removePreference(preference);
preference.setOrder(Preference.DEFAULT_ORDER);
oldPreferences.put(preference.getKey(), preference);
}
}
if (mRole.shouldShowNone()) {
Drawable icon = AppCompatResources.getDrawable(context, R.drawable.ic_remove_circle);
String title = getString(R.string.default_app_none);
boolean noHolderApplication = !hasHolderApplication(qualifyingApplications);
addPreference(PREFERENCE_KEY_NONE, icon, title, noHolderApplication, null,
oldPreferences, preferenceScreen, context);
}
int qualifyingApplicationsSize = qualifyingApplications.size();
for (int i = 0; i < qualifyingApplicationsSize; i++) {
Pair qualifyingApplication = qualifyingApplications.get(i);
ApplicationInfo qualifyingApplicationInfo = qualifyingApplication.first;
boolean isHolderApplication = qualifyingApplication.second;
String key = qualifyingApplicationInfo.packageName;
Drawable icon = Utils.getBadgedIcon(context, qualifyingApplicationInfo);
String title = Utils.getFullAppLabel(qualifyingApplicationInfo, context);
addPreference(key, icon, title, isHolderApplication, qualifyingApplicationInfo,
oldPreferences, preferenceScreen, context);
}
Preference descriptionPreference = oldDescriptionPreference;
if (descriptionPreference == null) {
descriptionPreference = preferenceFragment.createFooterPreference();
descriptionPreference.setKey(PREFERENCE_KEY_DESCRIPTION);
descriptionPreference.setSummary(mRole.getDescriptionResource());
}
preferenceScreen.addPreference(descriptionPreference);
preferenceFragment.onPreferenceScreenChanged();
}
private static boolean hasHolderApplication(
@NonNull List> qualifyingApplications) {
int qualifyingApplicationsSize = qualifyingApplications.size();
for (int i = 0; i < qualifyingApplicationsSize; i++) {
Pair qualifyingApplication = qualifyingApplications.get(i);
boolean isHolderApplication = qualifyingApplication.second;
if (isHolderApplication) {
return true;
}
}
return false;
}
private void addPreference(@NonNull String key, @NonNull Drawable icon,
@NonNull CharSequence title, boolean checked, @Nullable ApplicationInfo applicationInfo,
@NonNull ArrayMap oldPreferences,
@NonNull PreferenceScreen preferenceScreen, @NonNull Context context) {
RoleApplicationPreference roleApplicationPreference =
(RoleApplicationPreference) oldPreferences.get(key);
TwoStatePreference preference;
if (roleApplicationPreference == null) {
roleApplicationPreference = requirePreferenceFragment().createApplicationPreference();
preference = roleApplicationPreference.asTwoStatePreference();
preference.setKey(key);
preference.setIcon(icon);
preference.setTitle(title);
preference.setPersistent(false);
preference.setOnPreferenceChangeListener((preference2, newValue) -> false);
preference.setOnPreferenceClickListener(this);
} else {
preference = roleApplicationPreference.asTwoStatePreference();
}
preference.setChecked(checked);
if (applicationInfo != null) {
roleApplicationPreference.setRestrictionIntent(
mRole.getApplicationRestrictionIntentAsUser(applicationInfo, mUser, context));
RoleUiBehaviorUtils.prepareApplicationPreferenceAsUser(mRole, roleApplicationPreference,
applicationInfo, mUser, context);
}
preferenceScreen.addPreference(preference);
}
private void onManageRoleHolderStateChanged(int state) {
ManageRoleHolderStateLiveData liveData = mViewModel.getManageRoleHolderStateLiveData();
switch (state) {
case ManageRoleHolderStateLiveData.STATE_SUCCESS:
String packageName = liveData.getLastPackageName();
if (packageName != null) {
mRole.onHolderSelectedAsUser(packageName, liveData.getLastUser(),
requireContext());
}
liveData.resetState();
break;
case ManageRoleHolderStateLiveData.STATE_FAILURE:
liveData.resetState();
break;
}
}
@Override
public boolean onPreferenceClick(@NonNull Preference preference) {
String key = preference.getKey();
if (Objects.equals(key, PREFERENCE_KEY_NONE)) {
mViewModel.setNoneDefaultApp();
} else {
String packageName = key;
CharSequence confirmationMessage =
RoleUiBehaviorUtils.getConfirmationMessage(mRole, packageName,
requireContext());
if (confirmationMessage != null) {
DefaultAppConfirmationDialogFragment.show(packageName, confirmationMessage, this);
} else {
setDefaultApp(packageName);
}
}
return true;
}
@Override
public void setDefaultApp(@NonNull String packageName) {
mViewModel.setDefaultApp(packageName);
}
@NonNull
private PF requirePreferenceFragment() {
//noinspection unchecked
return (PF) requireParentFragment();
}
/**
* Interface that the parent fragment must implement.
*/
public interface Parent {
/**
* Set the title of the current settings page.
*
* @param title the title of the current settings page
*/
void setTitle(@NonNull CharSequence title);
/**
* Create a new preference for an application.
*
* @return a new preference for an application
*/
@NonNull
RoleApplicationPreference createApplicationPreference();
/**
* Create a new preference for the footer.
*
* @return a new preference for the footer
*/
@NonNull
Preference createFooterPreference();
/**
* Callback when changes have been made to the {@link PreferenceScreen} of the parent
* {@link PreferenceFragmentCompat}.
*/
void onPreferenceScreenChanged();
}
}