1 /* 2 * Copyright (C) 2020 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.permissioncontroller.permission.ui.television; 18 19 import static android.Manifest.permission_group.STORAGE; 20 21 import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID; 22 import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID; 23 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW; 24 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_ALWAYS; 25 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_FOREGROUND; 26 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ASK_EVERY_TIME; 27 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY; 28 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY_FOREGROUND; 29 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED; 30 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED_DO_NOT_ASK_AGAIN; 31 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ALWAYS; 32 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_FOREGROUND_ONLY; 33 import static com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_CALLER_NAME; 34 import static com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_INTERACTED; 35 import static com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_RESULT; 36 import static com.android.permissioncontroller.permission.ui.handheld.UtilsKt.pressBack; 37 38 import android.app.Activity; 39 import android.app.AlertDialog; 40 import android.app.Dialog; 41 import android.content.Context; 42 import android.content.DialogInterface; 43 import android.content.Intent; 44 import android.content.pm.PackageInfo; 45 import android.content.pm.PackageManager; 46 import android.graphics.drawable.Drawable; 47 import android.os.Bundle; 48 import android.os.Handler; 49 import android.os.Looper; 50 import android.os.UserHandle; 51 import android.text.BidiFormatter; 52 import android.util.Log; 53 import android.view.LayoutInflater; 54 import android.view.View; 55 import android.view.ViewGroup; 56 import android.widget.Toast; 57 58 import androidx.annotation.NonNull; 59 import androidx.annotation.Nullable; 60 import androidx.annotation.StringRes; 61 import androidx.fragment.app.DialogFragment; 62 import androidx.preference.Preference; 63 import androidx.preference.PreferenceScreen; 64 import androidx.preference.PreferenceViewHolder; 65 import androidx.lifecycle.ViewModelProvider; 66 67 import com.android.permissioncontroller.permission.model.AppPermissionGroup; 68 import com.android.permissioncontroller.permission.model.AppPermissions; 69 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler; 70 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel; 71 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonState; 72 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType; 73 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ChangeRequest; 74 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModelFactory; 75 import com.android.permissioncontroller.permission.utils.KotlinUtils; 76 import com.android.permissioncontroller.permission.utils.Utils; 77 import com.android.permissioncontroller.R; 78 79 import java.util.Map; 80 import java.util.Objects; 81 82 /** 83 * Show and manage a single permission group for an app. 84 * 85 * <p>Allows the user to control whether the app is granted the permission. 86 */ 87 public class AppPermissionFragment extends SettingsWithHeader 88 implements AppPermissionViewModel.ConfirmDialogShowingFragment { 89 private static final String LOG_TAG = "AppPermissionFragment"; 90 private static final long POST_DELAY_MS = 20; 91 92 static final String GRANT_CATEGORY = "grant_category"; 93 94 private @NonNull AppPermissionViewModel mViewModel; 95 private @NonNull RadioButtonPreference mAllowButton; 96 private @NonNull RadioButtonPreference mAllowAlwaysButton; 97 private @NonNull RadioButtonPreference mAllowForegroundButton; 98 private @NonNull RadioButtonPreference mAskOneTimeButton; 99 private @NonNull RadioButtonPreference mAskButton; 100 private @NonNull RadioButtonPreference mDenyButton; 101 private @NonNull RadioButtonPreference mDenyForegroundButton; 102 private @NonNull String mPackageName; 103 private @NonNull String mPermGroupName; 104 private @NonNull UserHandle mUser; 105 private boolean mIsStorageGroup; 106 private boolean mIsInitialLoad; 107 private long mSessionId; 108 109 private @NonNull String mPackageLabel; 110 private @NonNull String mPermGroupLabel; 111 private Drawable mPackageIcon; 112 private Utils.ForegroundCapableType mForegroundCapableType; 113 114 /** 115 * Create a bundle with the arguments needed by this fragment 116 * 117 * @param packageName The name of the package 118 * @param permName The name of the permission whose group this fragment is for (optional) 119 * @param groupName The name of the permission group (required if permName not specified) 120 * @param userHandle The user of the app permission group 121 * @param caller The name of the fragment we called from 122 * @param sessionId The current session ID 123 * @param grantCategory The grant status of this app permission group. Used to initially set 124 * the button state 125 * @return A bundle with all of the args placed 126 */ createArgs(@onNull String packageName, @Nullable String permName, @Nullable String groupName, @NonNull UserHandle userHandle, @Nullable String caller, long sessionId, @Nullable String grantCategory)127 public static Bundle createArgs(@NonNull String packageName, 128 @Nullable String permName, @Nullable String groupName, 129 @NonNull UserHandle userHandle, @Nullable String caller, long sessionId, @Nullable 130 String grantCategory) { 131 Bundle arguments = new Bundle(); 132 arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName); 133 if (groupName == null) { 134 arguments.putString(Intent.EXTRA_PERMISSION_NAME, permName); 135 } else { 136 arguments.putString(Intent.EXTRA_PERMISSION_GROUP_NAME, groupName); 137 } 138 arguments.putParcelable(Intent.EXTRA_USER, userHandle); 139 arguments.putString(EXTRA_CALLER_NAME, caller); 140 arguments.putLong(EXTRA_SESSION_ID, sessionId); 141 arguments.putString(GRANT_CATEGORY, grantCategory); 142 return arguments; 143 } 144 145 @Override onCreate(Bundle savedInstanceState)146 public void onCreate(Bundle savedInstanceState) { 147 super.onCreate(savedInstanceState); 148 149 mPackageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME); 150 mPermGroupName = getArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME); 151 if (mPermGroupName == null) { 152 mPermGroupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME); 153 } 154 if (mPackageName == null || mPermGroupName == null) { 155 if (mPackageName == null) { 156 Log.e(LOG_TAG, "Package name is null: " + Intent.EXTRA_PACKAGE_NAME); 157 } 158 if (mPermGroupName == null) { 159 Log.e(LOG_TAG, "Permission group is null: " + Intent.EXTRA_PERMISSION_GROUP_NAME); 160 } 161 final Activity activity = getActivity(); 162 Toast.makeText(activity, R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show(); 163 activity.finish(); 164 return; 165 } 166 mIsStorageGroup = Objects.equals(mPermGroupName, STORAGE); 167 mUser = getArguments().getParcelable(Intent.EXTRA_USER); 168 mPackageLabel = BidiFormatter.getInstance().unicodeWrap( 169 KotlinUtils.INSTANCE.getPackageLabel(getActivity().getApplication(), mPackageName, 170 mUser)); 171 mPermGroupLabel = KotlinUtils.INSTANCE.getPermGroupLabel(getContext(), 172 mPermGroupName).toString(); 173 mPackageIcon = KotlinUtils.INSTANCE.getBadgedPackageIcon(getActivity().getApplication(), 174 mPackageName, mUser); 175 try { 176 mForegroundCapableType = Utils.getForegroundCapableType(getContext(), mPackageName); 177 } catch (PackageManager.NameNotFoundException e) { 178 Log.e(LOG_TAG, "Package " + mPackageName + " not found", e); 179 } 180 181 mSessionId = getArguments().getLong(EXTRA_SESSION_ID, INVALID_SESSION_ID); 182 183 AppPermissionViewModelFactory factory = new AppPermissionViewModelFactory( 184 getActivity().getApplication(), mPackageName, mPermGroupName, mUser, mSessionId, 185 mForegroundCapableType); 186 mViewModel = new ViewModelProvider(this, factory).get(AppPermissionViewModel.class); 187 Handler delayHandler = new Handler(Looper.getMainLooper()); 188 mViewModel.getButtonStateLiveData().observe(this, buttonState -> { 189 if (mIsInitialLoad) { 190 setRadioButtonsState(buttonState); 191 } else { 192 delayHandler.removeCallbacksAndMessages(null); 193 delayHandler.postDelayed(() -> setRadioButtonsState(buttonState), POST_DELAY_MS); 194 } 195 }); 196 } 197 198 @Override onViewCreated(View view, Bundle savedInstanceState)199 public void onViewCreated(View view, Bundle savedInstanceState) { 200 super.onViewCreated(view, savedInstanceState); 201 mIsInitialLoad = true; 202 setHeader(mPackageIcon, mPackageLabel, null, 203 getString(R.string.app_permissions_decor_title)); 204 createPreferences(); 205 updatePreferences(); 206 } 207 208 @Override onResume()209 public void onResume() { 210 super.onResume(); 211 updatePreferences(); 212 } 213 createPreferences()214 public void createPreferences() { 215 PreferenceScreen screen = getPreferenceScreen(); 216 Context context = getContext(); 217 screen.removeAll(); 218 219 PackageInfo packageInfo = getPackageInfo(getActivity(), mPackageName); 220 AppPermissions appPermissions = new AppPermissions(getActivity(), packageInfo, true, 221 () -> getActivity().finish()); 222 AppPermissionGroup group = appPermissions.getPermissionGroup(mPermGroupName); 223 Drawable icon = Utils.loadDrawable(context.getPackageManager(), 224 group.getIconPkg(), group.getIconResId()); 225 226 screen.addPreference(createHeaderLineTwoPreference(context)); 227 228 Preference permHeader = new Preference(context); 229 permHeader.setTitle(mPermGroupLabel); 230 permHeader.setSummary(context.getString(R.string.app_permission_header, mPermGroupLabel)); 231 permHeader.setSelectable(false); 232 permHeader.setIcon(Utils.applyTint(getContext(), icon, android.R.attr.colorControlNormal)); 233 screen.addPreference(permHeader); 234 235 mAllowButton = new RadioButtonPreference(context, R.string.app_permission_button_allow); 236 mAllowAlwaysButton = 237 new RadioButtonPreference(context, R.string.app_permission_button_allow_always); 238 mAllowForegroundButton = 239 new RadioButtonPreference(context, R.string.app_permission_button_allow_foreground); 240 mAskOneTimeButton = new RadioButtonPreference(context, R.string.app_permission_button_ask); 241 mAskButton = new RadioButtonPreference(context, R.string.app_permission_button_ask); 242 mDenyButton = new RadioButtonPreference(context, R.string.app_permission_button_deny); 243 mDenyForegroundButton = 244 new RadioButtonPreference(context, R.string.app_permission_button_deny); 245 246 for (Preference preference : new Preference[] { 247 mAllowButton, 248 mAllowAlwaysButton, 249 mAllowForegroundButton, 250 mAskOneTimeButton, 251 mAskButton, 252 mDenyButton, 253 mDenyForegroundButton}) { 254 preference.setVisible(false); 255 preference.setIcon(android.R.color.transparent); 256 screen.addPreference(preference); 257 } 258 } 259 updatePreferences()260 public void updatePreferences() { 261 if (mViewModel.getButtonStateLiveData().getValue() != null) { 262 setRadioButtonsState(mViewModel.getButtonStateLiveData().getValue()); 263 } 264 } 265 setRadioButtonsState(Map<ButtonType, ButtonState> states)266 private void setRadioButtonsState(Map<ButtonType, ButtonState> states) { 267 if (states == null && mViewModel.getButtonStateLiveData().isInitialized()) { 268 pressBack(this); 269 Log.w(LOG_TAG, "invalid package " + mPackageName + " or perm group " 270 + mPermGroupName); 271 Toast.makeText( 272 getActivity(), R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show(); 273 return; 274 } else if (states == null) { 275 return; 276 } 277 278 mAllowButton.setOnPreferenceClickListener((v) -> { 279 mViewModel.requestChange(false, this, this, ChangeRequest.GRANT_FOREGROUND, 280 APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW); 281 setResult(GRANTED_ALWAYS); 282 return false; 283 }); 284 mAllowAlwaysButton.setOnPreferenceClickListener((v) -> { 285 if (mIsStorageGroup) { 286 showConfirmDialog(ChangeRequest.GRANT_All_FILE_ACCESS, 287 R.string.special_file_access_dialog, -1, false); 288 } else { 289 mViewModel.requestChange(false, this, this, ChangeRequest.GRANT_BOTH, 290 APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_ALWAYS); 291 } 292 setResult(GRANTED_ALWAYS); 293 return false; 294 }); 295 mAllowForegroundButton.setOnPreferenceClickListener((v) -> { 296 if (mIsStorageGroup) { 297 mViewModel.setAllFilesAccess(false); 298 mViewModel.requestChange(false, this, this, ChangeRequest.GRANT_BOTH, 299 APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW); 300 setResult(GRANTED_ALWAYS); 301 return false; 302 } else { 303 mViewModel.requestChange(false, this, this, ChangeRequest.GRANT_FOREGROUND_ONLY, 304 APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_FOREGROUND); 305 setResult(GRANTED_FOREGROUND_ONLY); 306 return false; 307 } 308 }); 309 // mAskOneTimeButton only shows if checked hence should do nothing 310 mAskButton.setOnPreferenceClickListener((v) -> { 311 mViewModel.requestChange(true, this, this, ChangeRequest.REVOKE_BOTH, 312 APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ASK_EVERY_TIME); 313 setResult(DENIED); 314 return false; 315 }); 316 mDenyButton.setOnPreferenceClickListener((v) -> { 317 mViewModel.requestChange(false, this, this, ChangeRequest.REVOKE_BOTH, 318 APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY); 319 setResult(DENIED_DO_NOT_ASK_AGAIN); 320 return false; 321 }); 322 mDenyForegroundButton.setOnPreferenceClickListener((v) -> { 323 mViewModel.requestChange(false, this, this, ChangeRequest.REVOKE_FOREGROUND, 324 APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY_FOREGROUND); 325 setResult(DENIED_DO_NOT_ASK_AGAIN); 326 return false; 327 }); 328 329 setButtonState(mAllowButton, states.get(ButtonType.ALLOW)); 330 setButtonState(mAllowAlwaysButton, states.get(ButtonType.ALLOW_ALWAYS)); 331 setButtonState(mAllowForegroundButton, states.get(ButtonType.ALLOW_FOREGROUND)); 332 setButtonState(mAskOneTimeButton, states.get(ButtonType.ASK_ONCE)); 333 setButtonState(mAskButton, states.get(ButtonType.ASK)); 334 setButtonState(mDenyButton, states.get(ButtonType.DENY)); 335 setButtonState(mDenyForegroundButton, states.get(ButtonType.DENY_FOREGROUND)); 336 337 mIsInitialLoad = false; 338 } 339 setButtonState(RadioButtonPreference button, AppPermissionViewModel.ButtonState state)340 private void setButtonState(RadioButtonPreference button, AppPermissionViewModel.ButtonState state) { 341 button.setVisible(state.isShown()); 342 if (state.isShown()) { 343 button.setChecked(state.isChecked()); 344 button.setEnabled(state.isEnabled()); 345 } 346 if (state.isShown() && state.isChecked()) { 347 scrollToPreference(button); 348 } 349 } 350 /** 351 * Creates a heading below decor_title and above the rest of the preferences. This heading 352 * displays the app name and banner icon. It's used in both system and additional permissions 353 * fragments for each app. The styling used is the same as a leanback preference with a 354 * customized background color 355 * @param context The context the preferences created on 356 * @return The preference header to be inserted as the first preference in the list. 357 */ createHeaderLineTwoPreference(Context context)358 private Preference createHeaderLineTwoPreference(Context context) { 359 Preference headerLineTwo = new Preference(context) { 360 @Override 361 public void onBindViewHolder(PreferenceViewHolder holder) { 362 super.onBindViewHolder(holder); 363 holder.itemView.setBackgroundColor( 364 getResources().getColor(R.color.lb_header_banner_color)); 365 } 366 }; 367 headerLineTwo.setKey(HEADER_PREFERENCE_KEY); 368 headerLineTwo.setSelectable(false); 369 headerLineTwo.setTitle(mPackageLabel); 370 headerLineTwo.setIcon(mPackageIcon); 371 return headerLineTwo; 372 } 373 getPackageInfo(Activity activity, String packageName)374 private static PackageInfo getPackageInfo(Activity activity, String packageName) { 375 try { 376 return activity.getPackageManager().getPackageInfo( 377 packageName, PackageManager.GET_PERMISSIONS); 378 } catch (PackageManager.NameNotFoundException e) { 379 Log.i(LOG_TAG, "No package:" + activity.getCallingPackage(), e); 380 return null; 381 } 382 } 383 setResult(@rantPermissionsViewHandler.Result int result)384 private void setResult(@GrantPermissionsViewHandler.Result int result) { 385 Intent intent = new Intent() 386 .putExtra(EXTRA_RESULT_PERMISSION_INTERACTED, mPermGroupName) 387 .putExtra(EXTRA_RESULT_PERMISSION_RESULT, result); 388 getActivity().setResult(Activity.RESULT_OK, intent); 389 } 390 391 /** 392 * Show a dialog that warns the user that they are about to revoke permissions that were 393 * granted by default, or that they are about to grant full file access to an app. 394 * 395 * 396 * The order of operation to revoke a permission granted by default is: 397 * 1. `showConfirmDialog` 398 * 1. [ConfirmDialog.onCreateDialog] 399 * 1. [AppPermissionViewModel.onDenyAnyWay] or [AppPermissionViewModel.onConfirmFileAccess] 400 * TODO: Remove once data can be passed between dialogs and fragments with nav component 401 * 402 * @param changeRequest Whether background or foreground should be changed 403 * @param messageId The Id of the string message to show 404 * @param buttonPressed Button which was pressed to initiate the dialog, one of 405 * AppPermissionFragmentActionReported.button_pressed constants 406 * @param oneTime Whether the one-time (ask) button was clicked rather than the deny 407 * button 408 */ 409 @Override showConfirmDialog(ChangeRequest changeRequest, @StringRes int messageId, int buttonPressed, boolean oneTime)410 public void showConfirmDialog(ChangeRequest changeRequest, @StringRes int messageId, 411 int buttonPressed, boolean oneTime) { 412 Bundle args = getArguments().deepCopy(); 413 args.putInt(ConfirmDialog.MSG, messageId); 414 args.putSerializable(ConfirmDialog.CHANGE_REQUEST, changeRequest); 415 args.putInt(ConfirmDialog.BUTTON, buttonPressed); 416 args.putBoolean(ConfirmDialog.ONE_TIME, oneTime); 417 ConfirmDialog defaultDenyDialog = new ConfirmDialog(); 418 defaultDenyDialog.setCancelable(true); 419 defaultDenyDialog.setArguments(args); 420 defaultDenyDialog.setTargetFragment(this, 0); 421 defaultDenyDialog.show(getFragmentManager(), 422 ConfirmDialog.class.getName()); 423 } 424 425 /** 426 * A dialog warning the user that they are about to deny a permission that was granted by 427 * default, or that they are denying a permission on a Pre-M app 428 * 429 * @see AppPermissionViewModel.ConfirmDialogShowingFragment#showConfirmDialog(ChangeRequest, 430 * int, int, boolean) 431 * @see #showConfirmDialog(ChangeRequest, int, int) 432 */ 433 public static class ConfirmDialog extends DialogFragment { 434 static final String MSG = ConfirmDialog.class.getName() + ".arg.msg"; 435 static final String CHANGE_REQUEST = ConfirmDialog.class.getName() 436 + ".arg.changeRequest"; 437 private static final String KEY = ConfirmDialog.class.getName() + ".arg.key"; 438 private static final String BUTTON = ConfirmDialog.class.getName() + ".arg.button"; 439 private static final String ONE_TIME = ConfirmDialog.class.getName() + ".arg.onetime"; 440 441 @Override onCreateDialog(Bundle savedInstanceState)442 public Dialog onCreateDialog(Bundle savedInstanceState) { 443 AppPermissionFragment fragment = (AppPermissionFragment) getTargetFragment(); 444 boolean isGrantFileAccess = getArguments().getSerializable(CHANGE_REQUEST) 445 == ChangeRequest.GRANT_All_FILE_ACCESS; 446 int positiveButtonStringResId = R.string.grant_dialog_button_deny_anyway; 447 if (isGrantFileAccess) { 448 positiveButtonStringResId = R.string.grant_dialog_button_allow; 449 } 450 AlertDialog.Builder b = new AlertDialog.Builder(getContext()) 451 .setMessage(getArguments().getInt(MSG)) 452 .setNegativeButton(R.string.cancel, 453 (DialogInterface dialog, int which) -> dialog.cancel()) 454 .setPositiveButton(positiveButtonStringResId, 455 (DialogInterface dialog, int which) -> { 456 if (isGrantFileAccess) { 457 fragment.mViewModel.setAllFilesAccess(true); 458 } else { 459 fragment.mViewModel.onDenyAnyWay((ChangeRequest) 460 getArguments().getSerializable(CHANGE_REQUEST), 461 getArguments().getInt(BUTTON), 462 getArguments().getBoolean(ONE_TIME)); 463 } 464 }); 465 Dialog d = b.create(); 466 d.setCanceledOnTouchOutside(true); 467 return d; 468 } 469 470 @Override onCancel(DialogInterface dialog)471 public void onCancel(DialogInterface dialog) { 472 AppPermissionFragment fragment = (AppPermissionFragment) getTargetFragment(); 473 fragment.setRadioButtonsState(fragment.mViewModel.getButtonStateLiveData().getValue()); 474 } 475 } 476 } 477