1 /* 2 * Copyright 2018 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.car.apps.common; 18 19 import androidx.annotation.CheckResult; 20 import androidx.annotation.NonNull; 21 import androidx.annotation.Nullable; 22 import androidx.annotation.VisibleForTesting; 23 import androidx.fragment.app.Fragment; 24 import androidx.fragment.app.FragmentActivity; 25 26 import java.util.Objects; 27 28 /** Utility methods for working with Fragments */ 29 public class FragmentUtils { 30 FragmentUtils()31 private FragmentUtils() { 32 // no instances 33 } 34 35 private static Object sParentForTesting; 36 37 @VisibleForTesting(otherwise = VisibleForTesting.NONE) setParentForTesting(Object parentForTesting)38 public static void setParentForTesting(Object parentForTesting) { 39 FragmentUtils.sParentForTesting = parentForTesting; 40 } 41 42 /** 43 * Returns the parent of {@code fragment} that implements the {@code parentType} or {@code null} 44 * if no such parent can be found. 45 */ 46 @CheckResult(suggest = "#checkParent(Fragment, Class)}") 47 @Nullable getParent(@onNull Fragment fragment, @NonNull Class<T> parentType)48 public static <T> T getParent(@NonNull Fragment fragment, @NonNull Class<T> parentType) { 49 if (parentType.isInstance(sParentForTesting)) { 50 @SuppressWarnings("unchecked") // Casts are checked using runtime methods 51 T parent = (T) sParentForTesting; 52 return parent; 53 } 54 55 Fragment parentFragment = fragment.getParentFragment(); 56 if (parentType.isInstance(parentFragment)) { 57 @SuppressWarnings("unchecked") // Casts are checked using runtime methods 58 T parent = (T) parentFragment; 59 return parent; 60 } 61 62 FragmentActivity activity = fragment.getActivity(); 63 if (parentType.isInstance(activity)) { 64 @SuppressWarnings("unchecked") // Casts are checked using runtime methods 65 T parent = (T) activity; 66 return parent; 67 } 68 return null; 69 } 70 71 /** 72 * Returns the parent or throws. Should call {@link #checkParent(Fragment, Class)} or otherwise 73 * enforce parent type elsewhere (e.g. {@link Fragment#onAttach(android.content.Context) 74 * onAttach(Context)} or factory method). 75 */ 76 @CheckResult(suggest = "#checkParent(Fragment, Class)}") 77 @NonNull requireParent(@onNull Fragment fragment, @NonNull Class<T> parentType)78 public static <T> T requireParent(@NonNull Fragment fragment, @NonNull Class<T> parentType) { 79 return Objects.requireNonNull(getParent(fragment, parentType)); 80 } 81 82 /** 83 * Ensures {@code fragment} has a parent that implements the corresponding {@code parentType} 84 * 85 * @param fragment The Fragment whose parents are to be checked 86 * @param parentType The interface class that a parent should implement 87 * @throws AssertionError if no parents are found that implement {@code parentType} 88 */ checkParent(@onNull Fragment fragment, @NonNull Class<?> parentType)89 public static void checkParent(@NonNull Fragment fragment, @NonNull Class<?> parentType) 90 throws AssertionError { 91 if (sParentForTesting != null) { 92 return; 93 } 94 if (FragmentUtils.getParent(fragment, parentType) != null) { 95 return; 96 } 97 String parent; 98 if (fragment.getParentFragment() == null) { 99 if (fragment.getActivity() != null) { 100 parent = fragment.getActivity().getClass().getName(); 101 } else if (fragment.getHost() != null) { 102 parent = fragment.getHost().getClass().getName(); 103 } else { 104 throw new AssertionError(fragment.getClass().getName() 105 + " must be added to a parent that implements " 106 + parentType.getName() 107 + " but is currently unattached to a parent."); 108 } 109 } else { 110 parent = fragment.getParentFragment().getClass().getName(); 111 } 112 throw new AssertionError( 113 fragment.getClass().getName() 114 + " must be added to a parent that implements " 115 + parentType.getName() 116 + ". Instead found " 117 + parent); 118 } 119 } 120