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