package org.commcare.activities; import android.app.Activity; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.database.DataSetObserver; import android.os.Build; import android.os.Bundle; import android.support.v4.app.FragmentManager; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.Button; import android.widget.FrameLayout; import android.widget.GridView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import org.commcare.CommCareApplication; import org.commcare.activities.components.EntitySelectCalloutSetup; import org.commcare.activities.components.EntitySelectViewSetup; import org.commcare.adapters.EntityListAdapter; import org.commcare.cases.entity.Entity; import org.commcare.cases.entity.NodeEntityFactory; import org.commcare.dalvik.R; import org.commcare.fragments.ContainerFragment; import org.commcare.google.services.ads.AdLocation; import org.commcare.google.services.ads.AdMobManager; import org.commcare.logic.DetailCalloutListenerDefaultImpl; import org.commcare.models.AndroidSessionWrapper; import org.commcare.preferences.CommCarePreferences; import org.commcare.provider.SimprintsCalloutProcessing; import org.commcare.session.CommCareSession; import org.commcare.session.SessionFrame; import org.commcare.suite.model.Action; import org.commcare.suite.model.Callout; import org.commcare.suite.model.CalloutData; import org.commcare.suite.model.Detail; import org.commcare.suite.model.DetailField; import org.commcare.suite.model.EntityDatum; import org.commcare.tasks.EntityLoaderListener; import org.commcare.tasks.EntityLoaderTask; import org.commcare.utils.AndroidInstanceInitializer; import org.commcare.utils.DetailCalloutListener; import org.commcare.utils.EntityDetailUtils; import org.commcare.utils.EntitySelectRefreshTimer; import org.commcare.utils.HereFunctionHandler; import org.commcare.utils.SerializationUtil; import org.commcare.views.EntityView; import org.commcare.views.TabbedDetailView; import org.commcare.views.UserfacingErrorHandling; import org.commcare.views.ViewUtil; import org.commcare.views.dialogs.DialogChoiceItem; import org.commcare.views.dialogs.LocationNotificationHandler; import org.commcare.views.dialogs.PaneledChoiceDialog; import org.commcare.views.media.AudioController; import org.javarosa.core.model.condition.EvaluationContext; import org.javarosa.core.model.instance.TreeReference; import org.javarosa.core.services.Logger; import org.javarosa.core.services.locale.Localization; import org.javarosa.core.util.OrderedHashtable; import org.javarosa.xpath.XPathTypeMismatchException; import org.commcare.android.javarosa.IntentCallout; import java.util.ArrayList; import java.util.List; /** * @author ctsims */ public class EntitySelectActivity extends SaveSessionCommCareActivity implements EntityLoaderListener, OnItemClickListener, DetailCalloutListener { private CommCareSession session; private AndroidSessionWrapper asw; public static final String EXTRA_ENTITY_KEY = "esa_entity_key"; private static final String CONTAINS_HERE_FUNCTION = "contains_here_function"; private static final String MAPPING_ENABLED = "map_view_enabled"; private static final String LOCATION_CHANGED_WHILE_LOADING = "location_changed_while_loading"; private static final String IS_AUTO_LAUNCHING = "is_auto_launching"; private static final int CONFIRM_SELECT = 0; private static final int MAP_SELECT = 2; public static final int BARCODE_FETCH = 1; public static final int CALLOUT = 3; private static final int MENU_SORT = Menu.FIRST + 1; private static final int MENU_MAP = Menu.FIRST + 2; private static final int MENU_ACTION = Menu.FIRST + 3; private static final int MENU_ACTION_GROUP = Menu.FIRST + 1; private EntityListAdapter adapter; private LinearLayout header; private EntityDatum selectDatum; private boolean mResultIsMap = false; private boolean isMappingEnabled = false; // Is the detail screen for showing entities, without option for moving // forward on to form manipulation? private boolean mViewMode = false; // No detail confirm screen is defined for this entity select private boolean mNoDetailMode = false; private EntityLoaderTask loader; private boolean inAwesomeMode = false; private FrameLayout rightFrame; private TabbedDetailView detailView; private Intent selectedIntent = null; private boolean hideActionsFromOptionsMenu; private boolean hideActionsFromEntityList; private EntitySelectSearchUI entitySelectSearchUI; private Detail shortSelect; private Callout customCallout; private DataSetObserver mListStateObserver; public OnClickListener barcodeScanOnClickListener; private boolean isCalloutAutoLaunching; private boolean resuming = false; private boolean isStartingDetailActivity = false; private boolean rightFrameSetup = false; private NodeEntityFactory factory; private ContainerFragment<EntityListAdapter> containerFragment; private EntitySelectRefreshTimer refreshTimer; // Function handler for handling XPath evaluation of the function here(). // Although only one instance is created, which is used by NodeEntityFactory, // every instance of EntitySelectActivity registers itself (one at a time) // to listen to the handler and refresh whenever a new location is obtained. private static final HereFunctionHandler hereFunctionHandler = new HereFunctionHandler(); private boolean containsHereFunction = false; private boolean locationChangedWhileLoading = false; // Handler for displaying alert dialog when no location providers are found private final LocationNotificationHandler locationNotificationHandler = new LocationNotificationHandler(this); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); createDataSetObserver(); restoreSavedState(savedInstanceState); if (savedInstanceState == null) { hereFunctionHandler.refreshLocation(); } refreshTimer = new EntitySelectRefreshTimer(); asw = CommCareApplication.instance().getCurrentSessionWrapper(); session = asw.getSession(); // avoid session dependent when there is no command if (session.getCommand() != null) { selectDatum = (EntityDatum)session.getNeededDatum(); shortSelect = session.getDetail(selectDatum.getShortDetail()); if (shortSelect.forcesLandscape()) { this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); } this.customCallout = initCustomCallout(); mNoDetailMode = selectDatum.getLongDetail() == null; mViewMode = session.isViewCommand(session.getCommand()); // Don't show actions (e.g. 'register patient', 'claim patient') at all when in the // middle of workflow triggered by a (sync) action. Also hide them from the entity // list (but not the options menu) when we are showing the entity list in grid mode hideActionsFromEntityList = session.isRemoteRequestCommand(session.getCommand()) || shortSelect.shouldBeLaidOutInGrid(); hideActionsFromOptionsMenu = session.isRemoteRequestCommand(session.getCommand()); boolean isOrientationChange = savedInstanceState != null; setupUI(isOrientationChange); } } private Callout initCustomCallout() { Callout customCallout = shortSelect.getCallout(); if (customCallout != null && customCallout.isSimprintsCallout() && Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { // If this device can't support the simprints library, don't create the callout return null; } return customCallout; } private void createDataSetObserver() { mListStateObserver = new DataSetObserver() { @Override public void onChanged() { super.onChanged(); entitySelectSearchUI.setSearchBannerState(); } }; } private void restoreSavedState(Bundle savedInstanceState) { if (savedInstanceState != null) { isMappingEnabled = savedInstanceState.getBoolean(MAPPING_ENABLED); containsHereFunction = savedInstanceState.getBoolean(CONTAINS_HERE_FUNCTION); locationChangedWhileLoading = savedInstanceState.getBoolean(LOCATION_CHANGED_WHILE_LOADING); isCalloutAutoLaunching = savedInstanceState.getBoolean(IS_AUTO_LAUNCHING); } } private void setupUI(boolean isOrientationChange) { if (this.getString(R.string.panes).equals("two") && !mNoDetailMode) { if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { setupLandscapeDualPaneView(); } else { setContentView(R.layout.entity_select_layout); restoreExistingSelection(isOrientationChange); } } else { setContentView(R.layout.entity_select_layout); } AdapterView visibleView; GridView gridView = (GridView)this.findViewById(R.id.screen_entity_select_grid); ListView listView = ((ListView)this.findViewById(R.id.screen_entity_select_list)); if (shortSelect.shouldBeLaidOutInGrid()) { visibleView = gridView; gridView.setVisibility(View.VISIBLE); gridView.setNumColumns(shortSelect.getNumEntitiesToDisplayPerRow()); listView.setVisibility(View.GONE); } else { visibleView = listView; listView.setVisibility(View.VISIBLE); gridView.setVisibility(View.GONE); EntitySelectViewSetup.setupDivider(this, listView, shortSelect.usesEntityTileView()); } visibleView.setOnItemClickListener(this); header = (LinearLayout)findViewById(R.id.entity_select_header); entitySelectSearchUI = new EntitySelectSearchUI(this); restoreLastQueryString(); persistAdapterState(visibleView); setUpCalloutClickListener(); entitySelectSearchUI.setupPreHoneycombFooter(barcodeScanOnClickListener, this.customCallout); setupMapNav(); AdMobManager.requestBannerAdForView(this, (FrameLayout)findViewById(R.id.ad_container), AdLocation.EntitySelect); } private void setUpCalloutClickListener() { if (this.customCallout == null) { barcodeScanOnClickListener = EntitySelectCalloutSetup.makeBarcodeClickListener(this); } else { isCalloutAutoLaunching = this.customCallout.isAutoLaunching(); barcodeScanOnClickListener = EntitySelectCalloutSetup.makeCalloutClickListener(this, this.customCallout, evalContext()); } } private void setupLandscapeDualPaneView() { setContentView(R.layout.screen_compound_select); View.inflate(this, R.layout.entity_select_layout, (ViewGroup)findViewById(R.id.screen_compound_select_left_pane)); inAwesomeMode = true; rightFrame = (FrameLayout)findViewById(R.id.screen_compound_select_right_pane); TextView message = (TextView)findViewById(R.id.screen_compound_select_prompt); //use the old method here because some Android versions don't like Spannables for titles message.setText(Localization.get("select.placeholder.message", new String[]{Localization.get("cchq.case")})); } private void restoreExistingSelection(boolean isOrientationChange) { // Restore detail screen for selection from landscape mode as we move into portrait mode. if (isOrientationChange) { Intent intent = this.getIntent(); TreeReference selectedRef = SerializationUtil.deserializeFromIntent(intent, EntityDetailActivity.CONTEXT_REFERENCE, TreeReference.class); if (selectedRef != null) { // remove the reference from this intent, ensuring we // don't re-launch the detail for an entity even after // it being de-selected. intent.removeExtra(EntityDetailActivity.CONTEXT_REFERENCE); // include selected entity in the launching detail intent Intent detailIntent = EntityDetailUtils.getDetailIntent(getApplicationContext(), selectedRef, null, selectDatum, asw); isStartingDetailActivity = true; startActivityForResult(detailIntent, CONFIRM_SELECT); } } } private void persistAdapterState(AdapterView view) { FragmentManager fm = this.getSupportFragmentManager(); containerFragment = (ContainerFragment)fm.findFragmentByTag("entity-adapter"); // stateHolder and its previous state aren't null if the activity is // being created due to an orientation change. if (containerFragment == null) { containerFragment = new ContainerFragment<>(); fm.beginTransaction().add(containerFragment, "entity-adapter").commit(); } else { adapter = containerFragment.getData(); // on orientation change if (adapter != null) { setupUIFromAdapter(view); } } } private void setupUIFromAdapter(AdapterView view) { view.setAdapter(adapter); if (view instanceof ListView) { EntitySelectViewSetup.setupDivider(this, (ListView)view, shortSelect.usesEntityTileView()); } findViewById(R.id.entity_select_loading).setVisibility(View.GONE); entitySelectSearchUI.setSearchBannerState(); } private void setupMapNav() { for (DetailField field : shortSelect.getFields()) { if ("address".equals(field.getTemplateForm())) { isMappingEnabled = true; break; } } } @Override protected void onResumeSessionSafe() { if (!isFinishing() && !isStartingDetailActivity) { if (adapter != null) { adapter.registerDataSetObserver(mListStateObserver); } if (!resuming && !mNoDetailMode && this.getIntent().hasExtra(EXTRA_ENTITY_KEY)) { if (resumeSelectedEntity()) { return; } } hereFunctionHandler.registerEvalLocationListener(this); if (containsHereFunction) { hereFunctionHandler.allowGpsUse(); } refreshView(); if (isCalloutAutoLaunching) { isCalloutAutoLaunching = false; barcodeScanOnClickListener.onClick(null); } } } private boolean resumeSelectedEntity() { TreeReference selectedEntity = selectDatum.getEntityFromID(evalContext(), this.getIntent().getStringExtra(EXTRA_ENTITY_KEY)); if (selectedEntity != null) { if (inAwesomeMode) { if (adapter != null) { displayReferenceAwesome(selectedEntity, adapter.getPosition(selectedEntity)); updateSelectedItem(selectedEntity, true); } } else { //Once we've done the initial dispatch, we don't want to end up triggering it later. this.getIntent().removeExtra(EXTRA_ENTITY_KEY); Intent i = EntityDetailUtils.getDetailIntent(getApplicationContext(), selectedEntity, null, selectDatum, asw); if (adapter != null) { i.putExtra("entity_detail_index", adapter.getPosition(selectedEntity)); i.putExtra(EntityDetailActivity.DETAIL_PERSISTENT_ID, selectDatum.getShortDetail()); } startActivityForResult(i, CONFIRM_SELECT); return true; } } return false; } /** * Get form list from database and insert into view. */ protected void refreshView() { try { rebuildHeaders(); if (adapter == null) { loadEntities(); } else { refreshTimer.start(this); } } catch (RuntimeException re) { UserfacingErrorHandling.createErrorDialog(this, re.getMessage(), true); } } private void rebuildHeaders() { //TODO: Get ec into these text's String[] headers = new String[shortSelect.getFields().length]; for (int i = 0; i < headers.length; ++i) { headers[i] = shortSelect.getFields()[i].getHeader().evaluate(); } header.removeAllViews(); // only add headers if we're not using case tiles if (!shortSelect.usesEntityTileView()) { boolean hasCalloutResponseData = (adapter != null && adapter.hasCalloutResponseData()); //Hm, sadly we possibly need to rebuild this each time. EntityView v = EntityView.buildHeadersEntityView(this, shortSelect, headers, hasCalloutResponseData); header.addView(v); } } public boolean loadEntities() { if (adapter != null) { // Store extra data to be reloaded upon load task completion adapter.saveCalloutDataToSession(); } if (loader == null && !EntityLoaderTask.attachToActivity(this)) { EntityLoaderTask entityLoader = new EntityLoaderTask(shortSelect, evalContext()); entityLoader.attachListener(this); entityLoader.executeParallel(selectDatum.getNodeset()); return true; } return false; } @Override public void onSaveInstanceState(Bundle savedInstanceState) { super.onSaveInstanceState(savedInstanceState); savedInstanceState.putBoolean(CONTAINS_HERE_FUNCTION, containsHereFunction); savedInstanceState.putBoolean(MAPPING_ENABLED, isMappingEnabled); savedInstanceState.putBoolean(LOCATION_CHANGED_WHILE_LOADING, locationChangedWhileLoading); savedInstanceState.putBoolean(IS_AUTO_LAUNCHING, isCalloutAutoLaunching); } @Override protected void onPause() { super.onPause(); refreshTimer.stop(); if (adapter != null) { adapter.unregisterDataSetObserver(mListStateObserver); } hereFunctionHandler.forbidGpsUse(); hereFunctionHandler.unregisterEvalLocationListener(); } @Override protected void onStop() { super.onStop(); refreshTimer.stop(); saveLastQueryString(); } @Override protected void onDestroy() { super.onDestroy(); if (loader != null) { if (isFinishing()) { loader.cancel(false); } else { loader.detachActivity(); } } if (adapter != null) { adapter.signalKilled(); } } @Override public void onItemClick(AdapterView<?> listView, View view, int position, long id) { if (adapter.getItemViewType(position) == EntityListAdapter.ENTITY_TYPE) { TreeReference selection = adapter.getItem(position); if (CommCarePreferences.isEntityDetailLoggingEnabled()) { Logger.log(EntityDetailActivity.class.getSimpleName(), selectDatum.getLongDetail()); } if (inAwesomeMode) { displayReferenceAwesome(selection, position); updateSelectedItem(selection, false); } else { Intent i = EntityDetailUtils.getDetailIntent(getApplicationContext(), selection, null, selectDatum, asw); i.putExtra("entity_detail_index", position); if (mNoDetailMode) { // Not actually launching detail intent because there's no confirm detail available returnWithResult(i); } else { startActivityForResult(i, CONFIRM_SELECT); } } } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { switch (requestCode) { case BARCODE_FETCH: processBarcodeFetch(resultCode, intent); break; case CALLOUT: processCalloutResult(resultCode, intent); break; case CONFIRM_SELECT: resuming = true; isStartingDetailActivity = false; if (resultCode == RESULT_OK && !mViewMode) { // create intent for return and store path returnWithResult(intent); } else { //Did we enter the detail from mapping mode? If so, go back to that if (mResultIsMap) { mResultIsMap = false; Intent i = new Intent(this, EntityMapActivity.class); this.startActivityForResult(i, MAP_SELECT); return; } if (inAwesomeMode) { // Retain original element selection TreeReference r = SerializationUtil.deserializeFromIntent(intent, EntityDetailActivity.CONTEXT_REFERENCE, TreeReference.class); if (r != null && adapter != null) { this.displayReferenceAwesome(r, adapter.getPosition(r)); updateSelectedItem(r, true); } AudioController.INSTANCE.releaseCurrentMediaEntity(); } } break; case MAP_SELECT: if (resultCode == RESULT_OK) { TreeReference r = SerializationUtil.deserializeFromIntent(intent, EntityDetailActivity.CONTEXT_REFERENCE, TreeReference.class); if (inAwesomeMode) { this.displayReferenceAwesome(r, adapter.getPosition(r)); } else { Intent i = EntityDetailUtils.getDetailIntent(getApplicationContext(), r, null, selectDatum, asw); if (mNoDetailMode) { returnWithResult(i); } else { //To go back to map mode if confirm is false mResultIsMap = true; i.putExtra("entity_detail_index", adapter.getPosition(r)); startActivityForResult(i, CONFIRM_SELECT); } } } else { refreshView(); } break; default: super.onActivityResult(requestCode, resultCode, intent); } } private void processBarcodeFetch(int resultCode, Intent intent) { if (resultCode == Activity.RESULT_OK) { String result = intent.getStringExtra("SCAN_RESULT"); if (result != null) { result = result.trim(); } entitySelectSearchUI.setSearchText(result); } } private void processCalloutResult(int resultCode, Intent intent) { if (resultCode == Activity.RESULT_OK) { if (intent.hasExtra(IntentCallout.INTENT_RESULT_VALUE)) { handleSearchStringCallout(intent); } else if (SimprintsCalloutProcessing.isIdentificationResponse(intent)) { handleFingerprintMatchCallout(intent); } else { Toast.makeText(this, Localization.get("select.callout.search.invalid"), Toast.LENGTH_SHORT).show(); } } } private void handleSearchStringCallout(Intent intent) { String result = intent.getStringExtra(IntentCallout.INTENT_RESULT_VALUE); if (result != null) { entitySelectSearchUI.setSearchText(result.trim()); } else { for (String key : this.customCallout.getResponses()) { result = intent.getExtras().getString(key); if (result != null) { entitySelectSearchUI.setSearchText(result); return; } } } } private void handleFingerprintMatchCallout(Intent intent) { OrderedHashtable<String, String> guidToMatchConfidenceMap = SimprintsCalloutProcessing.getConfidenceMatchesFromCalloutResponse(intent); adapter.filterByKeyedCalloutData(guidToMatchConfidenceMap); refreshView(); } /** * Finish this activity, including all extras from the given intent in the finishing intent */ private void returnWithResult(Intent intent) { if (adapter != null) { adapter.saveCalloutDataToSession(); } Intent i = new Intent(this.getIntent()); i.putExtras(intent.getExtras()); setResult(RESULT_OK, i); finish(); } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); //use the old method here because some Android versions don't like Spannables for titles menu.add(0, MENU_SORT, MENU_SORT, Localization.get("select.menu.sort")).setIcon( android.R.drawable.ic_menu_sort_alphabetically); if (isMappingEnabled) { menu.add(0, MENU_MAP, MENU_MAP, Localization.get("select.menu.map")).setIcon( android.R.drawable.ic_menu_mapmode); } tryToAddSearchActionToAppBar(this, menu, entitySelectSearchUI.getActionBarInstantiator()); setupActionOptionsMenu(menu); return true; } private void setupActionOptionsMenu(Menu menu) { if (shortSelect != null && !hideActionsFromOptionsMenu) { int indexToAddActionAt = MENU_ACTION; for (Action action : shortSelect.getCustomActions(evalContext())) { if (action != null) { ViewUtil.addActionToMenu(this, action, menu, indexToAddActionAt, MENU_ACTION_GROUP); indexToAddActionAt += 1; } } entitySelectSearchUI.setupActionImage(this.customCallout); } } @Override public boolean onPrepareOptionsMenu(Menu menu) { // only enable sorting once entity loading is complete menu.findItem(MENU_SORT).setEnabled(adapter != null); // hide sorting menu when using async loading strategy menu.findItem(MENU_SORT).setVisible((shortSelect == null || !shortSelect.useAsyncStrategy())); menu.findItem(R.id.menu_settings).setVisible(!CommCareApplication.instance().isConsumerApp()); return super.onPrepareOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getGroupId() == MENU_ACTION_GROUP) { triggerDetailAction(item.getItemId() - MENU_ACTION); } switch (item.getItemId()) { case MENU_SORT: createSortMenu(); return true; case MENU_MAP: Intent i = new Intent(this, EntityMapActivity.class); this.startActivityForResult(i, MAP_SELECT); return true; // handling click on the barcode scanner's actionbar // trying to set the onclicklistener in its view in the onCreateOptionsMenu method does not work because it returns null case R.id.barcode_scan_action_bar: barcodeScanOnClickListener.onClick(null); return true; // this is needed because superclasses do not implement the menu_settings click case R.id.menu_settings: HomeScreenBaseActivity.createPreferencesMenu(this); return true; } return super.onOptionsItemSelected(item); } private void triggerDetailAction(int index) { Action action = shortSelect.getCustomActions(evalContext()).get(index); triggerDetailAction(action, this); } public static void triggerDetailAction(Action action, CommCareActivity activity) { try { CommCareApplication.instance().getCurrentSessionWrapper().executeStackActions(action.getStackOperations()); } catch (XPathTypeMismatchException e) { UserfacingErrorHandling.logErrorAndShowDialog(activity, e, true); return; } activity.setResult(HomeScreenBaseActivity.RESULT_RESTART); activity.finish(); } private void createSortMenu() { final PaneledChoiceDialog dialog = new PaneledChoiceDialog(this, Localization.get("select.menu.sort")); dialog.setChoiceItems(getSortOptionsList()); showAlertDialog(dialog); } private DialogChoiceItem[] getSortOptionsList() { DetailField[] fields = session.getDetail(selectDatum.getShortDetail()).getFields(); List<String> namesList = new ArrayList<>(); final int[] keyArray = new int[fields.length]; int[] sorts = adapter.getCurrentSort(); int currentSort = sorts.length == 1 ? sorts[0] : -1; boolean reversed = adapter.isCurrentSortReversed(); int added = 0; for (int i = 0; i < fields.length; ++i) { String result = fields[i].getHeader().evaluate(); if (!"".equals(result)) { String prepend = ""; if (currentSort == -1) { for (int j = 0; j < sorts.length; ++j) { if (sorts[j] == i) { prepend = (j + 1) + " " + (fields[i].getSortDirection() == DetailField.DIRECTION_DESCENDING ? "(v) " : "(^) "); } } } else if (currentSort == i) { prepend = reversed ^ fields[i].getSortDirection() == DetailField.DIRECTION_DESCENDING ? "(v) " : "(^) "; } namesList.add(prepend + result); keyArray[added] = i; added++; } } DialogChoiceItem[] choiceItems = new DialogChoiceItem[namesList.size()]; for (int i = 0; i < namesList.size(); i++) { final int index = i; View.OnClickListener listener = new View.OnClickListener() { @Override public void onClick(View v) { adapter.sortEntities(new int[]{keyArray[index]}); adapter.filterByString(entitySelectSearchUI.getSearchText().toString()); dismissAlertDialog(); } }; DialogChoiceItem item = new DialogChoiceItem(namesList.get(i), -1, listener); choiceItems[i] = item; } return choiceItems; } @Override public void deliverLoadResult(List<Entity<TreeReference>> entities, List<TreeReference> references, NodeEntityFactory factory, int focusTargetIndex) { loader = null; int[] order = shortSelect.getSortOrder(); for (int i = 0; i < shortSelect.getFields().length; ++i) { String header = shortSelect.getFields()[i].getHeader().evaluate(); if (order.length == 0 && !"".equals(header)) { order = new int[]{i}; } } AdapterView visibleView; if (shortSelect.shouldBeLaidOutInGrid()) { visibleView = ((GridView)this.findViewById(R.id.screen_entity_select_grid)); } else { ListView listView = ((ListView)this.findViewById(R.id.screen_entity_select_list)); EntitySelectViewSetup.setupDivider(this, listView, shortSelect.usesEntityTileView()); visibleView = listView; } adapter = new EntityListAdapter(this, shortSelect, references, entities, order, factory, hideActionsFromEntityList, shortSelect.getCustomActions(evalContext()), inAwesomeMode); visibleView.setAdapter(adapter); adapter.registerDataSetObserver(this.mListStateObserver); containerFragment.setData(adapter); // Pre-select entity if one was provided in original intent if (!resuming && !mNoDetailMode && inAwesomeMode && this.getIntent().hasExtra(EXTRA_ENTITY_KEY)) { TreeReference entity = selectDatum.getEntityFromID(evalContext(), this.getIntent().getStringExtra(EXTRA_ENTITY_KEY)); if (entity != null) { displayReferenceAwesome(entity, adapter.getPosition(entity)); updateSelectedItem(entity, true); } } findViewById(R.id.entity_select_loading).setVisibility(View.GONE); if (adapter != null) { // filter by additional session data (search string, callout result data) // Relevant when user navigates so far forward in the session that // the entity list needs to be reloaded upon returning to it restoreAdapterStateFromSession(); } if (inAwesomeMode) { updateSelectedItem(true); } else if (focusTargetIndex != -1) { visibleView.setSelection(focusTargetIndex); } refreshTimer.start(this); if (locationChangedWhileLoading) { Log.i("HereFunctionHandler", "location changed while reloading"); locationChangedWhileLoading = false; loadEntities(); } } private void restoreAdapterStateFromSession() { entitySelectSearchUI.restoreSearchString(); adapter.loadCalloutDataFromSession(); } private void updateSelectedItem(boolean forceMove) { TreeReference chosen = null; if (selectedIntent != null) { chosen = SerializationUtil.deserializeFromIntent(selectedIntent, EntityDetailActivity.CONTEXT_REFERENCE, TreeReference.class); } updateSelectedItem(chosen, forceMove); } private void updateSelectedItem(TreeReference selected, boolean forceMove) { if (adapter != null && selected != null) { adapter.notifyCurrentlyHighlighted(selected); if (forceMove) { ListView view = ((ListView)this.findViewById(R.id.screen_entity_select_list)); view.setSelection(adapter.getPosition(selected)); } } } @Override public void attachLoader(EntityLoaderTask task) { findViewById(R.id.entity_select_loading).setVisibility(View.VISIBLE); this.loader = task; } @Override public void callRequested(String phoneNumber) { DetailCalloutListenerDefaultImpl.callRequested(this, phoneNumber); } @Override public void addressRequested(String address) { DetailCalloutListenerDefaultImpl.addressRequested(this, address); } @Override public void playVideo(String videoRef) { DetailCalloutListenerDefaultImpl.playVideo(this, videoRef); } @Override public void performCallout(CalloutData callout, int id) { DetailCalloutListenerDefaultImpl.performCallout(this, callout, id); } private void displayReferenceAwesome(final TreeReference selection, int detailIndex) { selectedIntent = EntityDetailUtils.getDetailIntent(getApplicationContext(), selection, getIntent(), selectDatum, asw); //this should be 100% "fragment" able if (!rightFrameSetup) { findViewById(R.id.screen_compound_select_prompt).setVisibility(View.GONE); View.inflate(this, R.layout.entity_detail, rightFrame); Button next = (Button)findViewById(R.id.entity_select_button); //use the old method here because some Android versions don't like Spannables for titles next.setText(Localization.get("select.detail.confirm")); next.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { performEntitySelect(); } }); if (mViewMode) { next.setVisibility(View.GONE); next.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); } String passedCommand = selectedIntent.getStringExtra(SessionFrame.STATE_COMMAND_ID); if (passedCommand != null) { mViewMode = session.isViewCommand(passedCommand); } else { mViewMode = session.isViewCommand(session.getCommand()); } detailView = (TabbedDetailView)rightFrame.findViewById(R.id.entity_detail_tabs); detailView.setRoot(detailView); Detail detail = session.getDetail(selectedIntent.getStringExtra(EntityDetailActivity.DETAIL_ID)); factory = new NodeEntityFactory(detail, session.getEvaluationContext(new AndroidInstanceInitializer(session))); detailView.showMenu(); if (detail.isCompound()) { // border around right panel doesn't look right when there are tabs rightFrame.setBackgroundDrawable(null); } rightFrameSetup = true; } detailView.refresh(factory.getDetail(), selection, detailIndex); } private void performEntitySelect() { if (adapter != null) { adapter.saveCalloutDataToSession(); } Intent i = new Intent(EntitySelectActivity.this.getIntent()); i.putExtra(SessionFrame.STATE_DATUM_VAL, selectedIntent.getStringExtra(SessionFrame.STATE_DATUM_VAL)); setResult(RESULT_OK, i); finish(); } @Override public void deliverLoadError(Exception e) { displayCaseListFilterException(e); } @Override protected boolean onForwardSwipe() { // If user has picked an entity, move along to form entry if (selectedIntent != null) { if (inAwesomeMode && detailView != null && detailView.getCurrentTab() < detailView.getTabCount() - 1) { return false; } if (!mViewMode) { performEntitySelect(); } } return true; } @Override protected boolean onBackwardSwipe() { if (inAwesomeMode && detailView != null && detailView.getCurrentTab() > 0) { return false; } finish(); return true; } public void onEvalLocationChanged() { boolean loaded = loadEntities(); if (!loaded) { locationChangedWhileLoading = true; } } public static HereFunctionHandler getHereFunctionHandler() { return hereFunctionHandler; } public void onHereFunctionEvaluated() { if (!containsHereFunction) { // First time here() is evaluated hereFunctionHandler.refreshLocation(); hereFunctionHandler.allowGpsUse(); containsHereFunction = true; if (!hereFunctionHandler.locationProvidersFound()) { locationNotificationHandler.sendEmptyMessage(0); } } } @Override protected boolean isTopNavEnabled() { return true; } @Override public String getActivityTitle() { return null; } protected EntityListAdapter getAdapter() { return adapter; } public EvaluationContext evalContext() { return asw.getEvaluationContext(); } }