package org.osmdroid.samplefragments.ui; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ExpandableListView; import android.widget.ListView; import android.widget.Toast; import org.osmdroid.BugsTestingActivity; import org.osmdroid.ExtraSamplesActivity; import org.osmdroid.ISampleFactory; import org.osmdroid.R; import org.osmdroid.model.BaseActivity; import org.osmdroid.model.IBaseActivity; import org.osmdroid.samplefragments.BaseSampleFragment; import org.osmdroid.views.overlay.Polygon; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * http://www.androidhive.info/2013/07/android-expandable-list-view-tutorial/ * * created on 1/1/2017. * * @author Alex O'Ree */ public class SamplesMenuFragment extends Fragment { public final static String TAG="osmfragsample"; private Bundle savedState = null; private ISampleFactory sampleFactory=null; private List<IBaseActivity> additionActivitybasedSamples; ExpandableListAdapter listAdapter; ExpandableListView expListView; List<String> listDataHeader; HashMap<String, List<String>> listDataChild; Map<String, Object> titleSampleMap = new HashMap<>(); public static SamplesMenuFragment newInstance(ISampleFactory fac, List<IBaseActivity> additionActivitybasedSamples) { SamplesMenuFragment x= new SamplesMenuFragment(); x.sampleFactory=fac; x.additionActivitybasedSamples=additionActivitybasedSamples; return x; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View root = inflater.inflate(R.layout.sample_menu_layout, container,false); //http://stackoverflow.com/a/15314508/1203182 /* (...) */ /* If the Fragment was destroyed inbetween (screen rotation), we need to recover the savedState first */ /* However, if it was not, it stays in the instance from the last onDestroyView() and we don't want to overwrite it */ if(savedInstanceState != null && savedState == null) { savedState = savedInstanceState.getBundle(TAG); } if(savedState != null) { if (sampleFactory!=null){ //do nothing } else { String factory = savedState.getString("factory"); ArrayList<String> acts = null; if (savedState.containsKey("acts")) acts=savedState.getStringArrayList("acts"); try { Class<?> aClass = Class.forName(factory); Method method = aClass.getMethod("getInstance"); sampleFactory = (ISampleFactory) method.invoke(null); if (acts==null) { additionActivitybasedSamples = Collections.EMPTY_LIST; } else { //restore the list additionActivitybasedSamples = new ArrayList<>(); for (int i=0; i < acts.size(); i++){ additionActivitybasedSamples.add((IBaseActivity) Class.forName(acts.get(i)).newInstance()); } } } catch (Throwable t) { //can resume for some reason t.printStackTrace(); getActivity().finish(); } } } savedState = null; // get the listview expListView = (ExpandableListView) root.findViewById(R.id.lvExp); // Listview on child click listener expListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() { @Override public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { String title = listDataChild.get( listDataHeader.get(groupPosition)).get( childPosition); Object o = titleSampleMap.get(title); if (o!=null && o instanceof BaseSampleFragment) { // Replace Fragment with selected sample BaseSampleFragment frag = (BaseSampleFragment) o; Log.i(TAG, "loading fragment " + frag.getSampleTitle() + ", " + frag.getClass().getCanonicalName()); FragmentManager fm = getFragmentManager(); fm.beginTransaction().replace(org.osmdroid.R.id.samples_container, frag, ExtraSamplesActivity.SAMPLES_FRAGMENT_TAG) .addToBackStack(null).commit(); } else if (o!=null && o instanceof IBaseActivity && o instanceof Activity){ IBaseActivity activity = (IBaseActivity) o; Intent i = new Intent(getContext(), activity.getClass()); Log.i(TAG, "loading activity " + activity.getActivityTitle() + ", " + activity.getClass().getCanonicalName()); getActivity().startActivity(i); } else if (o==null) { //NOOP } else{ Toast.makeText(getActivity(), "Example is of an unexpected type, please report this", Toast.LENGTH_LONG).show(); } return false; } }); return root; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); //the following block of code took me an entire weekend to track down the root cause. //if the code block is in onCreate, it will leak. onActivityCreated = no leak. //makes no sense, but that's Android for you. // preparing list data boolean success=prepareListData(); if (!success) { Activity act = getActivity(); act.finish(); return; } listAdapter = new ExpandableListAdapter(getActivity(), listDataHeader, listDataChild); // setting list adapter expListView.setAdapter(listAdapter); } /* * Preparing the list data */ private boolean prepareListData() { Set<String> headers = new HashSet<>(); listDataHeader = new ArrayList<String>(); //category, content listDataChild = new HashMap<String, List<String>>(); if (sampleFactory==null || additionActivitybasedSamples==null) { //getActivity().getSupportFragmentManager().popBackStack(); return false; } //had this throw an NPE once after device rotation and a back button press. for (int a = 0; a < sampleFactory.count(); a++) { final BaseSampleFragment f = sampleFactory.getSample(a); titleSampleMap.put(f.getSampleTitle(), f); String clz = f.getClass().getCanonicalName(); String[] bits=clz.split("\\."); String group = bits[bits.length-2]; group=capitialize(group); headers.add(group); if (!listDataChild.containsKey(group)) { listDataChild.put(group, new ArrayList<String>()); } listDataChild.get(group).add(f.getSampleTitle()); } if (!additionActivitybasedSamples.isEmpty()) { listDataHeader.add("Activities"); listDataChild.put("Activities", new ArrayList<String>()); for (int a = 0; a < additionActivitybasedSamples.size(); a++) { listDataChild.get("Activities").add(additionActivitybasedSamples.get(a).getActivityTitle()); titleSampleMap.put(additionActivitybasedSamples.get(a).getActivityTitle(), additionActivitybasedSamples.get(a)); } } listDataHeader.addAll(headers); return true; } private String capitialize(String group) { if (group.charAt(0) >= 'a' && group.charAt(0) <='z') { String first = group.substring(0, 1).toUpperCase(); group = first + group.substring(1); } return group; } @Override public void onResume(){ super.onResume(); //FragmentManager fm = getFragmentManager(); //fm.popBackStack(); //System.gc(); } @Override public void onDestroyView() { super.onDestroyView(); savedState = saveState(); } private Bundle saveState() { /* called either from onDestroyView() or onSaveInstanceState() */ Bundle state = new Bundle(); if (sampleFactory!=null) //yup, hate android state.putString("factory", sampleFactory.getClass().getCanonicalName()); if (additionActivitybasedSamples!=null){ ArrayList<String> actClasses = new ArrayList<>(); for (int i=0; i < additionActivitybasedSamples.size(); i++) actClasses.add(additionActivitybasedSamples.get(i).getClass().getCanonicalName()); state.putStringArrayList("acts", actClasses); } return state; } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); /* If onDestroyView() is called first, we can use the previously savedState but we can't call saveState() anymore */ /* If onSaveInstanceState() is called first, we don't have savedState, so we need to call saveState() */ /* => (?:) operator inevitable! */ outState.putBundle(TAG, (savedState != null) ? savedState : saveState()); } }