1 /*
2  * Copyright (C) 2016 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.dialer.common;
18 
19 import android.support.annotation.CheckResult;
20 import android.support.annotation.NonNull;
21 import android.support.annotation.Nullable;
22 import android.support.annotation.VisibleForTesting;
23 import android.support.v4.app.Fragment;
24 import com.android.dialer.main.MainActivityPeer;
25 
26 /** Utility methods for working with Fragments */
27 public class FragmentUtils {
28 
29   private static Object parentForTesting;
30 
31   @VisibleForTesting(otherwise = VisibleForTesting.NONE)
setParentForTesting(Object parentForTesting)32   public static void setParentForTesting(Object parentForTesting) {
33     FragmentUtils.parentForTesting = parentForTesting;
34   }
35 
36   /**
37    * Returns an instance of the {@code callbackInterface} that is defined in the parent of the
38    * {@code fragment}, or null if no such call back can be found.
39    */
40   @CheckResult(suggest = "#checkParent(Fragment, Class)}")
41   @Nullable
getParent(@onNull Fragment fragment, @NonNull Class<T> callbackInterface)42   public static <T> T getParent(@NonNull Fragment fragment, @NonNull Class<T> callbackInterface) {
43     if (callbackInterface.isInstance(parentForTesting)) {
44       @SuppressWarnings("unchecked") // Casts are checked using runtime methods
45       T parent = (T) parentForTesting;
46       return parent;
47     }
48 
49     Fragment parentFragment = fragment.getParentFragment();
50     if (callbackInterface.isInstance(parentFragment)) {
51       @SuppressWarnings("unchecked") // Casts are checked using runtime methods
52       T parent = (T) parentFragment;
53       return parent;
54     } else if (callbackInterface.isInstance(fragment.getActivity())) {
55       @SuppressWarnings("unchecked") // Casts are checked using runtime methods
56       T parent = (T) fragment.getActivity();
57       return parent;
58     } else if (fragment.getActivity() instanceof FragmentUtilListener) {
59       @SuppressWarnings("unchecked") // Casts are checked using runtime methods
60       T parent = ((FragmentUtilListener) fragment.getActivity()).getImpl(callbackInterface);
61       return parent;
62     } else if (fragment.getActivity() instanceof MainActivityPeer.PeerSupplier) {
63       MainActivityPeer peer = ((MainActivityPeer.PeerSupplier) fragment.getActivity()).getPeer();
64       if (peer instanceof FragmentUtilListener) {
65         return ((FragmentUtilListener) peer).getImpl(callbackInterface);
66       }
67     }
68     return null;
69   }
70 
71   /**
72    * Returns an instance of the {@code callbackInterface} that is defined in the parent of the
73    * {@code fragment}, or null if no such call back can be found.
74    */
75   @CheckResult(suggest = "#checkParent(Fragment, Class)}")
76   @Nullable
getParent( @onNull android.app.Fragment fragment, @NonNull Class<T> callbackInterface)77   public static <T> T getParent(
78       @NonNull android.app.Fragment fragment, @NonNull Class<T> callbackInterface) {
79     if (callbackInterface.isInstance(parentForTesting)) {
80       @SuppressWarnings("unchecked") // Casts are checked using runtime methods
81       T parent = (T) parentForTesting;
82       return parent;
83     }
84 
85     android.app.Fragment parentFragment = fragment.getParentFragment();
86     if (callbackInterface.isInstance(parentFragment)) {
87       @SuppressWarnings("unchecked") // Casts are checked using runtime methods
88       T parent = (T) parentFragment;
89       return parent;
90     } else if (callbackInterface.isInstance(fragment.getActivity())) {
91       @SuppressWarnings("unchecked") // Casts are checked using runtime methods
92       T parent = (T) fragment.getActivity();
93       return parent;
94     } else if (fragment.getActivity() instanceof FragmentUtilListener) {
95       @SuppressWarnings("unchecked") // Casts are checked using runtime methods
96       T parent = ((FragmentUtilListener) fragment.getActivity()).getImpl(callbackInterface);
97       return parent;
98     } else if (fragment.getActivity() instanceof MainActivityPeer.PeerSupplier) {
99       MainActivityPeer peer = ((MainActivityPeer.PeerSupplier) fragment.getActivity()).getPeer();
100       if (peer instanceof FragmentUtilListener) {
101         return ((FragmentUtilListener) peer).getImpl(callbackInterface);
102       }
103     }
104     return null;
105   }
106 
107   /** Returns the parent or throws. Should perform check elsewhere(e.g. onAttach, newInstance). */
108   @NonNull
getParentUnsafe( @onNull Fragment fragment, @NonNull Class<T> callbackInterface)109   public static <T> T getParentUnsafe(
110       @NonNull Fragment fragment, @NonNull Class<T> callbackInterface) {
111     return Assert.isNotNull(getParent(fragment, callbackInterface));
112   }
113 
114   /**
115    * Version of {@link #getParentUnsafe(Fragment, Class)} which supports {@link
116    * android.app.Fragment}.
117    */
118   @NonNull
getParentUnsafe( @onNull android.app.Fragment fragment, @NonNull Class<T> callbackInterface)119   public static <T> T getParentUnsafe(
120       @NonNull android.app.Fragment fragment, @NonNull Class<T> callbackInterface) {
121     return Assert.isNotNull(getParent(fragment, callbackInterface));
122   }
123 
124   /**
125    * Ensures fragment has a parent that implements the corresponding interface
126    *
127    * @param frag The Fragment whose parents are to be checked
128    * @param callbackInterface The interface class that a parent should implement
129    * @throws IllegalStateException if no parents are found that implement callbackInterface
130    */
checkParent(@onNull Fragment frag, @NonNull Class<?> callbackInterface)131   public static void checkParent(@NonNull Fragment frag, @NonNull Class<?> callbackInterface)
132       throws IllegalStateException {
133     if (parentForTesting != null) {
134       return;
135     }
136     if (FragmentUtils.getParent(frag, callbackInterface) == null) {
137       String parent =
138           frag.getParentFragment() == null
139               ? frag.getActivity().getClass().getName()
140               : frag.getParentFragment().getClass().getName();
141       throw new IllegalStateException(
142           frag.getClass().getName()
143               + " must be added to a parent"
144               + " that implements "
145               + callbackInterface.getName()
146               + ". Instead found "
147               + parent);
148     }
149   }
150 
151   /** Useful interface for activities that don't want to implement arbitrary listeners. */
152   public interface FragmentUtilListener {
153 
154     /** Returns an implementation of T if parent has one, otherwise null. */
155     @Nullable
getImpl(Class<T> callbackInterface)156     <T> T getImpl(Class<T> callbackInterface);
157   }
158 }
159