• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 
15 package com.android.systemui.fragments;
16 
17 import android.annotation.Nullable;
18 import android.app.Fragment;
19 import android.app.FragmentController;
20 import android.app.FragmentHostCallback;
21 import android.app.FragmentManager;
22 import android.app.FragmentManager.FragmentLifecycleCallbacks;
23 import android.app.FragmentManagerNonConfig;
24 import android.content.Context;
25 import android.content.pm.ActivityInfo;
26 import android.content.res.Configuration;
27 import android.os.Bundle;
28 import android.os.Handler;
29 import android.os.Looper;
30 import android.os.Parcelable;
31 import android.os.Trace;
32 import android.util.ArrayMap;
33 import android.view.LayoutInflater;
34 import android.view.View;
35 
36 import androidx.annotation.NonNull;
37 
38 import com.android.settingslib.applications.InterestingConfigChanges;
39 import com.android.systemui.plugins.Plugin;
40 import com.android.systemui.util.leak.LeakDetector;
41 
42 import java.io.FileDescriptor;
43 import java.io.PrintWriter;
44 import java.lang.reflect.InvocationTargetException;
45 import java.util.ArrayList;
46 import java.util.HashMap;
47 
48 import dagger.assisted.Assisted;
49 import dagger.assisted.AssistedFactory;
50 import dagger.assisted.AssistedInject;
51 
52 public class FragmentHostManager {
53 
54     private final Handler mHandler = new Handler(Looper.getMainLooper());
55     private final Context mContext;
56     private final HashMap<String, ArrayList<FragmentListener>> mListeners = new HashMap<>();
57     private final View mRootView;
58     private final LeakDetector mLeakDetector;
59     private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
60             ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_LOCALE
61                     | ActivityInfo.CONFIG_ASSETS_PATHS);
62     private final FragmentService mManager;
63     private final ExtensionFragmentManager mPlugins = new ExtensionFragmentManager();
64 
65     private FragmentController mFragments;
66     private FragmentLifecycleCallbacks mLifecycleCallbacks;
67 
68     @AssistedInject
FragmentHostManager( @ssisted View rootView, FragmentService manager, LeakDetector leakDetector)69     FragmentHostManager(
70             @Assisted View rootView,
71             FragmentService manager,
72             LeakDetector leakDetector) {
73         mContext = rootView.getContext();
74         mManager = manager;
75         mRootView = rootView;
76         mLeakDetector = leakDetector;
77         mConfigChanges.applyNewConfig(mContext.getResources());
78         createFragmentHost(null);
79     }
80 
81     @AssistedFactory
82     public interface Factory {
create(View rootView)83         FragmentHostManager create(View rootView);
84     }
85 
createFragmentHost(Parcelable savedState)86     private void createFragmentHost(Parcelable savedState) {
87         mFragments = FragmentController.createController(new HostCallbacks());
88         mFragments.attachHost(null);
89         mLifecycleCallbacks = new FragmentLifecycleCallbacks() {
90             @Override
91             public void onFragmentViewCreated(FragmentManager fm, Fragment f, View v,
92                     Bundle savedInstanceState) {
93                 FragmentHostManager.this.onFragmentViewCreated(f);
94             }
95 
96             @Override
97             public void onFragmentViewDestroyed(FragmentManager fm, Fragment f) {
98                 FragmentHostManager.this.onFragmentViewDestroyed(f);
99             }
100 
101             @Override
102             public void onFragmentDestroyed(FragmentManager fm, Fragment f) {
103                 mLeakDetector.trackGarbage(f);
104             }
105         };
106         mFragments.getFragmentManager().registerFragmentLifecycleCallbacks(mLifecycleCallbacks,
107                 true);
108         if (savedState != null) {
109             mFragments.restoreAllState(savedState, (FragmentManagerNonConfig) null);
110         }
111         // For now just keep all fragments in the resumed state.
112         mFragments.dispatchCreate();
113         mFragments.dispatchStart();
114         mFragments.dispatchResume();
115     }
116 
destroyFragmentHost()117     private Parcelable destroyFragmentHost() {
118         mFragments.dispatchPause();
119         Parcelable p = mFragments.saveAllState();
120         mFragments.dispatchStop();
121         mFragments.dispatchDestroy();
122         mFragments.getFragmentManager().unregisterFragmentLifecycleCallbacks(mLifecycleCallbacks);
123         return p;
124     }
125 
126     /**
127      * Add a {@link FragmentListener} for a given tag
128      *
129      * @param tag string identifier for the fragment
130      * @param listener the listener to register
131      *
132      * @return this
133      */
addTagListener( @onNull String tag, @NonNull FragmentListener listener )134     public FragmentHostManager addTagListener(
135             @NonNull String tag,
136             @NonNull FragmentListener listener
137     ) {
138         ArrayList<FragmentListener> listeners = mListeners.get(tag);
139         if (listeners == null) {
140             listeners = new ArrayList<>();
141             mListeners.put(tag, listeners);
142         }
143         listeners.add(listener);
144         Fragment current = getFragmentManager().findFragmentByTag(tag);
145         if (current != null && current.getView() != null) {
146             listener.onFragmentViewCreated(tag, current);
147         }
148         return this;
149     }
150 
151     // Shouldn't generally be needed, included for completeness sake.
removeTagListener(String tag, FragmentListener listener)152     public void removeTagListener(String tag, FragmentListener listener) {
153         ArrayList<FragmentListener> listeners = mListeners.get(tag);
154         if (listeners != null && listeners.remove(listener) && listeners.size() == 0) {
155             mListeners.remove(tag);
156         }
157     }
158 
onFragmentViewCreated(Fragment fragment)159     private void onFragmentViewCreated(Fragment fragment) {
160         String tag = fragment.getTag();
161 
162         ArrayList<FragmentListener> listeners = mListeners.get(tag);
163         if (listeners != null) {
164             listeners.forEach((listener) -> listener.onFragmentViewCreated(tag, fragment));
165         }
166     }
167 
onFragmentViewDestroyed(Fragment fragment)168     private void onFragmentViewDestroyed(Fragment fragment) {
169         String tag = fragment.getTag();
170 
171         ArrayList<FragmentListener> listeners = mListeners.get(tag);
172         if (listeners != null) {
173             listeners.forEach((listener) -> listener.onFragmentViewDestroyed(tag, fragment));
174         }
175     }
176 
177     /**
178      * Called when the configuration changed, return true if the fragments
179      * should be recreated.
180      */
onConfigurationChanged(Configuration newConfig)181     protected void onConfigurationChanged(Configuration newConfig) {
182         if (mConfigChanges.applyNewConfig(mContext.getResources())) {
183             reloadFragments();
184         } else {
185             mFragments.dispatchConfigurationChanged(newConfig);
186         }
187     }
188 
dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)189     private void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
190         // TODO: Do something?
191     }
192 
findViewById(int id)193     private <T extends View> T findViewById(int id) {
194         return mRootView.findViewById(id);
195     }
196 
197     /**
198      * Note: Values from this shouldn't be cached as they can change after config changes.
199      */
getFragmentManager()200     public FragmentManager getFragmentManager() {
201         return mFragments.getFragmentManager();
202     }
203 
getExtensionManager()204     ExtensionFragmentManager getExtensionManager() {
205         return mPlugins;
206     }
207 
destroy()208     void destroy() {
209         mFragments.dispatchDestroy();
210     }
211 
212     /**
213      * Creates a fragment that requires injection.
214      */
create(Class<T> fragmentCls)215     public <T> T create(Class<T> fragmentCls) {
216         return (T) mPlugins.instantiate(mContext, fragmentCls.getName(), null);
217     }
218 
219     public interface FragmentListener {
onFragmentViewCreated(String tag, Fragment fragment)220         void onFragmentViewCreated(String tag, Fragment fragment);
221 
222         // The facts of lifecycle
223         // When a fragment is destroyed, you should not talk to it any longer.
onFragmentViewDestroyed(String tag, Fragment fragment)224         default void onFragmentViewDestroyed(String tag, Fragment fragment) {
225         }
226     }
227 
reloadFragments()228     public void reloadFragments() {
229         Trace.beginSection("FrargmentHostManager#reloadFragments");
230         // Save the old state.
231         Parcelable p = destroyFragmentHost();
232         // Generate a new fragment host and restore its state.
233         createFragmentHost(p);
234         Trace.endSection();
235     }
236 
237     class HostCallbacks extends FragmentHostCallback<FragmentHostManager> {
HostCallbacks()238         public HostCallbacks() {
239             super(mContext, FragmentHostManager.this.mHandler, 0);
240         }
241 
242         @Override
onGetHost()243         public FragmentHostManager onGetHost() {
244             return FragmentHostManager.this;
245         }
246 
247         @Override
onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)248         public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
249             FragmentHostManager.this.dump(prefix, fd, writer, args);
250         }
251 
252         @Override
instantiate(Context context, String className, Bundle arguments)253         public Fragment instantiate(Context context, String className, Bundle arguments) {
254             return mPlugins.instantiate(context, className, arguments);
255         }
256 
257         @Override
onShouldSaveFragmentState(Fragment fragment)258         public boolean onShouldSaveFragmentState(Fragment fragment) {
259             return true; // True for now.
260         }
261 
262         @Override
onGetLayoutInflater()263         public LayoutInflater onGetLayoutInflater() {
264             return LayoutInflater.from(mContext);
265         }
266 
267         @Override
onUseFragmentManagerInflaterFactory()268         public boolean onUseFragmentManagerInflaterFactory() {
269             return true;
270         }
271 
272         @Override
onHasWindowAnimations()273         public boolean onHasWindowAnimations() {
274             return false;
275         }
276 
277         @Override
onGetWindowAnimations()278         public int onGetWindowAnimations() {
279             return 0;
280         }
281 
282         @Override
onAttachFragment(Fragment fragment)283         public void onAttachFragment(Fragment fragment) {
284         }
285 
286         @Override
287         @Nullable
onFindViewById(int id)288         public <T extends View> T onFindViewById(int id) {
289             return FragmentHostManager.this.findViewById(id);
290         }
291 
292         @Override
onHasView()293         public boolean onHasView() {
294             return true;
295         }
296     }
297 
298     class ExtensionFragmentManager {
299         private final ArrayMap<String, Context> mExtensionLookup = new ArrayMap<>();
300 
setCurrentExtension(int id, @NonNull String tag, @Nullable String oldClass, @NonNull String currentClass, @Nullable Context context)301         public void setCurrentExtension(int id, @NonNull  String tag, @Nullable String oldClass,
302                 @NonNull String currentClass, @Nullable Context context) {
303             if (oldClass != null) {
304                 mExtensionLookup.remove(oldClass);
305             }
306             mExtensionLookup.put(currentClass, context);
307             getFragmentManager().beginTransaction()
308                     .replace(id, instantiate(context, currentClass, null), tag)
309                     .commit();
310             reloadFragments();
311         }
312 
instantiate(Context context, String className, Bundle arguments)313         Fragment instantiate(Context context, String className, Bundle arguments) {
314             Context extensionContext = mExtensionLookup.get(className);
315             if (extensionContext != null) {
316                 Fragment f = instantiateWithInjections(extensionContext, className, arguments);
317                 if (f instanceof Plugin) {
318                     ((Plugin) f).onCreate(mContext, extensionContext);
319                 }
320                 return f;
321             }
322             return instantiateWithInjections(context, className, arguments);
323         }
324 
instantiateWithInjections( Context context, String className, Bundle args)325         private Fragment instantiateWithInjections(
326                 Context context, String className, Bundle args) {
327             FragmentService.FragmentInstantiationInfo fragmentInstantiationInfo =
328                     mManager.getInjectionMap().get(className);
329             if (fragmentInstantiationInfo != null) {
330                 try {
331                     Fragment f = (Fragment) fragmentInstantiationInfo
332                             .mMethod
333                             .invoke(fragmentInstantiationInfo.mDaggerComponent);
334                     // Setup the args, taken from Fragment#instantiate.
335                     if (args != null) {
336                         args.setClassLoader(f.getClass().getClassLoader());
337                         f.setArguments(args);
338                     }
339                     return f;
340                 } catch (IllegalAccessException | InvocationTargetException e) {
341                     throw new Fragment.InstantiationException("Unable to instantiate " + className,
342                             e);
343                 }
344             }
345             return Fragment.instantiate(context, className, args);
346         }
347     }
348 
349     private static class PluginState {
350         Context mContext;
351         String mCls;
352 
PluginState(String cls, Context context)353         private PluginState(String cls, Context context) {
354             mCls = cls;
355             mContext = context;
356         }
357     }
358 }
359