Java tutorial
/* * Copyright (C) 2009-2013 University of Washington * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package org.opendatakit.survey.android.activities; import java.io.File; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.UUID; import org.apache.commons.lang3.StringEscapeUtils; import org.json.JSONObject; import org.opendatakit.common.android.database.DatabaseFactory; import org.opendatakit.common.android.logic.PropertyManager; import org.opendatakit.common.android.provider.FormsColumns; import org.opendatakit.common.android.utilities.AndroidUtils; import org.opendatakit.common.android.utilities.AndroidUtils.MacroStringExpander; import org.opendatakit.common.android.utilities.ODKDatabaseUtils; import org.opendatakit.common.android.utilities.ODKFileUtils; import org.opendatakit.common.android.utilities.UrlUtils; import org.opendatakit.common.android.utilities.WebLogger; import org.opendatakit.survey.android.R; import org.opendatakit.survey.android.application.Survey; import org.opendatakit.survey.android.fragments.AboutMenuFragment; import org.opendatakit.survey.android.fragments.FormChooserListFragment; import org.opendatakit.survey.android.fragments.FormDeleteListFragment; import org.opendatakit.survey.android.fragments.FormDownloadListFragment; import org.opendatakit.survey.android.fragments.InitializationFragment; import org.opendatakit.survey.android.fragments.InstanceUploaderListFragment; import org.opendatakit.survey.android.fragments.InstanceUploaderTableChooserListFragment; import org.opendatakit.survey.android.fragments.WebViewFragment; import org.opendatakit.survey.android.logic.DynamicPropertiesCallback; import org.opendatakit.survey.android.logic.FormIdStruct; import org.opendatakit.survey.android.logic.PropertiesSingleton; import org.opendatakit.survey.android.preferences.AdminPreferencesActivity; import org.opendatakit.survey.android.preferences.PreferencesActivity; import org.opendatakit.survey.android.provider.DbShimService; import org.opendatakit.survey.android.provider.FormsProviderAPI; import org.opendatakit.survey.android.views.ODKWebView; import android.annotation.SuppressLint; import android.app.ActionBar; import android.app.ActionBar.Tab; import android.app.Activity; import android.app.AlertDialog; import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentManager.BackStackEntry; import android.app.FragmentTransaction; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.ServiceConnection; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.text.InputType; import android.text.method.PasswordTransformationMethod; import android.view.Gravity; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.Toast; /** * Responsible for displaying buttons to launch the major activities. Launches * some activities based on returns of others. * * @author Carl Hartung (carlhartung@gmail.com) * @author Yaw Anokwa (yanokwa@gmail.com) */ public class MainMenuActivity extends Activity implements ODKActivity { private static final String t = "MainMenuActivity"; public static enum ScreenList { MAIN_SCREEN, FORM_CHOOSER, FORM_DOWNLOADER, FORM_DELETER, WEBKIT, INSTANCE_UPLOADER_TABLE_CHOOSER, INSTANCE_UPLOADER, CUSTOM_VIEW, INITIALIZATION_DIALOG, ABOUT_MENU }; // Extra returned from gp activity // TODO: move to Survey??? public static final String LOCATION_LATITUDE_RESULT = "latitude"; public static final String LOCATION_LONGITUDE_RESULT = "longitude"; public static final String LOCATION_ALTITUDE_RESULT = "altitude"; public static final String LOCATION_ACCURACY_RESULT = "accuracy"; // tags for retained context private static final String PAGE_WAITING_FOR_DATA = "pageWaitingForData"; private static final String PATH_WAITING_FOR_DATA = "pathWaitingForData"; private static final String ACTION_WAITING_FOR_DATA = "actionWaitingForData"; public static final String APP_NAME = "appName"; private static final String FORM_URI = "formUri"; private static final String UPLOAD_TABLE_ID = "uploadTableId"; private static final String INSTANCE_ID = "instanceId"; private static final String SCREEN_PATH = "screenPath"; private static final String CONTROLLER_STATE = "controllerState"; private static final String AUXILLARY_HASH = "auxillaryHash"; private static final String SESSION_VARIABLES = "sessionVariables"; private static final String SECTION_STATE_SCREEN_HISTORY = "sectionStateScreenHistory"; private static final String CURRENT_FRAGMENT = "currentFragment"; /** tables that have conflict rows */ public static final String CONFLICT_TABLES = "conflictTables"; // menu options private static final int MENU_FILL_FORM = Menu.FIRST; private static final int MENU_PULL_FORMS = Menu.FIRST + 1; private static final int MENU_CLOUD_FORMS = Menu.FIRST + 2; private static final int MENU_MANAGE_FORMS = Menu.FIRST + 3; private static final int MENU_PREFERENCES = Menu.FIRST + 4; private static final int MENU_ADMIN_PREFERENCES = Menu.FIRST + 5; private static final int MENU_EDIT_INSTANCE = Menu.FIRST + 6; private static final int MENU_PUSH_FORMS = Menu.FIRST + 7; private static final int MENU_ABOUT = Menu.FIRST + 8; // activity callback codes private static final int HANDLER_ACTIVITY_CODE = 20; private static final int INTERNAL_ACTIVITY_CODE = 21; private static final int SYNC_ACTIVITY_CODE = 22; private static final int CONFLICT_ACTIVITY_CODE = 23; // values for external intents to resolve conflicts /** Survey's package name as declared in the manifest. */ public static final String SYNC_PACKAGE_NAME = "org.opendatakit.sync"; /** The full path to Sync's checkpoint list activity. */ public static final String SYNC_CHECKPOINT_ACTIVITY_COMPONENT_NAME = "org.opendatakit.conflict.activities.CheckpointResolutionListActivity"; /** The full path to Sync's conflict list activity. */ public static final String SYNC_CONFLICT_ACTIVITY_COMPONENT_NAME = "org.opendatakit.conflict.activities.ConflictResolutionListActivity"; /** The field name for the tableId to resolve conflicts on */ public static final String SYNC_TABLE_ID_PARAMETER = "tableId"; private static final boolean EXIT = true; private static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER); public static class TabListener<T extends Fragment> implements ActionBar.TabListener { private Fragment mFragment; private final Activity mActivity; private final String mTag; private final Class<T> mClass; /** * Constructor used each time a new tab is created. * * @param activity * The host Activity, used to instantiate the fragment * @param tag * The identifier tag for the fragment * @param clz * The fragment's Class, used to instantiate the fragment */ public TabListener(Activity activity, String tag, Class<T> clz) { mActivity = activity; mTag = tag; mClass = clz; } /* The following are each of the ActionBar.TabListener callbacks */ public void onTabSelected(Tab tab, FragmentTransaction ft) { // Check if the fragment is already initialized if (mFragment == null) { // If not, instantiate and add it to the activity mFragment = Fragment.instantiate(mActivity, mClass.getName()); ft.add(android.R.id.content, mFragment, mTag); } else { // If it exists, simply attach it in order to show it ft.attach(mFragment); } } public void onTabUnselected(Tab tab, FragmentTransaction ft) { if (mFragment != null) { // Detach the fragment, because another one is being attached ft.detach(mFragment); } } public void onTabReselected(Tab tab, FragmentTransaction ft) { // User selected the already selected tab. Usually do nothing. } } private static class ScreenState { String screenPath; String state; ScreenState(String screenPath, String state) { this.screenPath = screenPath; this.state = state; } } private static class SectionScreenStateHistory implements Parcelable { ScreenState currentScreen = new ScreenState(null, null); ArrayList<ScreenState> history = new ArrayList<ScreenState>(); @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(currentScreen.screenPath); dest.writeString(currentScreen.state); dest.writeInt(history.size()); for (int i = 0; i < history.size(); ++i) { ScreenState screen = history.get(i); dest.writeString(screen.screenPath); dest.writeString(screen.state); } } public static final Parcelable.Creator<SectionScreenStateHistory> CREATOR = new Parcelable.Creator<SectionScreenStateHistory>() { public SectionScreenStateHistory createFromParcel(Parcel in) { SectionScreenStateHistory cur = new SectionScreenStateHistory(); String screenPath = in.readString(); String state = in.readString(); cur.currentScreen = new ScreenState(screenPath, state); int count = in.readInt(); for (int i = 0; i < count; ++i) { screenPath = in.readString(); state = in.readString(); cur.history.add(new ScreenState(screenPath, state)); } return cur; } @Override public SectionScreenStateHistory[] newArray(int size) { SectionScreenStateHistory[] array = new SectionScreenStateHistory[size]; for (int i = 0; i < size; ++i) { array[i] = null; } return array; } }; } /** * Member variables that are saved and restored across orientation changes. */ private ScreenList currentFragment = ScreenList.FORM_CHOOSER; private String pageWaitingForData = null; private String pathWaitingForData = null; private String actionWaitingForData = null; private String appName = null; private String uploadTableId = null; private FormIdStruct currentForm = null; // via FORM_URI (formUri) private String instanceId = null; private Bundle sessionVariables = new Bundle(); private ArrayList<SectionScreenStateHistory> sectionStateScreenHistory = new ArrayList<SectionScreenStateHistory>(); private String refId = UUID.randomUUID().toString(); private String auxillaryHash = null; private String frameworkBaseUrl = null; private Long frameworkLastModifiedDate = 0L; // DO NOT USE THESE -- only used to determine if the current form has changed. private String trackingFormPath = null; private Long trackingFormLastModifiedDate = 0L; /** * track which tables have conflicts (these need to be resolved before Survey * can operate) */ Bundle mConflictTables = new Bundle(); /** * Member variables that do not need to be preserved across orientation * changes, etc. */ // no need to preserve private PropertyManager mPropertyManager; // no need to preserve private AlertDialog mAlertDialog; // cached for efficiency only -- no need to preserve private Bitmap mDefaultVideoPoster = null; // cached for efficiency only -- no need to preserve private View mVideoProgressView = null; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { ODKWebView wkt = (ODKWebView) findViewById(R.id.webkit_view); if (wkt != null) { wkt.onServiceConnected(name, service); } } @Override public void onServiceDisconnected(ComponentName name) { ODKWebView wkt = (ODKWebView) findViewById(R.id.webkit_view); if (wkt != null) { wkt.onServiceDisconnected(name); } } }; @Override protected void onPause() { // if (mAlertDialog != null && mAlertDialog.isShowing()) { // mAlertDialog.dismiss(); // } super.onPause(); } @Override protected void onSaveInstanceState(Bundle outState) { // TODO Auto-generated method stub super.onSaveInstanceState(outState); if (pageWaitingForData != null) { outState.putString(PAGE_WAITING_FOR_DATA, pageWaitingForData); } if (pathWaitingForData != null) { outState.putString(PATH_WAITING_FOR_DATA, pathWaitingForData); } if (actionWaitingForData != null) { outState.putString(ACTION_WAITING_FOR_DATA, actionWaitingForData); } outState.putString(CURRENT_FRAGMENT, currentFragment.name()); if (getCurrentForm() != null) { outState.putString(FORM_URI, getCurrentForm().formUri.toString()); } if (getInstanceId() != null) { outState.putString(INSTANCE_ID, getInstanceId()); } if (getUploadTableId() != null) { outState.putString(UPLOAD_TABLE_ID, getUploadTableId()); } if (getScreenPath() != null) { outState.putString(SCREEN_PATH, getScreenPath()); } if (getControllerState() != null) { outState.putString(CONTROLLER_STATE, getControllerState()); } if (getAuxillaryHash() != null) { outState.putString(AUXILLARY_HASH, getAuxillaryHash()); } if (getAppName() != null) { outState.putString(APP_NAME, getAppName()); } outState.putBundle(SESSION_VARIABLES, sessionVariables); outState.putParcelableArrayList(SECTION_STATE_SCREEN_HISTORY, sectionStateScreenHistory); if (mConflictTables != null && !mConflictTables.isEmpty()) { outState.putBundle(CONFLICT_TABLES, mConflictTables); } } @Override public void initializationCompleted(String fragmentToShowNext) { // whether we have can cancelled or completed update, // remember to not do the expansion files check next time through ScreenList newFragment = ScreenList.valueOf(fragmentToShowNext); if (newFragment == ScreenList.WEBKIT && getCurrentForm() == null) { // we were sent off to the initialization dialog to try to // discover the form. We need to inquire about the form again // and, if we cannot find it, report an error to the user. final Uri uriFormsProvider = FormsProviderAPI.CONTENT_URI; Uri uri = getIntent().getData(); Uri formUri = null; if (uri.getScheme().equalsIgnoreCase(uriFormsProvider.getScheme()) && uri.getAuthority().equalsIgnoreCase(uriFormsProvider.getAuthority())) { List<String> segments = uri.getPathSegments(); if (segments != null && segments.size() >= 2) { String appName = segments.get(0); setAppName(appName); formUri = Uri.withAppendedPath(Uri.withAppendedPath(uriFormsProvider, appName), segments.get(1)); } else { swapToFragmentView(ScreenList.FORM_CHOOSER); createErrorDialog(getString(R.string.invalid_uri_expecting_n_segments, uri.toString(), 2), EXIT); return; } // request specifies a specific formUri -- try to open that FormIdStruct newForm = FormIdStruct.retrieveFormIdStruct(getContentResolver(), formUri); if (newForm == null) { // error swapToFragmentView(ScreenList.FORM_CHOOSER); createErrorDialog(getString(R.string.form_not_found, segments.get(1)), EXIT); return; } else { transitionToFormHelper(uri, newForm); swapToFragmentView(newFragment); } } } else { WebLogger.getLogger(getAppName()).i(t, "initializationCompleted: swapping to " + newFragment.name()); swapToFragmentView(newFragment); } } private void transitionToFormHelper(Uri uri, FormIdStruct newForm) { // work through switching to that form setAppName(newForm.appName); setCurrentForm(newForm); clearSectionScreenState(); String fragment = uri.getFragment(); if (fragment != null && fragment.length() != 0) { // and process the fragment to find the instanceId, screenPath and other // kv pairs String[] pargs = fragment.split("&"); boolean first = true; StringBuilder b = new StringBuilder(); int i; for (i = 0; i < pargs.length; ++i) { String[] keyValue = pargs[i].split("="); if ("instanceId".equals(keyValue[0])) { if (keyValue.length == 2) { setInstanceId(StringEscapeUtils.unescapeHtml4(keyValue[1])); } } else if ("screenPath".equals(keyValue[0])) { if (keyValue.length == 2) { setSectionScreenState(StringEscapeUtils.unescapeHtml4(keyValue[1]), null); } } else if ("refId".equals(keyValue[0]) || "formPath".equals(keyValue[0])) { // ignore } else { if (!first) { b.append("&"); } first = false; b.append(pargs[i]); } } String aux = b.toString(); if (aux.length() != 0) { setAuxillaryHash(aux); } } else { setInstanceId(null); setAuxillaryHash(null); } currentFragment = ScreenList.WEBKIT; } @Override protected void onStop() { super.onStop(); ODKWebView wkt = (ODKWebView) findViewById(R.id.webkit_view); if (wkt != null) { wkt.beforeDbShimServiceDisconnected(); } unbindService(mConnection); } @SuppressLint("InlinedApi") @Override protected void onStart() { super.onStart(); // ensure the DbShimService is started Intent intent = new Intent(this, DbShimService.class); this.startService(intent); this.bindService(intent, mConnection, Context.BIND_AUTO_CREATE | ((Build.VERSION.SDK_INT >= 14) ? Context.BIND_ADJUST_WITH_ACTIVITY : 0)); FrameLayout shadow = (FrameLayout) findViewById(R.id.shadow_content); View frags = findViewById(R.id.main_content); ODKWebView wkt = (ODKWebView) findViewById(R.id.webkit_view); if (currentFragment == ScreenList.FORM_CHOOSER || currentFragment == ScreenList.FORM_DOWNLOADER || currentFragment == ScreenList.FORM_DELETER || currentFragment == ScreenList.INSTANCE_UPLOADER_TABLE_CHOOSER || currentFragment == ScreenList.INSTANCE_UPLOADER || currentFragment == ScreenList.INITIALIZATION_DIALOG) { shadow.setVisibility(View.GONE); shadow.removeAllViews(); wkt.setVisibility(View.GONE); frags.setVisibility(View.VISIBLE); } else if (currentFragment == ScreenList.WEBKIT) { shadow.setVisibility(View.GONE); shadow.removeAllViews(); wkt.setVisibility(View.VISIBLE); wkt.invalidate(); frags.setVisibility(View.GONE); } else if (currentFragment == ScreenList.CUSTOM_VIEW) { shadow.setVisibility(View.VISIBLE); // shadow.removeAllViews(); wkt.setVisibility(View.GONE); frags.setVisibility(View.GONE); } FragmentManager mgr = getFragmentManager(); if (mgr.getBackStackEntryCount() == 0) { swapToFragmentView(currentFragment); } } public void scanForConflictAllTables() { long now = System.currentTimeMillis(); WebLogger.getLogger(getAppName()).i(this.getClass().getSimpleName(), "scanForConflictAllTables -- searching for conflicts and checkpoints "); SQLiteDatabase db = null; try { db = DatabaseFactory.get().getDatabase(this, getAppName()); ArrayList<String> tableIds = ODKDatabaseUtils.get().getAllTableIds(db); Bundle conflictTables = new Bundle(); for (String tableId : tableIds) { int health = ODKDatabaseUtils.get().getTableHealth(db, tableId); if ((health & ODKDatabaseUtils.TABLE_HEALTH_HAS_CONFLICTS) != 0) { conflictTables.putString(tableId, tableId); } } mConflictTables = conflictTables; } finally { if (db != null) { db.close(); } } long elapsed = System.currentTimeMillis() - now; WebLogger.getLogger(getAppName()).i(this.getClass().getSimpleName(), "scanForConflictAllTables -- full table scan completed: " + Long.toString(elapsed) + " ms"); } @Override protected void onPostResume() { super.onPostResume(); // Hijack the app here, after all screens have been resumed, // to ensure that all checkpoints and conflicts have been // resolved. If they haven't, we branch to the resolution // activity. if (mConflictTables == null || mConflictTables.isEmpty()) { scanForConflictAllTables(); } if ((mConflictTables != null) && !mConflictTables.isEmpty()) { Iterator<String> iterator = mConflictTables.keySet().iterator(); String tableId = iterator.next(); mConflictTables.remove(tableId); Intent i; i = new Intent(); i.setComponent(new ComponentName(SYNC_PACKAGE_NAME, SYNC_CONFLICT_ACTIVITY_COMPONENT_NAME)); i.setAction(Intent.ACTION_EDIT); i.putExtra(APP_NAME, getAppName()); i.putExtra(SYNC_TABLE_ID_PARAMETER, tableId); try { this.startActivityForResult(i, CONFLICT_ACTIVITY_CODE); } catch (ActivityNotFoundException e) { Toast.makeText(this, getString(R.string.activity_not_found, SYNC_CONFLICT_ACTIVITY_COMPONENT_NAME), Toast.LENGTH_LONG).show(); } } } public void setCurrentForm(FormIdStruct currentForm) { WebLogger.getLogger(getAppName()).i(t, "setCurrentForm: " + ((currentForm == null) ? "null" : currentForm.formPath)); this.currentForm = currentForm; } public FormIdStruct getCurrentForm() { return this.currentForm; } private void setUploadTableId(String uploadTableId) { WebLogger.getLogger(getAppName()).i(t, "setUploadTableId: " + uploadTableId); this.uploadTableId = uploadTableId; } @Override public String getUploadTableId() { return this.uploadTableId; } @Override public void setInstanceId(String instanceId) { WebLogger.getLogger(getAppName()).i(t, "setInstanceId: " + instanceId); this.instanceId = instanceId; } @Override public String getInstanceId() { return this.instanceId; } public void setAuxillaryHash(String auxillaryHash) { WebLogger.getLogger(getAppName()).i(t, "setAuxillaryHash: " + auxillaryHash); this.auxillaryHash = auxillaryHash; } @Override public String getAppName() { return this.appName; } @Override public String getActiveUser() { FormIdStruct form = getCurrentForm(); final DynamicPropertiesCallback cb = new DynamicPropertiesCallback(this, getAppName(), form == null ? null : getCurrentForm().tableId, getInstanceId()); String name = mPropertyManager.getSingularProperty(PropertyManager.EMAIL, cb); if (name == null || name.length() == 0) { name = mPropertyManager.getSingularProperty(PropertyManager.USERNAME, cb); if (name != null && name.length() != 0) { name = "username:" + name; } else { name = null; } } else { name = "mailto:" + name; } return name; } @Override public String getProperty(String propertyId) { FormIdStruct form = getCurrentForm(); final DynamicPropertiesCallback cb = new DynamicPropertiesCallback(this, getAppName(), form == null ? null : getCurrentForm().tableId, getInstanceId()); String value = mPropertyManager.getSingularProperty(propertyId, cb); return value; } @Override public String getWebViewContentUri() { Uri u = UrlUtils.getWebViewContentUri(this); String uriString = u.toString(); // Ensures that the string always ends with '/' if (uriString.charAt(uriString.length() - 1) != '/') { return uriString + "/"; } else { return uriString; } } @Override public FrameworkFormPathInfo getFrameworkFormPathInfo() { // Find the formPath for the default form with the most recent // version... Cursor c = null; String formPath = null; Long lastModified = null; try { // // the default form is named 'default' ... String selection = FormsColumns.FORM_ID + "=?"; String[] selectionArgs = { FormsColumns.COMMON_BASE_FORM_ID }; // use the most recently created of the matches // (in case DB corrupted) String orderBy = FormsColumns.FORM_VERSION + " DESC"; c = getContentResolver().query(Uri.withAppendedPath(FormsProviderAPI.CONTENT_URI, appName), null, selection, selectionArgs, orderBy); if (c != null && c.getCount() > 0) { // we found a match... c.moveToFirst(); formPath = ODKDatabaseUtils.get().getIndexAsString(c, c.getColumnIndex(FormsColumns.FORM_PATH)); lastModified = ODKDatabaseUtils.get().getIndexAsType(c, Long.class, c.getColumnIndex(FormsColumns.DATE)); } } finally { if (c != null && !c.isClosed()) { c.close(); } } if (formPath == null) { return null; } else { return new FrameworkFormPathInfo(formPath, lastModified); } } @Override public String getUrlBaseLocation(boolean ifChanged) { // Find the formPath for the default form with the most recent // version... // we need this so that we can load the index.html and main javascript // code FrameworkFormPathInfo info = getFrameworkFormPathInfo(); if (info == null) { return null; } String formPath = info.relativePath; // formPath always begins ../ -- strip that off to get explicit path // suffix... File mediaFolder = new File(new File(ODKFileUtils.getAppFolder(appName)), formPath.substring(3)); // File htmlFile = new File(mediaFolder, mPrompt.getAppearanceHint()); File htmlFile = new File(mediaFolder, "index.html"); if (!htmlFile.exists()) { return null; } String fullPath = UrlUtils.getAsWebViewUri(this, appName, ODKFileUtils.asUriFragment(appName, htmlFile)); if (fullPath == null) { return null; } // for some reason, the jqMobile framework wants an empty search string... // add this here now... fullPath += "?"; Long frameworkLastModified = info.lastModified; boolean changed = false; if (ifChanged && frameworkBaseUrl != null && frameworkBaseUrl.equals(fullPath)) { // determine if there are any changes in the framework // or in the form. If there are, reload. Otherwise, // return null. changed = (!frameworkLastModified.equals(frameworkLastModifiedDate)); } if (currentForm == null) { trackingFormPath = null; trackingFormLastModifiedDate = 0L; changed = true; } else if (trackingFormPath == null || !trackingFormPath.equals(currentForm.formPath)) { trackingFormPath = currentForm.formPath; trackingFormLastModifiedDate = currentForm.lastDownloadDate.getTime(); } else { changed = changed || (Long.valueOf(trackingFormLastModifiedDate) .compareTo(currentForm.lastDownloadDate.getTime()) < 0); trackingFormLastModifiedDate = currentForm.lastDownloadDate.getTime(); } frameworkBaseUrl = fullPath; frameworkLastModifiedDate = frameworkLastModified; return (ifChanged && !changed) ? null : frameworkBaseUrl; } @Override public String getUrlLocationHash() { if (currentForm == null) { // we want framework... FrameworkFormPathInfo info = getFrameworkFormPathInfo(); if (info == null) { return ""; } String hashUrl = "#formPath=" + StringEscapeUtils.escapeHtml4(info.relativePath) + ((instanceId == null) ? "" : "&instanceId=" + StringEscapeUtils.escapeHtml4(instanceId)) + ((getScreenPath() == null) ? "" : "&screenPath=" + StringEscapeUtils.escapeHtml4(getScreenPath())) + ((refId == null) ? "" : "&refId=" + StringEscapeUtils.escapeHtml4(refId)) + ((auxillaryHash == null) ? "" : "&" + auxillaryHash); return hashUrl; } else { String hashUrl = "#formPath=" + StringEscapeUtils.escapeHtml4((currentForm == null) ? "" : currentForm.formPath) + ((instanceId == null) ? "" : "&instanceId=" + StringEscapeUtils.escapeHtml4(instanceId)) + ((getScreenPath() == null) ? "" : "&screenPath=" + StringEscapeUtils.escapeHtml4(getScreenPath())) + ((refId == null) ? "" : "&refId=" + StringEscapeUtils.escapeHtml4(refId)) + ((auxillaryHash == null) ? "" : "&" + auxillaryHash); return hashUrl; } } public void setAppName(String appName) { this.appName = appName; } public String getRefId() { return this.refId; } public String getAuxillaryHash() { return this.auxillaryHash; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // android.os.Debug.waitForDebugger(); mPropertyManager = new PropertyManager(this); // must be at the beginning of any activity that can be called from an // external intent setAppName("survey"); Uri uri = getIntent().getData(); Uri formUri = null; if (uri != null) { // initialize to the URI, then we will customize further based upon the // savedInstanceState... final Uri uriFormsProvider = FormsProviderAPI.CONTENT_URI; final Uri uriWebView = UrlUtils.getWebViewContentUri(this); if (uri.getScheme().equalsIgnoreCase(uriFormsProvider.getScheme()) && uri.getAuthority().equalsIgnoreCase(uriFormsProvider.getAuthority())) { List<String> segments = uri.getPathSegments(); if (segments != null && segments.size() == 1) { String appName = segments.get(0); setAppName(appName); } else if (segments != null && segments.size() >= 2) { String appName = segments.get(0); setAppName(appName); formUri = Uri.withAppendedPath(Uri.withAppendedPath(uriFormsProvider, appName), segments.get(1)); } else { assignContentView(); createErrorDialog(getString(R.string.invalid_uri_expecting_n_segments, uri.toString(), 2), EXIT); return; } } else if (uri.getScheme().equals(uriWebView.getScheme()) && uri.getAuthority().equals(uriWebView.getAuthority()) && uri.getPort() == uriWebView.getPort()) { List<String> segments = uri.getPathSegments(); if (segments != null && segments.size() == 1) { String appName = segments.get(0); setAppName(appName); } else { assignContentView(); createErrorDialog(getString(R.string.invalid_uri_expecting_one_segment, uri.toString()), EXIT); return; } } else { assignContentView(); createErrorDialog(getString(R.string.unrecognized_uri, uri.toString(), uriWebView.toString(), uriFormsProvider.toString()), EXIT); return; } } if (savedInstanceState != null) { // if appName is explicitly set, use it... setAppName(savedInstanceState.containsKey(APP_NAME) ? savedInstanceState.getString(APP_NAME) : getAppName()); if (savedInstanceState.containsKey(CONFLICT_TABLES)) { mConflictTables = savedInstanceState.getBundle(CONFLICT_TABLES); } } WebLogger.getLogger(getAppName()).i(t, "Starting up, creating directories"); try { String appName = getAppName(); if (appName != null && appName.length() != 0) { ODKFileUtils.verifyExternalStorageAvailability(); ODKFileUtils.assertDirectoryStructure(appName); } } catch (RuntimeException e) { assignContentView(); createErrorDialog(e.getMessage(), EXIT); return; } if (savedInstanceState != null) { // if we are restoring, assume that initialization has already occurred. pageWaitingForData = savedInstanceState.containsKey(PAGE_WAITING_FOR_DATA) ? savedInstanceState.getString(PAGE_WAITING_FOR_DATA) : null; pathWaitingForData = savedInstanceState.containsKey(PATH_WAITING_FOR_DATA) ? savedInstanceState.getString(PATH_WAITING_FOR_DATA) : null; actionWaitingForData = savedInstanceState.containsKey(ACTION_WAITING_FOR_DATA) ? savedInstanceState.getString(ACTION_WAITING_FOR_DATA) : null; currentFragment = ScreenList.valueOf(savedInstanceState.containsKey(CURRENT_FRAGMENT) ? savedInstanceState.getString(CURRENT_FRAGMENT) : currentFragment.name()); if (savedInstanceState.containsKey(FORM_URI)) { FormIdStruct newForm = FormIdStruct.retrieveFormIdStruct(getContentResolver(), Uri.parse(savedInstanceState.getString(FORM_URI))); if (newForm != null) { setAppName(newForm.appName); setCurrentForm(newForm); } } setInstanceId(savedInstanceState.containsKey(INSTANCE_ID) ? savedInstanceState.getString(INSTANCE_ID) : getInstanceId()); setUploadTableId( savedInstanceState.containsKey(UPLOAD_TABLE_ID) ? savedInstanceState.getString(UPLOAD_TABLE_ID) : getUploadTableId()); String tmpScreenPath = savedInstanceState.containsKey(SCREEN_PATH) ? savedInstanceState.getString(SCREEN_PATH) : getScreenPath(); String tmpControllerState = savedInstanceState.containsKey(CONTROLLER_STATE) ? savedInstanceState.getString(CONTROLLER_STATE) : getControllerState(); setSectionScreenState(tmpScreenPath, tmpControllerState); setAuxillaryHash( savedInstanceState.containsKey(AUXILLARY_HASH) ? savedInstanceState.getString(AUXILLARY_HASH) : getAuxillaryHash()); if (savedInstanceState.containsKey(SESSION_VARIABLES)) { sessionVariables = savedInstanceState.getBundle(SESSION_VARIABLES); } if (savedInstanceState.containsKey(SECTION_STATE_SCREEN_HISTORY)) { sectionStateScreenHistory = savedInstanceState.getParcelableArrayList(SECTION_STATE_SCREEN_HISTORY); } } else if (formUri != null) { // request specifies a specific formUri -- try to open that FormIdStruct newForm = FormIdStruct.retrieveFormIdStruct(getContentResolver(), formUri); if (newForm == null) { // can't find it -- launch the initialization dialog to hopefully // discover it. WebLogger.getLogger(getAppName()).i(t, "onCreate -- calling setRunInitializationTask"); Survey.getInstance().setRunInitializationTask(getAppName()); currentFragment = ScreenList.WEBKIT; } else { transitionToFormHelper(uri, newForm); } } assignContentView(); } /** * This creates the WebKit. We need all our values initialized by this point. */ private void assignContentView() { setContentView(R.layout.main_screen); ActionBar actionBar = getActionBar(); actionBar.setDisplayOptions(ActionBar.DISPLAY_USE_LOGO); actionBar.show(); } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); int showOption = MenuItem.SHOW_AS_ACTION_IF_ROOM; MenuItem item; if (currentFragment != ScreenList.WEBKIT) { ActionBar actionBar = getActionBar(); actionBar.setDisplayOptions(ActionBar.DISPLAY_USE_LOGO | ActionBar.DISPLAY_SHOW_TITLE); actionBar.show(); item = menu.add(Menu.NONE, MENU_FILL_FORM, Menu.NONE, getString(R.string.enter_data_button)); item.setIcon(R.drawable.ic_action_collections_collection).setShowAsAction(showOption); // Using a file for this work now String get = PropertiesSingleton.getProperty(appName, AdminPreferencesActivity.KEY_GET_BLANK); if (get.equalsIgnoreCase("true")) { item = menu.add(Menu.NONE, MENU_PULL_FORMS, Menu.NONE, getString(R.string.get_forms)); item.setIcon(R.drawable.ic_action_av_download).setShowAsAction(showOption); item = menu.add(Menu.NONE, MENU_CLOUD_FORMS, Menu.NONE, getString(R.string.get_forms)); item.setIcon(R.drawable.ic_action_cloud).setShowAsAction(showOption); } String send = PropertiesSingleton.getProperty(appName, AdminPreferencesActivity.KEY_SEND_FINALIZED); if (send.equalsIgnoreCase("true")) { item = menu.add(Menu.NONE, MENU_PUSH_FORMS, Menu.NONE, getString(R.string.send_data)); item.setIcon(R.drawable.ic_action_av_upload).setShowAsAction(showOption); } String manage = PropertiesSingleton.getProperty(appName, AdminPreferencesActivity.KEY_MANAGE_FORMS); if (manage.equalsIgnoreCase("true")) { item = menu.add(Menu.NONE, MENU_MANAGE_FORMS, Menu.NONE, getString(R.string.manage_files)); item.setIcon(R.drawable.trash).setShowAsAction(showOption); } String settings = PropertiesSingleton.getProperty(appName, AdminPreferencesActivity.KEY_ACCESS_SETTINGS); if (settings.equalsIgnoreCase("true")) { item = menu.add(Menu.NONE, MENU_PREFERENCES, Menu.NONE, getString(R.string.general_preferences)); item.setIcon(R.drawable.ic_menu_preferences).setShowAsAction(showOption); } item = menu.add(Menu.NONE, MENU_ADMIN_PREFERENCES, Menu.NONE, getString(R.string.admin_preferences)); item.setIcon(R.drawable.ic_action_device_access_accounts).setShowAsAction(showOption); item = menu.add(Menu.NONE, MENU_ABOUT, Menu.NONE, getString(R.string.about)); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); } else { ActionBar actionBar = getActionBar(); actionBar.hide(); } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == MENU_FILL_FORM) { swapToFragmentView(ScreenList.FORM_CHOOSER); return true; } else if (item.getItemId() == MENU_PULL_FORMS) { swapToFragmentView(ScreenList.FORM_DOWNLOADER); return true; } else if (item.getItemId() == MENU_CLOUD_FORMS) { try { Intent syncIntent = new Intent(); syncIntent.setComponent( new ComponentName("org.opendatakit.sync", "org.opendatakit.sync.activities.SyncActivity")); syncIntent.setAction(Intent.ACTION_DEFAULT); Bundle bundle = new Bundle(); bundle.putString(APP_NAME, appName); syncIntent.putExtras(bundle); this.startActivityForResult(syncIntent, SYNC_ACTIVITY_CODE); } catch (ActivityNotFoundException e) { WebLogger.getLogger(getAppName()).printStackTrace(e); Toast.makeText(this, R.string.sync_not_found, Toast.LENGTH_LONG).show(); } return true; } else if (item.getItemId() == MENU_MANAGE_FORMS) { swapToFragmentView(ScreenList.FORM_DELETER); return true; } else if (item.getItemId() == MENU_EDIT_INSTANCE) { swapToFragmentView(ScreenList.WEBKIT); return true; } else if (item.getItemId() == MENU_PUSH_FORMS) { swapToFragmentView(ScreenList.INSTANCE_UPLOADER_TABLE_CHOOSER); return true; } else if (item.getItemId() == MENU_PREFERENCES) { // PreferenceFragment missing from support library... Intent ig = new Intent(this, PreferencesActivity.class); // TODO: convert this activity into a preferences fragment ig.putExtra(APP_NAME, getAppName()); startActivity(ig); return true; } else if (item.getItemId() == MENU_ADMIN_PREFERENCES) { String pw = PropertiesSingleton.getProperty(appName, AdminPreferencesActivity.KEY_ADMIN_PW); if (pw == null || "".equalsIgnoreCase(pw)) { Intent i = new Intent(getApplicationContext(), AdminPreferencesActivity.class); // TODO: convert this activity into a preferences fragment i.putExtra(APP_NAME, getAppName()); startActivity(i); } else { createPasswordDialog(); } return true; } else if (item.getItemId() == MENU_ABOUT) { swapToFragmentView(ScreenList.ABOUT_MENU); return true; } return super.onOptionsItemSelected(item); } @Override public void chooseForm(Uri formUri) { Intent i = new Intent(Intent.ACTION_EDIT, formUri, this, MainMenuActivity.class); startActivityForResult(i, INTERNAL_ACTIVITY_CODE); } @Override public void chooseInstanceUploaderTable(String tableId) { boolean success = true; // TODO: Verify there are no checkpoint saves on this tableId // TODO: Verify there are no checkpoint saves on this tableId // TODO: Verify there are no checkpoint saves on this tableId // TODO: Verify there are no checkpoint saves on this tableId // TODO: Verify there are no checkpoint saves on this tableId setUploadTableId(tableId); swapToFragmentView(ScreenList.INSTANCE_UPLOADER); } @Override public void onBackPressed() { FragmentManager mgr = getFragmentManager(); int idxLast = mgr.getBackStackEntryCount() - 2; if (idxLast < 0) { Intent result = new Intent(); // If we are in a WEBKIT, return the instanceId and the savepoint_type... if (this.getInstanceId() != null && currentFragment == ScreenList.WEBKIT) { result.putExtra("instanceId", getInstanceId()); // in this case, the savepoint_type is null (a checkpoint). } this.setResult(RESULT_OK, result); finish(); } else { BackStackEntry entry = mgr.getBackStackEntryAt(idxLast); swapToFragmentView(ScreenList.valueOf(entry.getName())); } } private void createErrorDialog(String errorMsg, final boolean shouldExit) { WebLogger.getLogger(getAppName()).e(t, errorMsg); if (mAlertDialog != null) { mAlertDialog.dismiss(); mAlertDialog = null; } mAlertDialog = new AlertDialog.Builder(this).create(); mAlertDialog.setIcon(android.R.drawable.ic_dialog_info); mAlertDialog.setMessage(errorMsg); DialogInterface.OnClickListener errorListener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int button) { switch (button) { case DialogInterface.BUTTON_POSITIVE: if (shouldExit) { Intent i = new Intent(); setResult(RESULT_CANCELED, i); finish(); } break; } } }; mAlertDialog.setCancelable(false); mAlertDialog.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.ok), errorListener); mAlertDialog.show(); } protected void createPasswordDialog() { if (mAlertDialog != null) { mAlertDialog.dismiss(); mAlertDialog = null; } final AlertDialog passwordDialog = new AlertDialog.Builder(this).create(); passwordDialog.setTitle(getString(R.string.enter_admin_password)); final EditText input = new EditText(this); input.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD); input.setTransformationMethod(PasswordTransformationMethod.getInstance()); passwordDialog.setView(input, 20, 10, 20, 10); passwordDialog.setButton(AlertDialog.BUTTON_POSITIVE, getString(R.string.ok), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { String value = input.getText().toString(); String pw = PropertiesSingleton.getProperty(appName, AdminPreferencesActivity.KEY_ADMIN_PW); if (pw != null && pw.compareTo(value) == 0) { Intent i = new Intent(getApplicationContext(), AdminPreferencesActivity.class); // TODO: convert this activity into a preferences fragment i.putExtra(APP_NAME, getAppName()); startActivity(i); input.setText(""); passwordDialog.dismiss(); } else { Toast.makeText(MainMenuActivity.this, getString(R.string.admin_password_incorrect), Toast.LENGTH_SHORT).show(); } } }); passwordDialog.setButton(AlertDialog.BUTTON_NEGATIVE, getString(R.string.cancel), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { input.setText(""); return; } }); passwordDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); mAlertDialog = passwordDialog; mAlertDialog.show(); } @Override public synchronized Bitmap getDefaultVideoPoster() { if (mDefaultVideoPoster == null) { mDefaultVideoPoster = BitmapFactory.decodeResource(getResources(), R.drawable.default_video_poster); } return mDefaultVideoPoster; } @Override public synchronized View getVideoLoadingProgressView() { if (mVideoProgressView == null) { LayoutInflater inflater = LayoutInflater.from(this); mVideoProgressView = inflater.inflate(R.layout.video_loading_progress, null); } return mVideoProgressView; } public void hideWebkitView() { // This is a callback thread. // We must invalidate the options menu on the UI thread this.runOnUiThread(new Runnable() { @Override public void run() { WebLogger.getLogger(getAppName()).i(t, "hideWebkitView"); // In the fragment UI, we want to return to not having any // instanceId defined. // JQueryODKView webkitView = (JQueryODKView) // findViewById(R.id.webkit_view); // setInstanceId(null); // setSectionScreenState(null,null); // setAuxillaryHash(null); // webkitView.loadPage(); levelSafeInvalidateOptionsMenu(); } }); } @Override public void swapToCustomView(View customView) { FrameLayout shadow = (FrameLayout) findViewById(R.id.shadow_content); View frags = findViewById(R.id.main_content); View wkt = findViewById(R.id.webkit_view); shadow.removeAllViews(); shadow.addView(customView, COVER_SCREEN_GRAVITY_CENTER); frags.setVisibility(View.GONE); wkt.setVisibility(View.GONE); shadow.setVisibility(View.VISIBLE); currentFragment = ScreenList.CUSTOM_VIEW; } @Override public void swapOffCustomView() { FrameLayout shadow = (FrameLayout) findViewById(R.id.shadow_content); View frags = findViewById(R.id.main_content); View wkt = findViewById(R.id.webkit_view); shadow.setVisibility(View.GONE); shadow.removeAllViews(); frags.setVisibility(View.GONE); wkt.setVisibility(View.VISIBLE); wkt.invalidate(); currentFragment = ScreenList.WEBKIT; levelSafeInvalidateOptionsMenu(); } public void swapToFragmentView(ScreenList newFragment) { WebLogger.getLogger(getAppName()).i(t, "swapToFragmentView: " + newFragment.toString()); FragmentManager mgr = getFragmentManager(); Fragment f; if (newFragment == ScreenList.MAIN_SCREEN) { throw new IllegalStateException("unexpected reference to generic main screen"); } else if (newFragment == ScreenList.CUSTOM_VIEW) { WebLogger.getLogger(getAppName()).w(t, "swapToFragmentView: changing navigation to move to WebKit (was custom view)"); f = mgr.findFragmentById(WebViewFragment.ID); if (f == null) { f = new WebViewFragment(); } newFragment = ScreenList.WEBKIT; } else if (newFragment == ScreenList.FORM_CHOOSER) { f = mgr.findFragmentById(FormChooserListFragment.ID); if (f == null) { f = new FormChooserListFragment(); } } else if (newFragment == ScreenList.INITIALIZATION_DIALOG) { if (currentFragment == ScreenList.INITIALIZATION_DIALOG) { WebLogger.getLogger(getAppName()).e(t, "Unexpected: currentFragment == INITIALIZATION_DIALOG"); return; } else { f = mgr.findFragmentById(InitializationFragment.ID); if (f == null) { f = new InitializationFragment(); } ((InitializationFragment) f).setFragmentToShowNext( (currentFragment == null) ? ScreenList.FORM_CHOOSER.name() : currentFragment.name()); } } else if (newFragment == ScreenList.FORM_DELETER) { f = mgr.findFragmentById(FormDeleteListFragment.ID); if (f == null) { f = new FormDeleteListFragment(); } } else if (newFragment == ScreenList.FORM_DOWNLOADER) { f = mgr.findFragmentById(FormDownloadListFragment.ID); if (f == null) { f = new FormDownloadListFragment(); } } else if (newFragment == ScreenList.INSTANCE_UPLOADER_TABLE_CHOOSER) { f = mgr.findFragmentById(InstanceUploaderTableChooserListFragment.ID); if (f == null) { f = new InstanceUploaderTableChooserListFragment(); } } else if (newFragment == ScreenList.INSTANCE_UPLOADER) { f = mgr.findFragmentById(InstanceUploaderListFragment.ID); if (f == null) { f = new InstanceUploaderListFragment(); } ((InstanceUploaderListFragment) f).changeUploadTableId(); } else if (newFragment == ScreenList.WEBKIT) { f = mgr.findFragmentById(WebViewFragment.ID); if (f == null) { f = new WebViewFragment(); } } else if (newFragment == ScreenList.ABOUT_MENU) { f = mgr.findFragmentById(AboutMenuFragment.ID); if (f == null) { f = new AboutMenuFragment(); } } else { throw new IllegalStateException("Unrecognized ScreenList type"); } FrameLayout shadow = (FrameLayout) findViewById(R.id.shadow_content); View frags = findViewById(R.id.main_content); View wkt = findViewById(R.id.webkit_view); shadow.setVisibility(View.GONE); shadow.removeAllViews(); if (newFragment == ScreenList.WEBKIT) { frags.setVisibility(View.GONE); wkt.setVisibility(View.VISIBLE); wkt.invalidate(); } else { wkt.setVisibility(View.GONE); frags.setVisibility(View.VISIBLE); } currentFragment = newFragment; BackStackEntry entry = null; for (int i = 0; i < mgr.getBackStackEntryCount(); ++i) { BackStackEntry e = mgr.getBackStackEntryAt(i); if (e.getName().equals(currentFragment.name())) { entry = e; break; } } if (entry != null) { // flush backward, including the screen want to go back to mgr.popBackStackImmediate(currentFragment.name(), FragmentManager.POP_BACK_STACK_INCLUSIVE); } // add transaction to show the screen we want FragmentTransaction trans = mgr.beginTransaction(); trans.replace(R.id.main_content, f); trans.addToBackStack(currentFragment.name()); trans.commit(); // and see if we should re-initialize... if ((currentFragment != ScreenList.INITIALIZATION_DIALOG) && Survey.getInstance().shouldRunInitializationTask(getAppName())) { WebLogger.getLogger(getAppName()).i(t, "swapToFragmentView -- calling clearRunInitializationTask"); // and immediately clear the should-run flag... Survey.getInstance().clearRunInitializationTask(getAppName()); // OK we should swap to the InitializationFragment view swapToFragmentView(ScreenList.INITIALIZATION_DIALOG); } else { levelSafeInvalidateOptionsMenu(); } } private void levelSafeInvalidateOptionsMenu() { invalidateOptionsMenu(); } /*********************************************************************** * ********************************************************************* * ********************************************************************* * ********************************************************************* * ********************************************************************* * ********************************************************************* * Interfaces to Javascript layer (also used in Java). */ private void dumpScreenStateHistory() { WebLogger l = WebLogger.getLogger(getAppName()); l.d(t, "-------------*start* dumpScreenStateHistory--------------------"); if (sectionStateScreenHistory.isEmpty()) { l.d(t, "sectionScreenStateHistory EMPTY"); } else { for (int i = sectionStateScreenHistory.size() - 1; i >= 0; --i) { SectionScreenStateHistory thisSection = sectionStateScreenHistory.get(i); l.d(t, "[" + i + "] screenPath: " + thisSection.currentScreen.screenPath); l.d(t, "[" + i + "] state: " + thisSection.currentScreen.state); if (thisSection.history.isEmpty()) { l.d(t, "[" + i + "] history[] EMPTY"); } else { for (int j = thisSection.history.size() - 1; j >= 0; --j) { ScreenState ss = thisSection.history.get(j); l.d(t, "[" + i + "] history[" + j + "] screenPath: " + ss.screenPath); l.d(t, "[" + i + "] history[" + j + "] state: " + ss.state); } } } } l.d(t, "------------- *end* dumpScreenStateHistory--------------------"); } @Override public void pushSectionScreenState() { if (sectionStateScreenHistory.size() == 0) { WebLogger.getLogger(getAppName()).i(t, "pushSectionScreenState: NULL!"); return; } SectionScreenStateHistory lastSection = sectionStateScreenHistory.get(sectionStateScreenHistory.size() - 1); lastSection.history .add(new ScreenState(lastSection.currentScreen.screenPath, lastSection.currentScreen.state)); } @Override public void setSectionScreenState(String screenPath, String state) { if (screenPath == null) { WebLogger.getLogger(getAppName()).e(t, "pushSectionScreenState: NULL currentScreen.screenPath!"); return; } else { String[] splits = screenPath.split("/"); String sectionName = splits[0] + "/"; SectionScreenStateHistory lastSection; if (sectionStateScreenHistory.size() == 0) { sectionStateScreenHistory.add(new SectionScreenStateHistory()); lastSection = sectionStateScreenHistory.get(sectionStateScreenHistory.size() - 1); lastSection.currentScreen.screenPath = screenPath; lastSection.currentScreen.state = state; lastSection.history.clear(); } else { lastSection = sectionStateScreenHistory.get(sectionStateScreenHistory.size() - 1); if (lastSection.currentScreen.screenPath.startsWith(sectionName)) { lastSection.currentScreen.screenPath = screenPath; lastSection.currentScreen.state = state; } else { sectionStateScreenHistory.add(new SectionScreenStateHistory()); lastSection = sectionStateScreenHistory.get(sectionStateScreenHistory.size() - 1); lastSection.currentScreen.screenPath = screenPath; lastSection.currentScreen.state = state; lastSection.history.clear(); } } } } @Override public void clearSectionScreenState() { sectionStateScreenHistory.clear(); sectionStateScreenHistory.add(new SectionScreenStateHistory()); SectionScreenStateHistory lastSection = sectionStateScreenHistory.get(sectionStateScreenHistory.size() - 1); lastSection.currentScreen.screenPath = "initial/0"; lastSection.currentScreen.state = null; lastSection.history.clear(); } @Override public String getControllerState() { if (sectionStateScreenHistory.size() == 0) { WebLogger.getLogger(getAppName()).i(t, "getControllerState: NULL!"); return null; } SectionScreenStateHistory lastSection = sectionStateScreenHistory.get(sectionStateScreenHistory.size() - 1); return lastSection.currentScreen.state; } public String getScreenPath() { dumpScreenStateHistory(); if (sectionStateScreenHistory.size() == 0) { WebLogger.getLogger(getAppName()).i(t, "getScreenPath: NULL!"); return null; } SectionScreenStateHistory lastSection = sectionStateScreenHistory.get(sectionStateScreenHistory.size() - 1); return lastSection.currentScreen.screenPath; } @Override public boolean hasScreenHistory() { // two or more sections -- there must be history if (sectionStateScreenHistory.size() > 1) { return true; } // no sections -- no history if (sectionStateScreenHistory.size() == 0) { return false; } SectionScreenStateHistory thisSection = sectionStateScreenHistory.get(0); return thisSection.history.size() != 0; } @Override public String popScreenHistory() { if (sectionStateScreenHistory.size() == 0) { return null; } SectionScreenStateHistory lastSection; lastSection = sectionStateScreenHistory.get(sectionStateScreenHistory.size() - 1); if (lastSection.history.size() != 0) { ScreenState lastHistory = lastSection.history.remove(lastSection.history.size() - 1); lastSection.currentScreen.screenPath = lastHistory.screenPath; lastSection.currentScreen.state = lastHistory.state; return lastSection.currentScreen.screenPath; } // pop to an enclosing screen sectionStateScreenHistory.remove(sectionStateScreenHistory.size() - 1); if (sectionStateScreenHistory.size() == 0) { return null; } lastSection = sectionStateScreenHistory.get(sectionStateScreenHistory.size() - 1); return lastSection.currentScreen.screenPath; } @Override public boolean hasSectionStack() { return sectionStateScreenHistory.size() != 0; } @Override public String popSectionStack() { if (sectionStateScreenHistory.size() != 0) { sectionStateScreenHistory.remove(sectionStateScreenHistory.size() - 1); } if (sectionStateScreenHistory.size() != 0) { SectionScreenStateHistory lastSection = sectionStateScreenHistory .get(sectionStateScreenHistory.size() - 1); return lastSection.currentScreen.screenPath; } return null; } @Override public void setSessionVariable(String elementPath, String jsonValue) { sessionVariables.putString(elementPath, jsonValue); } @Override public String getSessionVariable(String elementPath) { return sessionVariables.getString(elementPath); } @Override public void saveAllChangesCompleted(String instanceId, final boolean asComplete) { Intent result = new Intent(); result.putExtra("instanceId", instanceId); result.putExtra("savepoint_type", "COMPLETE"); // TODO: unclear what to put in the result intent... this.setResult(RESULT_OK, result); finish(); } @Override public void saveAllChangesFailed(String instanceId) { // should we message anything? } @Override public void ignoreAllChangesCompleted(String instanceId) { Intent result = new Intent(); result.putExtra("instanceId", instanceId); result.putExtra("savepoint_type", "INCOMPLETE"); // TODO: unclear what to put in the result intent... this.setResult(RESULT_OK, result); finish(); } @Override public void ignoreAllChangesFailed(String instanceId) { // should we message anything? } /** * Invoked from within Javascript to launch an activity. * * @param page * -- page containing prompt requesting the action * @param path * -- prompt requesting the action * @param action * -- the intent to be launched * @param valueContentMap * -- parameters to pass to the intent * { * uri: uriValue, // parse to a uri and set as the data of the * // intent * extras: extrasMap, // added as extras to the intent * package: packageStr, // the name of a package to launch * type: typeStr, // will be set as the type * data: dataUri // will be parsed to a uri and set as the data of * // the intent. For now this is equivalent to the * // uri field, although that name is less precise. * } */ @Override public String doAction(String page, String path, String action, JSONObject valueContentMap) { // android.os.Debug.waitForDebugger(); if (isWaitingForBinaryData()) { WebLogger.getLogger(getAppName()).w(t, "Already waiting for data -- ignoring"); return "IGNORE"; } Intent i; boolean isSurveyApp = false; boolean isTablesApp = false; if (action.startsWith("org.opendatakit.survey")) { Class<?> clazz; try { clazz = Class.forName(action); i = new Intent(this, clazz); isSurveyApp = true; } catch (ClassNotFoundException e) { WebLogger.getLogger(getAppName()).printStackTrace(e); i = new Intent(action); } } else { i = new Intent(action); } if (action.startsWith("org.opendatakit.tables")) { isTablesApp = true; } try { String uriKey = "uri"; String extrasKey = "extras"; String packageKey = "package"; String typeKey = "type"; String dataKey = "data"; JSONObject valueMap = null; if (valueContentMap != null) { // do type first, as it says in the spec this call deletes any other // data (eg by setData()) on the intent. if (valueContentMap.has(typeKey)) { String type = valueContentMap.getString(typeKey); i.setType(type); } if (valueContentMap.has(uriKey) || valueContentMap.has(dataKey)) { // as it currently stands, the data property can be in either the uri // or data keys. String uriValueStr = null; if (valueContentMap.has(uriKey)) { uriValueStr = valueContentMap.getString(uriKey); } // go ahead and overwrite with data if it's present. if (valueContentMap.has(dataKey)) { uriValueStr = valueContentMap.getString(dataKey); } if (uriValueStr != null) { Uri uri = Uri.parse(uriValueStr); i.setData(uri); } } if (valueContentMap.has(extrasKey)) { valueMap = valueContentMap.getJSONObject(extrasKey); } if (valueContentMap.has(packageKey)) { String packageStr = valueContentMap.getString(packageKey); i.setPackage(packageStr); } } if (valueMap != null) { Bundle b; final DynamicPropertiesCallback cb = new DynamicPropertiesCallback(this, getAppName(), getCurrentForm().tableId, getInstanceId()); b = AndroidUtils.convertToBundle(valueMap, new MacroStringExpander() { @Override public String expandString(String value) { if (value != null && value.startsWith("opendatakit-macro(") && value.endsWith(")")) { String term = value.substring("opendatakit-macro(".length(), value.length() - 1).trim(); String v = mPropertyManager.getSingularProperty(term, cb); if (v != null) { return v; } else { WebLogger.getLogger(getAppName()).e(t, "Unable to process opendatakit-macro: " + value); throw new IllegalArgumentException( "Unable to process opendatakit-macro expression: " + value); } } else { return value; } } }); i.putExtras(b); } if (isSurveyApp || isTablesApp) { // ensure that we supply our appName... if (!i.hasExtra(APP_NAME)) { i.putExtra(APP_NAME, getAppName()); WebLogger.getLogger(getAppName()).w(t, "doAction into Survey or Tables does not supply an appName. Adding: " + getAppName()); } } } catch (Exception ex) { WebLogger.getLogger(getAppName()).e(t, "JSONException: " + ex.toString()); WebLogger.getLogger(getAppName()).printStackTrace(ex); return "JSONException: " + ex.toString(); } pageWaitingForData = page; pathWaitingForData = path; actionWaitingForData = action; try { startActivityForResult(i, HANDLER_ACTIVITY_CODE); return "OK"; } catch (ActivityNotFoundException ex) { WebLogger.getLogger(getAppName()).e(t, "Unable to launch activity: " + ex.toString()); WebLogger.getLogger(getAppName()).printStackTrace(ex); return "Application not found"; } } /* * END - Interfaces to Javascript layer (also used in Java). * ********************************************************************* * ********************************************************************* * ********************************************************************* * ********************************************************************* * ********************************************************************* * ********************************************************************* */ public boolean isWaitingForBinaryData() { return actionWaitingForData != null; } @Override public void onActivityResult(int requestCode, int resultCode, Intent intent) { WebLogger.getLogger(getAppName()).i(t, "onActivityResult"); ODKWebView view = (ODKWebView) findViewById(R.id.webkit_view); if (requestCode == HANDLER_ACTIVITY_CODE) { try { String jsonObject = null; Bundle b = (intent == null) ? null : intent.getExtras(); JSONObject val = (b == null) ? null : AndroidUtils.convertFromBundle(getAppName(), b); jsonObject = "{\"status\":" + Integer.toString(resultCode) + ((val == null) ? "" : ", \"result\":" + val.toString()) + "}"; WebLogger.getLogger(getAppName()).i(t, "HANDLER_ACTIVITY_CODE: " + jsonObject); view.doActionResult(pageWaitingForData, pathWaitingForData, actionWaitingForData, jsonObject); } catch (Exception e) { view.doActionResult(pageWaitingForData, pathWaitingForData, actionWaitingForData, "{ \"status\":0, \"result\":\"" + e.toString() + "\"}"); } finally { pathWaitingForData = null; pageWaitingForData = null; actionWaitingForData = null; } } else if (requestCode == SYNC_ACTIVITY_CODE) { Survey.getInstance().setRunInitializationTask(getAppName()); this.swapToFragmentView((currentFragment == null) ? ScreenList.FORM_CHOOSER : currentFragment); } } }