Home | History | Annotate | Download | only in fragments
      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.util.ArrayMap;
     32 import android.view.LayoutInflater;
     33 import android.view.View;
     34 
     35 import androidx.annotation.NonNull;
     36 
     37 import com.android.settingslib.applications.InterestingConfigChanges;
     38 import com.android.systemui.Dependency;
     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.lang.reflect.Method;
     46 import java.util.ArrayList;
     47 import java.util.HashMap;
     48 
     49 public class FragmentHostManager {
     50 
     51     private final Handler mHandler = new Handler(Looper.getMainLooper());
     52     private final Context mContext;
     53     private final HashMap<String, ArrayList<FragmentListener>> mListeners = new HashMap<>();
     54     private final View mRootView;
     55     private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
     56             ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_LOCALE
     57                 | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS
     58                 | ActivityInfo.CONFIG_UI_MODE);
     59     private final FragmentService mManager;
     60     private final ExtensionFragmentManager mPlugins = new ExtensionFragmentManager();
     61 
     62     private FragmentController mFragments;
     63     private FragmentLifecycleCallbacks mLifecycleCallbacks;
     64 
     65     FragmentHostManager(FragmentService manager, View rootView) {
     66         mContext = rootView.getContext();
     67         mManager = manager;
     68         mRootView = rootView;
     69         mConfigChanges.applyNewConfig(mContext.getResources());
     70         createFragmentHost(null);
     71     }
     72 
     73     private void createFragmentHost(Parcelable savedState) {
     74         mFragments = FragmentController.createController(new HostCallbacks());
     75         mFragments.attachHost(null);
     76         mLifecycleCallbacks = new FragmentLifecycleCallbacks() {
     77             @Override
     78             public void onFragmentViewCreated(FragmentManager fm, Fragment f, View v,
     79                     Bundle savedInstanceState) {
     80                 FragmentHostManager.this.onFragmentViewCreated(f);
     81             }
     82 
     83             @Override
     84             public void onFragmentViewDestroyed(FragmentManager fm, Fragment f) {
     85                 FragmentHostManager.this.onFragmentViewDestroyed(f);
     86             }
     87 
     88             @Override
     89             public void onFragmentDestroyed(FragmentManager fm, Fragment f) {
     90                 Dependency.get(LeakDetector.class).trackGarbage(f);
     91             }
     92         };
     93         mFragments.getFragmentManager().registerFragmentLifecycleCallbacks(mLifecycleCallbacks,
     94                 true);
     95         if (savedState != null) {
     96             mFragments.restoreAllState(savedState, (FragmentManagerNonConfig) null);
     97         }
     98         // For now just keep all fragments in the resumed state.
     99         mFragments.dispatchCreate();
    100         mFragments.dispatchStart();
    101         mFragments.dispatchResume();
    102     }
    103 
    104     private Parcelable destroyFragmentHost() {
    105         mFragments.dispatchPause();
    106         Parcelable p = mFragments.saveAllState();
    107         mFragments.dispatchStop();
    108         mFragments.dispatchDestroy();
    109         mFragments.getFragmentManager().unregisterFragmentLifecycleCallbacks(mLifecycleCallbacks);
    110         return p;
    111     }
    112 
    113     public FragmentHostManager addTagListener(String tag, FragmentListener listener) {
    114         ArrayList<FragmentListener> listeners = mListeners.get(tag);
    115         if (listeners == null) {
    116             listeners = new ArrayList<>();
    117             mListeners.put(tag, listeners);
    118         }
    119         listeners.add(listener);
    120         Fragment current = getFragmentManager().findFragmentByTag(tag);
    121         if (current != null && current.getView() != null) {
    122             listener.onFragmentViewCreated(tag, current);
    123         }
    124         return this;
    125     }
    126 
    127     // Shouldn't generally be needed, included for completeness sake.
    128     public void removeTagListener(String tag, FragmentListener listener) {
    129         ArrayList<FragmentListener> listeners = mListeners.get(tag);
    130         if (listeners != null && listeners.remove(listener) && listeners.size() == 0) {
    131             mListeners.remove(tag);
    132         }
    133     }
    134 
    135     private void onFragmentViewCreated(Fragment fragment) {
    136         String tag = fragment.getTag();
    137 
    138         ArrayList<FragmentListener> listeners = mListeners.get(tag);
    139         if (listeners != null) {
    140             listeners.forEach((listener) -> listener.onFragmentViewCreated(tag, fragment));
    141         }
    142     }
    143 
    144     private void onFragmentViewDestroyed(Fragment fragment) {
    145         String tag = fragment.getTag();
    146 
    147         ArrayList<FragmentListener> listeners = mListeners.get(tag);
    148         if (listeners != null) {
    149             listeners.forEach((listener) -> listener.onFragmentViewDestroyed(tag, fragment));
    150         }
    151     }
    152 
    153     /**
    154      * Called when the configuration changed, return true if the fragments
    155      * should be recreated.
    156      */
    157     protected void onConfigurationChanged(Configuration newConfig) {
    158         if (mConfigChanges.applyNewConfig(mContext.getResources())) {
    159             reloadFragments();
    160         } else {
    161             mFragments.dispatchConfigurationChanged(newConfig);
    162         }
    163     }
    164 
    165     private void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
    166         // TODO: Do something?
    167     }
    168 
    169     private <T extends View> T findViewById(int id) {
    170         return mRootView.findViewById(id);
    171     }
    172 
    173     /**
    174      * Note: Values from this shouldn't be cached as they can change after config changes.
    175      */
    176     public FragmentManager getFragmentManager() {
    177         return mFragments.getFragmentManager();
    178     }
    179 
    180     ExtensionFragmentManager getExtensionManager() {
    181         return mPlugins;
    182     }
    183 
    184     void destroy() {
    185         mFragments.dispatchDestroy();
    186     }
    187 
    188     /**
    189      * Creates a fragment that requires injection.
    190      */
    191     public <T> T create(Class<T> fragmentCls) {
    192         return (T) mPlugins.instantiate(mContext, fragmentCls.getName(), null);
    193     }
    194 
    195     public interface FragmentListener {
    196         void onFragmentViewCreated(String tag, Fragment fragment);
    197 
    198         // The facts of lifecycle
    199         // When a fragment is destroyed, you should not talk to it any longer.
    200         default void onFragmentViewDestroyed(String tag, Fragment fragment) {
    201         }
    202     }
    203 
    204     public static FragmentHostManager get(View view) {
    205         try {
    206             return Dependency.get(FragmentService.class).getFragmentHostManager(view);
    207         } catch (ClassCastException e) {
    208             // TODO: Some auto handling here?
    209             throw e;
    210         }
    211     }
    212 
    213     public static void removeAndDestroy(View view) {
    214         Dependency.get(FragmentService.class).removeAndDestroy(view);
    215     }
    216 
    217     public void reloadFragments() {
    218         // Save the old state.
    219         Parcelable p = destroyFragmentHost();
    220         // Generate a new fragment host and restore its state.
    221         createFragmentHost(p);
    222     }
    223 
    224     class HostCallbacks extends FragmentHostCallback<FragmentHostManager> {
    225         public HostCallbacks() {
    226             super(mContext, FragmentHostManager.this.mHandler, 0);
    227         }
    228 
    229         @Override
    230         public FragmentHostManager onGetHost() {
    231             return FragmentHostManager.this;
    232         }
    233 
    234         @Override
    235         public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
    236             FragmentHostManager.this.dump(prefix, fd, writer, args);
    237         }
    238 
    239         @Override
    240         public Fragment instantiate(Context context, String className, Bundle arguments) {
    241             return mPlugins.instantiate(context, className, arguments);
    242         }
    243 
    244         @Override
    245         public boolean onShouldSaveFragmentState(Fragment fragment) {
    246             return true; // True for now.
    247         }
    248 
    249         @Override
    250         public LayoutInflater onGetLayoutInflater() {
    251             return LayoutInflater.from(mContext);
    252         }
    253 
    254         @Override
    255         public boolean onUseFragmentManagerInflaterFactory() {
    256             return true;
    257         }
    258 
    259         @Override
    260         public boolean onHasWindowAnimations() {
    261             return false;
    262         }
    263 
    264         @Override
    265         public int onGetWindowAnimations() {
    266             return 0;
    267         }
    268 
    269         @Override
    270         public void onAttachFragment(Fragment fragment) {
    271         }
    272 
    273         @Override
    274         @Nullable
    275         public <T extends View> T onFindViewById(int id) {
    276             return FragmentHostManager.this.findViewById(id);
    277         }
    278 
    279         @Override
    280         public boolean onHasView() {
    281             return true;
    282         }
    283     }
    284 
    285     class ExtensionFragmentManager {
    286         private final ArrayMap<String, Context> mExtensionLookup = new ArrayMap<>();
    287 
    288         public void setCurrentExtension(int id, @NonNull  String tag, @Nullable String oldClass,
    289                 @NonNull String currentClass, @Nullable Context context) {
    290             if (oldClass != null) {
    291                 mExtensionLookup.remove(oldClass);
    292             }
    293             mExtensionLookup.put(currentClass, context);
    294             getFragmentManager().beginTransaction()
    295                     .replace(id, instantiate(context, currentClass, null), tag)
    296                     .commit();
    297             reloadFragments();
    298         }
    299 
    300         Fragment instantiate(Context context, String className, Bundle arguments) {
    301             Context extensionContext = mExtensionLookup.get(className);
    302             if (extensionContext != null) {
    303                 Fragment f = instantiateWithInjections(extensionContext, className, arguments);
    304                 if (f instanceof Plugin) {
    305                     ((Plugin) f).onCreate(mContext, extensionContext);
    306                 }
    307                 return f;
    308             }
    309             return instantiateWithInjections(context, className, arguments);
    310         }
    311 
    312         private Fragment instantiateWithInjections(Context context, String className,
    313                 Bundle args) {
    314             Method method = mManager.getInjectionMap().get(className);
    315             if (method != null) {
    316                 try {
    317                     Fragment f = (Fragment) method.invoke(mManager.getFragmentCreator());
    318                     // Setup the args, taken from Fragment#instantiate.
    319                     if (args != null) {
    320                         args.setClassLoader(f.getClass().getClassLoader());
    321                         f.setArguments(args);
    322                     }
    323                     return f;
    324                 } catch (IllegalAccessException e) {
    325                     throw new Fragment.InstantiationException("Unable to instantiate " + className,
    326                             e);
    327                 } catch (InvocationTargetException e) {
    328                     throw new Fragment.InstantiationException("Unable to instantiate " + className,
    329                             e);
    330                 }
    331             }
    332             return Fragment.instantiate(context, className, args);
    333         }
    334     }
    335 
    336     private static class PluginState {
    337         Context mContext;
    338         String mCls;
    339 
    340         private PluginState(String cls, Context context) {
    341             mCls = cls;
    342             mContext = context;
    343         }
    344     }
    345 }
    346