/*
* Copyright (C) 2019 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.specialappaccess;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
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.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.permission.utils.Utils;
import com.android.permissioncontroller.role.ui.ManageRoleHolderStateLiveData;
import com.android.permissioncontroller.role.ui.RoleApplicationPreference;
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;
/**
* Child fragment for a special app access.
*
* 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 SpecialAppAccessChildFragment extends Fragment
implements Preference.OnPreferenceClickListener {
private static final String PREFERENCE_EXTRA_APPLICATION_INFO =
SpecialAppAccessChildFragment.class.getName() + ".extra.APPLICATION_INFO";
private static final String PREFERENCE_KEY_DESCRIPTION =
SpecialAppAccessChildFragment.class.getName() + ".preference.DESCRIPTION";
@NonNull
private String mRoleName;
@NonNull
private Role mRole;
@NonNull
private SpecialAppAccessViewModel mViewModel;
/**
* Create a new instance of this fragment.
*
* @param roleName the name of the role for the special app access
*
* @return a new instance of this fragment
*/
@NonNull
public static SpecialAppAccessChildFragment newInstance(@NonNull String roleName) {
SpecialAppAccessChildFragment fragment = new SpecialAppAccessChildFragment();
Bundle arguments = new Bundle();
arguments.putString(Intent.EXTRA_ROLE_NAME, roleName);
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);
}
@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 SpecialAppAccessViewModel.Factory(mRole,
activity.getApplication())).get(SpecialAppAccessViewModel.class);
mViewModel.getRoleLiveData().observe(this, this::onRoleChanged);
mViewModel.observeManageRoleHolderState(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);
}
}
int qualifyingApplicationsSize = qualifyingApplications.size();
for (int i = 0; i < qualifyingApplicationsSize; i++) {
Pair qualifyingApplication = qualifyingApplications.get(i);
ApplicationInfo qualifyingApplicationInfo = qualifyingApplication.first;
boolean isHolderPackage = qualifyingApplication.second;
String key = qualifyingApplicationInfo.packageName + '_'
+ qualifyingApplicationInfo.uid;
RoleApplicationPreference roleApplicationPreference =
(RoleApplicationPreference) oldPreferences.get(key);
TwoStatePreference preference;
if (roleApplicationPreference == null) {
roleApplicationPreference = preferenceFragment.createApplicationPreference();
preference = roleApplicationPreference.asTwoStatePreference();
preference.setKey(key);
preference.setIcon(Utils.getBadgedIcon(context, qualifyingApplicationInfo));
preference.setTitle(Utils.getFullAppLabel(qualifyingApplicationInfo, context));
preference.setPersistent(false);
preference.setOnPreferenceChangeListener((preference2, newValue) -> false);
preference.setOnPreferenceClickListener(this);
preference.getExtras().putParcelable(PREFERENCE_EXTRA_APPLICATION_INFO,
qualifyingApplicationInfo);
} else {
preference = roleApplicationPreference.asTwoStatePreference();
}
preference.setChecked(isHolderPackage);
UserHandle user = UserHandle.getUserHandleForUid(qualifyingApplicationInfo.uid);
RoleUiBehaviorUtils.prepareApplicationPreferenceAsUser(mRole, roleApplicationPreference,
qualifyingApplicationInfo, user, context);
preferenceScreen.addPreference(preference);
}
Preference descriptionPreference = oldDescriptionPreference;
if (descriptionPreference == null) {
descriptionPreference = preferenceFragment.createFooterPreference();
descriptionPreference.setKey(PREFERENCE_KEY_DESCRIPTION);
descriptionPreference.setSummary(mRole.getDescriptionResource());
}
preferenceScreen.addPreference(descriptionPreference);
preferenceFragment.onPreferenceScreenChanged();
}
private void onManageRoleHolderStateChanged(@NonNull ManageRoleHolderStateLiveData liveData,
int state) {
switch (state) {
case ManageRoleHolderStateLiveData.STATE_SUCCESS:
String packageName = liveData.getLastPackageName();
if (packageName != null && liveData.isLastAdd()) {
mRole.onHolderSelectedAsUser(packageName, liveData.getLastUser(),
requireContext());
}
liveData.resetState();
break;
case ManageRoleHolderStateLiveData.STATE_FAILURE:
liveData.resetState();
break;
}
}
@Override
public boolean onPreferenceClick(@NonNull Preference preference) {
ApplicationInfo applicationInfo = preference.getExtras().getParcelable(
PREFERENCE_EXTRA_APPLICATION_INFO);
String packageName = applicationInfo.packageName;
UserHandle user = UserHandle.getUserHandleForUid(applicationInfo.uid);
boolean allow = !((TwoStatePreference) preference).isChecked();
String key = preference.getKey();
mViewModel.setSpecialAppAccessAsUser(packageName, allow, user, key, this,
this::onManageRoleHolderStateChanged);
return true;
}
@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();
}
}