Java tutorial
/* Copyright (c) 2013 Max Lungarella This file is part of AmiKo for Android. AmiKo for Android is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.ywesee.amiko; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.Method; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Observable; import java.util.Observer; import java.util.regex.Matcher; import java.util.regex.Pattern; import android.Manifest; import android.animation.LayoutTransition; import android.annotation.TargetApi; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.DownloadManager; import android.app.DownloadManager.Query; import android.app.DownloadManager.Request; import android.app.ProgressDialog; import android.app.Service; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnDismissListener; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Picture; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.PictureDrawable; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.support.annotation.NonNull; import android.support.design.widget.BottomNavigationView; import android.support.v4.app.ActivityCompat; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBar.Tab; import android.support.v7.app.AppCompatActivity; import android.text.Editable; import android.text.Selection; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextWatcher; import android.text.style.ForegroundColorSpan; import android.util.Log; import android.view.GestureDetector; import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; import android.view.ViewGroup; import android.view.Window; import android.view.animation.TranslateAnimation; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.webkit.WebView; import android.webkit.WebView.FindListener; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import android.widget.TextView.BufferType; import android.widget.Toast; import com.ywesee.amiko.R; public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private static final String AMIKO_PREFS_FILE = "AmikoPrefsFile"; private static final String PREF_DB_UPDATE_DATE_DE = "GermanDBUpdateDate"; private static final String PREF_DB_UPDATE_DATE_FR = "FrenchDBUpdateDate"; // German section title abbreviations private static final String[] SectionTitle_DE = { "Zusammensetzung", "Galenische Form", "Kontraindikationen", "Indikationen", "Dosierung/Anwendung", "Vorsichtsmassnahmen", "Interaktionen", "Schwangerschaft", "Fahrtchtigkeit", "Unerwnschte Wirk.", "berdosierung", "Eig./Wirkung", "Kinetik", "Prklinik", "Sonstige Hinweise", "Zulassungsnummer", "Packungen", "Inhaberin", "Stand der Information" }; // French section title abbrevations private static final String[] SectionTitle_FR = { "Composition", "Forme galnique", "Contre-indications", "Indications", "Posologie", "Prcautions", "Interactions", "Grossesse/All.", "Conduite", "Effets indsir.", "Surdosage", "Proprits/Effets", "Cintique", "Prclinique", "Remarques", "Numro d'autorisation", "Prsentation", "Titulaire", "Mise jour" }; // Main AsyncTask private AsyncSearchTask mAsyncSearchTask = null; // SQLite database adapter private DBAdapter mMediDataSource; // List of medications returned by SQLite query private List<Medication> mMedis = null; // Index of most recently clicked medication private long mMedIndex = -1; // Html string displayed in show_view private String mHtmlString; // Current action bar tab private String mActionName = ""; // Current search query private String mSearchQuery = ""; // Minimum number of characters used for SQLite query (default: min 1-chars search) private int mMinCharSearch = 0; // Global timer used for benchmarking app private long mTimer = 0; // Listview of suggestions returned by SQLite query private ListView mListView = null; // ListView of section titles (shortcuts) private ListView mSectionView = null; // Webview used to display "Fachinformation" and the "med interaction basket" private WebView mWebView; // Cascading style sheet private String mCSS_str = null; // Hashset containing registration numbers of favorite medications private HashSet<String> mFavoriteMedsSet = null; // Reference to favorites' datastore private DataStore mFavoriteData = null; // This is the currently used database private String mDatabaseUsed = "aips"; // Searching for interactions? private boolean mSearchInteractions = false; // Drug interaction basket private Interactions mMedInteractionBasket = null; // Actionbar menu items private MenuItem mSearchItem = null; private EditText mSearch = null; private Button mDelete = null; // Viewholder and views private ViewGroup mViewHolder = null; private View mSuggestView = null; private View mShowView = null; // This is the currently visible view private View mCurrentView = null; private BottomNavigationView mBottomNavigationView; // This is the drawerlayout for the section titles in expert view private DrawerLayout mDrawerLayout = null; // This is the global toast object private CustomToast mToastObject = null; // Downloadmanager related private BroadcastReceiver mBroadcastReceiver; private ProgressDialog mProgressBar; // In-text-search hits counter private TextView mSearchHitsCntView = null; // Global flag for checking if a search is in progress private boolean mSearchInProgress = false; // Global flag for checking of we are restoring a state private boolean mRestoringState = false; // Global flag to signal that update is in progress private boolean mUpdateInProgress = false; // Global flag to signal if main db has been initialized private boolean mSQLiteDBInitialized = false; // Splash screen dialog private Dialog mSplashDialog = null; /** * The download manager is a system service that handles long-running HTTP downloads. * Clients may request that a URI be downloaded to a particular destination file. * The download manager will conduct the download in the background, taking care of * HTTP interactions and retrying downloads after failures or across connectivity changes * and system reboots. */ private DownloadManager mDownloadManager = null; private long mDatabaseId = 0; // SQLite DB (zipped) private long mReportId = 0; // Report file (html) private long mInteractionsId = 0; // Drug interactions file (zipped) private long mDownloadedFileCount = 0; /** * Show soft keyboard */ private void showSoftKeyboard(int duration) { mSearch.requestFocus(); mSearch.postDelayed(new Runnable() { @Override public void run() { // Display keyboard InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.showSoftInput(mSearch, InputMethodManager.SHOW_IMPLICIT); } }, duration); } /** * Hide soft keyboard */ private void hideSoftKeyboard(int duration) { mSearch.requestFocus(); mSearch.postDelayed(new Runnable() { @Override public void run() { // Remove keyboard InputMethodManager imm = (InputMethodManager) getSystemService(Service.INPUT_METHOD_SERVICE); if (imm != null && mSearch.getWindowToken() != null) imm.hideSoftInputFromWindow(mSearch.getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY); } }, duration); } private void showDownloadAlert(int install_type) { // Display message box asking people whether they want to download the DB from the ywesee server. AlertDialog.Builder alert = new AlertDialog.Builder(MainActivity.this); alert.setIcon(R.drawable.desitin_new); if (Constants.appLanguage().equals("de")) { alert.setTitle("Medikamentendatenbank"); String message = "AmiKo wurde installiert."; if (install_type == 1) message = "Ihre Datenbank ist lter als 30 Tage."; alert.setMessage( message + " Empfehlung: Laden Sie jetzt die tagesaktuelle Datenbank runter (ca. 50 MB). " + "Sie knnen die Daten tglich aktualisieren, falls Sie wnschen."); } else if (Constants.appLanguage().equals("fr")) { alert.setTitle("Banque de donnes des mdicaments"); String message = "L'installation de la nouvelle version de CoMed s'est droule."; if (install_type == 1) message = "Votre banque de donnes est age plus de 30 jours."; alert.setMessage(message + " Vous avez tout intrt de mettre jour " + "votre banque de donnes (env. 50 MB). D'ailleurs vous pouvez utiliser le download tout moment si vous dsirez."); } String yes = "Ja"; String no = "Nein"; if (Constants.appLanguage().equals("fr")) { yes = "Oui"; no = "Non"; } alert.setPositiveButton(yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { if (!mUpdateInProgress) requestPermissionAndDownloadUpdates(); } }); alert.setNegativeButton(no, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { // Do nothing... } }); alert.show(); } public void requestPermissionAndDownloadUpdates() { if (Build.VERSION.SDK_INT < 23) { //permission is automatically granted on sdk<23 upon installation downloadUpdates(); } else if (checkSelfPermission( android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { downloadUpdates(); } else { ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, 0); } } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == 0) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { downloadUpdates(); } } } @Override public Object onRetainCustomNonConfigurationInstance() { return mMedis; } /** * Shows the splash screen over the full Activity */ protected void showSplashScreen(boolean showIt, boolean dismissAuto) { if (showIt) { mSplashDialog = new Dialog(this, android.R.style.Theme_Holo /*Translucent_NoTitleBar_Fullscreen*/); mSplashDialog.setContentView(R.layout.splash_screen); mSplashDialog.setCancelable(false); mSplashDialog.show(); // Enable flag (disable toast message) mRestoringState = true; mSplashDialog.setOnDismissListener(new OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { createMainLayout(); // Re-enable toaster once the splash screen has been removed... mRestoringState = false; // mSearch.requestFocus(); // Show keyboard showSoftKeyboard(100); } }); // Set Runnable to remove splash screen just in case if (dismissAuto) { final Handler handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { if (mSplashDialog != null) { mSplashDialog.dismiss(); mSplashDialog = null; } } }, 3000); } } } protected void dismissSplashScreen() { if (mSplashDialog != null) { mSplashDialog.dismiss(); mSplashDialog = null; } } /** * Sets action bar tab click listeners */ private void addTabNavigation() { ActionBar actionBar = getSupportActionBar(); // Disable activity title actionBar.setDisplayShowTitleEnabled(false); // Hide caret symbol ("<") upper left corner actionBar.setDisplayHomeAsUpEnabled(false); actionBar.setHomeButtonEnabled(false); actionBar.setDisplayUseLogoEnabled(true); actionBar.setDisplayShowHomeEnabled(true); actionBar.setIcon(R.drawable.ic_launcher); // Sets color of action bar (including alpha-channel) actionBar.setBackgroundDrawable(new ColorDrawable(Color.argb(255, 180, 180, 180))); // actionBar.removeAllTabs(); actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); actionBar.setTitle(R.string.app_name); Tab tab = actionBar.newTab().setText(R.string.tab_name_1).setTabListener( new MyTabListener(this, TabFragment.class.getName(), getString(R.string.tab_name_1))); actionBar.addTab(tab); tab = actionBar.newTab().setText(R.string.tab_name_2).setTabListener( new MyTabListener(this, TabFragment.class.getName(), getString(R.string.tab_name_2))); actionBar.addTab(tab); tab = actionBar.newTab().setText(R.string.tab_name_3).setTabListener( new MyTabListener(this, TabFragment.class.getName(), getString(R.string.tab_name_3))); actionBar.addTab(tab); tab = actionBar.newTab().setText(R.string.tab_name_4).setTabListener( new MyTabListener(this, TabFragment.class.getName(), getString(R.string.tab_name_4))); actionBar.addTab(tab); tab = actionBar.newTab().setText(R.string.tab_name_5).setTabListener( new MyTabListener(this, TabFragment.class.getName(), getString(R.string.tab_name_5))); actionBar.addTab(tab); } private void restoreTabNavigation() { if (mActionName.equals(getString(R.string.tab_name_1))) getSupportActionBar().setSelectedNavigationItem(0); else if (mActionName.equals(getString(R.string.tab_name_2))) getSupportActionBar().setSelectedNavigationItem(1); else if (mActionName.equals(getString(R.string.tab_name_3))) getSupportActionBar().setSelectedNavigationItem(2); else if (mActionName.equals(getString(R.string.tab_name_4))) getSupportActionBar().setSelectedNavigationItem(3); else if (mActionName.equals(getString(R.string.tab_name_5))) getSupportActionBar().setSelectedNavigationItem(4); } private void removeTabNavigation() { try { ActionBar actionbar = (ActionBar) getSupportActionBar(); actionbar.selectTab(null); } catch (Exception e) { // Do nothing } // Alternative -> removeAllTabs(); } /** * Sets currently visible view * @param newCurrentView * @param withAnimation */ private void setCurrentView(View newCurrentView, boolean withAnimation) { if (mCurrentView == newCurrentView) return; // It's important to perform sanity checks on views and viewgroup if (mViewHolder != null) { // Set direction of transitation old view to new view int direction = -1; if (mCurrentView == mShowView) { direction = 1; } // Remove current view if (mCurrentView != null) { if (withAnimation == true) { TranslateAnimation animate = new TranslateAnimation(0, direction * mCurrentView.getWidth(), 0, 0); animate.setDuration(200); animate.setFillAfter(false); mCurrentView.startAnimation(animate); } mCurrentView.setVisibility(View.GONE); } // Add new view if (newCurrentView != null) { if (withAnimation == true) { TranslateAnimation animate = new TranslateAnimation(-direction * newCurrentView.getWidth(), 0, 0, 0); animate.setDuration(200); animate.setFillAfter(false); newCurrentView.startAnimation(animate); } newCurrentView.setVisibility(View.VISIBLE); } // Update currently visible view mCurrentView = newCurrentView; // Hide keyboard if (mCurrentView == mShowView) { hideSoftKeyboard(300); removeTabNavigation(); // getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); } else if (mCurrentView == mSuggestView) { restoreTabNavigation(); // getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); } } } /** * Changes to "suggestView" - called from ExpertInfoView */ public void setSuggestView() { // Enable flag mRestoringState = true; // Set view setCurrentView(mSuggestView, true); // Remove search hit counter mSearchHitsCntView.setVisibility(View.GONE); // Old search query if (!mSearchQuery.isEmpty()) mSearch.setText(mSearchQuery); else mSearch.getText().clear(); Editable s = mSearch.getText(); // Set cursor at the end Selection.setSelection(s, s.length()); // Restore search results showResults(mMedis); // Restore hint mSearch.setHint(getString(R.string.search) + " " + mActionName); // Show keyboard showSoftKeyboard(300); // Disable flag mRestoringState = false; } /** * Restore view state */ public void resetView(boolean doSearch) { mRestoringState = true; // Set database mDatabaseUsed = "aips"; // Change view setCurrentView(mSuggestView, true); // Set tab getSupportActionBar().setSelectedNavigationItem(0); // Restore hint mActionName = getString(R.string.tab_name_1); // Prparat mSearch.setHint(getString(R.string.search) + " " + mActionName); // Reset search if (mSearch.length() > 0) mSearch.getText().clear(); if (doSearch == true) performSearch(""); // Request menu update invalidateOptionsMenu(); mRestoringState = false; } /** * Implements listeners for action bar * @author MaxL */ private class MyTabListener implements ActionBar.TabListener { private Fragment mFragment; private final Activity mActivity; private final String mFragName; private final String mTabName; public MyTabListener(Activity activity, String fragName, String tabName) { mActivity = activity; mFragName = fragName; mTabName = tabName; // mActionName = getString(R.string.tab_name_1); // Prparat } @Override public void onTabSelected(Tab tab, FragmentTransaction ft) { mFragment = Fragment.instantiate(mActivity, mFragName); ft.add(android.R.id.content, mFragment); mActionName = mTabName; if (mMedis != null) { mTimer = System.currentTimeMillis(); showResults(mMedis); } // Set hint if (mSearch != null) { mSearch.setHint(getString(R.string.search) + " " + mTabName); } // Change content view if (mCurrentView == mSuggestView) setCurrentView(mSuggestView, true); else if (mCurrentView == mShowView) setSuggestView(); } @Override public void onTabReselected(Tab tab, FragmentTransaction ft) { if (mSearch != null) { // Set hint mSearch.setHint(getString(R.string.search) + " " + mTabName); } // Change content view if (mCurrentView == mSuggestView) setCurrentView(mSuggestView, true); else if (mCurrentView == mShowView) setSuggestView(); } @Override public void onTabUnselected(Tab tab, FragmentTransaction ft) { ft.remove(mFragment); mFragment = null; } } @TargetApi(16) void setLayoutTransition() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { LayoutTransition lt = new LayoutTransition(); lt.enableTransitionType(LayoutTransition.CHANGING); lt.setDuration(LayoutTransition.APPEARING, 100 /*500*/); lt.setDuration(LayoutTransition.DISAPPEARING, 100); mViewHolder.setLayoutTransition(lt); } } @TargetApi(16) void setFindListener(final WebView webView) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { webView.setFindListener(new FindListener() { @Override public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) { // Update hits counter if (isDoneCounting) { if (activeMatchOrdinal < numberOfMatches) { mSearchHitsCntView.setVisibility(View.VISIBLE); mSearchHitsCntView.setText((activeMatchOrdinal + 1) + "/" + numberOfMatches); } else { mSearchHitsCntView.setVisibility(View.GONE); webView.clearMatches(); } } } }); } } /** * This function creates the main layout, called when splashscreen is over */ public void createMainLayout() { // setContentView(R.layout.activity_main); // Initialize views mSuggestView = getLayoutInflater().inflate(R.layout.suggest_view, null); mShowView = getLayoutInflater().inflate(R.layout.show_view, null); // Sets current view mCurrentView = mSuggestView; // Setup webviews // Add views to viewholder mViewHolder = (ViewGroup) findViewById(R.id.main_layout); mViewHolder.addView(mSuggestView); mViewHolder.addView(mShowView); setLayoutTransition(); mBottomNavigationView = findViewById(R.id.bottom_navigation); setupBottomNavigationViewListener(); // Define and load webview ExpertInfoView mExpertInfoView = new ExpertInfoView(this, (WebView) findViewById(R.id.fach_info_view)); mExpertInfoView.adjustZoom(); mWebView = mExpertInfoView.getWebView(); setFindListener(mWebView); setupGestureDetector(mWebView); // Set up observer to JS messages JSInterface jsinterface = mExpertInfoView.getJSInterface(); jsinterface.addObserver(new Observer() { @Override public void update(Observable o, Object arg) { String s = (String) arg; if (s.equals("notify_interaction")) { // Remove softkeyboard hideSoftKeyboard(100); // Take screenshot and start email activity after 500ms (wait for the keyboard to disappear) final Handler handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { sendFeedbackScreenshot(MainActivity.this, 2); } }, 500); } else { if (s.equals("delete_all")) { mMedInteractionBasket.clearBasket(); } else { mMedInteractionBasket.deleteFromBasket(s); } // // TODO: please comment this "hack" // Handler mainHandler = new Handler(getMainLooper()); mainHandler.post(new Runnable() { @Override public void run() { mMedInteractionBasket.updateInteractionsHtml(); String html_str = mMedInteractionBasket.getInteractionsHtml(); mWebView.loadDataWithBaseURL("file:///android_res/drawable/", html_str, "text/html", "utf-8", null); } }); } } }); // Initialize suggestion listview mListView = (ListView) findViewById(R.id.suggestView); mListView.setClickable(true); // Set visibility of views mSuggestView.setVisibility(View.VISIBLE); mShowView.setVisibility(View.GONE); // Setup initial view setCurrentView(mSuggestView, false); // Reset it resetView(false); } private void checkTimeSinceLastUpdate() { SharedPreferences settings = getSharedPreferences(AMIKO_PREFS_FILE, 0); long timeMillisSince1970 = settings.getLong(PREF_DB_UPDATE_DATE_DE, 0); long timeDiff = (System.currentTimeMillis() - timeMillisSince1970) / 1000; // That's 30 days in seconds ;) if (timeDiff > 60 * 60 * 24 * 30) showDownloadAlert(1); if (Constants.DEBUG) Log.d(TAG, "Time since last update: " + timeDiff + " sec"); } @Override public void onPause() { super.onPause(); if (mBroadcastReceiver != null) unregisterReceiver(mBroadcastReceiver); if (mProgressBar != null && mProgressBar.isShowing()) mProgressBar.dismiss(); } @Override public void onResume() { super.onResume(); if (mProgressBar != null && mProgressBar.isShowing()) mProgressBar.dismiss(); registerReceiver(mBroadcastReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); // Check time since last update checkTimeSinceLastUpdate(); } /** * Overrides onCreate method */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); try { AsyncLoadDBTask loadDBTask = new AsyncLoadDBTask(this); loadDBTask.execute(); } catch (Exception e) { Log.e(TAG, "AsyncLoadDBTask exception caught!"); } // Load CSS from asset folder if (Utilities.isTablet(this)) mCSS_str = Utilities.loadFromAssetsFolder(this, "amiko_stylesheet.css", "UTF-8"); else mCSS_str = Utilities.loadFromAssetsFolder(this, "amiko_stylesheet_phone.css", "UTF-8"); // Flag for enabling the Action Bar on top getWindow().requestFeature(Window.FEATURE_ACTION_BAR); // Enable overlay mode for action bar (no good, search results disappear behind it...) // getWindow().requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY); // Create action bar int mode = ActionBar.NAVIGATION_MODE_TABS; if (savedInstanceState != null) { mode = savedInstanceState.getInt("mode", ActionBar.NAVIGATION_MODE_TABS); } // Sets tab bar items addTabNavigation(); // Reset action name Log.d(TAG, "OnCreate -> " + mActionName); mActionName = getString(R.string.tab_name_1); /* 'getFilesDir' returns a java.io.File object representing the root directory of the INTERNAL storage four the application from the current context. */ mFavoriteData = new DataStore(this.getFilesDir().toString()); // Load hashset containing registration numbers from persistent data store mFavoriteMedsSet = new HashSet<String>(); mFavoriteMedsSet = mFavoriteData.load(); // Initialize preferences SharedPreferences settings = getSharedPreferences(AMIKO_PREFS_FILE, 0); long timeMillisSince1970 = 0; if (Constants.appLanguage().equals("de")) { timeMillisSince1970 = settings.getLong(PREF_DB_UPDATE_DATE_DE, 0); if (timeMillisSince1970 == 0) { SharedPreferences.Editor editor = settings.edit(); editor.putLong(PREF_DB_UPDATE_DATE_DE, System.currentTimeMillis()); // Commit the edits! editor.commit(); } } else if (Constants.appLanguage().equals("fr")) { timeMillisSince1970 = settings.getLong(PREF_DB_UPDATE_DATE_FR, 0); if (timeMillisSince1970 == 0) { SharedPreferences.Editor editor = settings.edit(); editor.putLong(PREF_DB_UPDATE_DATE_DE, System.currentTimeMillis()); // Commit the edits! editor.commit(); } } checkTimeSinceLastUpdate(); // Init toast object mToastObject = new CustomToast(this); // Initialize download manager mDownloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE); mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) { long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0); if (downloadId == mDatabaseId || downloadId == mReportId || downloadId == mInteractionsId) mDownloadedFileCount++; // Before proceeding make sure all files have been downloaded before proceeding if (mDownloadedFileCount == 3) { Query query = new Query(); query.setFilterById(downloadId); Cursor c = mDownloadManager.query(query); if (c.moveToFirst()) { int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS); // Check if download was successful if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) { try { // Update database AsyncUpdateDBTask updateDBTask = new AsyncUpdateDBTask(MainActivity.this); updateDBTask.execute(); } catch (Exception e) { Log.e(TAG, "AsyncUpdateDBTask: exception caught!"); } // Toast mToastObject.show("Databases downloaded successfully. Installing...", Toast.LENGTH_SHORT); if (mProgressBar.isShowing()) mProgressBar.dismiss(); mUpdateInProgress = false; // Store time stamp SharedPreferences settings = getSharedPreferences(AMIKO_PREFS_FILE, 0); SharedPreferences.Editor editor = settings.edit(); editor.putLong(PREF_DB_UPDATE_DATE_DE, System.currentTimeMillis()); // Commit the edits! editor.commit(); } else { mToastObject.show("Error while downloading database...", Toast.LENGTH_SHORT); } } c.close(); } } } }; registerReceiver(mBroadcastReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); } /** * */ @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); // Inflate the menu. Add items to the action bar if present. getMenuInflater().inflate(R.menu.actionbar, menu); mSearchItem = menu.findItem(R.id.menu_search); mSearchItem.expandActionView(); mSearchItem.setVisible(true); mSearch = (EditText) mSearchItem.getActionView().findViewById(R.id.search_box); if (!Utilities.isTablet(this)) { float textSize = 16.0f; // in [sp] = scaled pixels mSearch.setTextSize(textSize); } mSearch.setFocusable(true); if (mSearch != null) { if (mSearchInteractions == false) mSearch.setHint(getString(R.string.search) + " " + mActionName); else mSearch.setHint(getString(R.string.search) + " " + getString(R.string.interactions_search)); } mSearchHitsCntView = (TextView) mSearchItem.getActionView().findViewById(R.id.hits_counter); mSearchHitsCntView.setVisibility(View.GONE); mSearch.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_DONE) { if (mCurrentView == mSuggestView) { // Hide keyboard hideSoftKeyboard(500); } else if (mCurrentView == mShowView) { // Searches forward mWebView.findNext(true); } return true; } return false; } }); mSearch.setOnFocusChangeListener(new View.OnFocusChangeListener() { public void onFocusChange(View v, boolean hasFocus) { if (hasFocus) { showSoftKeyboard(100); } } }); mSearch.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showSoftKeyboard(100); if (!mSQLiteDBInitialized) { runOnUiThread(new Runnable() { public void run() { showDownloadAlert(0); } }); } } }); // Action listener for search_box mSearch.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { // Do nothing } @Override public void onTextChanged(CharSequence cs, int start, int before, int count) { // Do nothing } @Override public void afterTextChanged(Editable s) { String text = mSearch.getText().toString(); if (text.length() > 0) { if (!mSQLiteDBInitialized) { runOnUiThread(new Runnable() { public void run() { showDownloadAlert(0); } }); return; } } if (!mRestoringState) { if (text.length() > 0) performSearch(text); } mDelete.setVisibility(s.length() > 0 ? View.VISIBLE : View.GONE); } }); mDelete = (Button) mSearchItem.getActionView().findViewById(R.id.delete); mDelete.setVisibility(mSearch.getText().length() > 0 ? View.VISIBLE : View.GONE); mDelete.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (mSearch.length() > 0) mSearch.getText().clear(); if (mCurrentView == mShowView) { mSearchHitsCntView.setVisibility(View.GONE); mWebView.clearMatches(); } else if (mCurrentView == mSuggestView) { showSoftKeyboard(300); } } }); return true; } /** * Gesture detector for expert-info webview * @param webView */ private void setupGestureDetector(WebView webView) { GestureDetector.SimpleOnGestureListener simpleOnGestureListener = new GestureDetector.SimpleOnGestureListener() { @Override public boolean onFling(MotionEvent event1, MotionEvent event2, float velocityX, float velocityY) { if (event1 == null || event2 == null) return false; if (event1.getPointerCount() > 1 || event2.getPointerCount() > 1) return false; else { try { // right to left swipe... return to mSuggestView // float diffX = event1.getX()-event2.getX(); // left to right swipe... return to mSuggestView float diffX = event2.getX() - event1.getX(); if (diffX > 120 && Math.abs(velocityX) > 300) { setSuggestView(); return true; } } catch (Exception e) { // Handle exceptions... } return false; } } }; final GestureDetector detector = new GestureDetector(this, simpleOnGestureListener); webView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (detector.onTouchEvent(event)) return true; hideSoftKeyboard(50); return false; } }); } private void setupBottomNavigationViewListener() { mBottomNavigationView .setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { switch (item.getItemId()) { case (R.id.bottom_nav_aips): { if (mSearchInteractions == true || mDatabaseUsed.equals("favorites")) { mSearchInteractions = false; resetView(false); // Show empty list showResults(null); showSoftKeyboard(300); } else { // We are already in AIPS mode if (mSearch.length() > 0) mSearch.getText().clear(); performSearch(""); } return true; } case (R.id.bottom_nav_favorites): { // Switch to favorites database mDatabaseUsed = "favorites"; mSearchInteractions = false; // Change view setCurrentView(mSuggestView, true); performSearch(""); // Reset search if (mSearch.length() > 0) mSearch.getText().clear(); return true; } case (R.id.bottom_nav_interactions): { // Switch to AIPS database mDatabaseUsed = "aips"; mSearchInteractions = true; // Update interaction basket updateInteractionBasket(); // Update webview String html_str = mMedInteractionBasket.getInteractionsHtml(); mWebView.loadDataWithBaseURL("file:///android_res/drawable/", html_str, "text/html", "utf-8", null); // Change view setCurrentView(mShowView, true); // Reset and change search hint if (mSearch != null) { if (mSearch.length() > 0) mSearch.getText().clear(); mSearch.setHint( getString(R.string.search) + " " + getString(R.string.interactions_search)); } return true; } } return true; } }); } /** * Asynchronous thread launched to initialize the SQLite database * @author Max */ private class AsyncLoadDBTask extends AsyncTask<Void, Integer, Void> { boolean dismissSplashAuto = false; public AsyncLoadDBTask(Context context) { // Do nothing } // Setup the task, invoked before task is executed @Override protected void onPreExecute() { // Initialize folders for databases mMediDataSource = new DBAdapter(MainActivity.this); mMedInteractionBasket = new Interactions(MainActivity.this); // Dismiss splashscreen once database is initialized dismissSplashAuto = mMediDataSource.checkDatabasesExist(); showSplashScreen(true, dismissSplashAuto); } // Used to perform background computation, invoked on the background UI thread @Override protected Void doInBackground(Void... voids) { try { // Creates all databases mMediDataSource.create(); // Opens SQLite database mSQLiteDBInitialized = mMediDataSource.openSQLiteDB(); } catch (IOException e) { Log.d(TAG, "AsyncLoadDBTask: Unable to create database folders!"); throw new Error("AsyncLoadDBTask: Unable to create database folders"); } // Open drug interactions csv file mMedInteractionBasket.loadCsv(); return null; } // Used to clean up, invoked on UI thread after background computation ends @Override protected void onPostExecute(Void result) { if (dismissSplashAuto == false) dismissSplashScreen(); } } /** * Asynchronous thread launched to initialize the SQLite database * @author Max * */ private class AsyncUpdateDBTask extends AsyncTask<Void, Integer, Void> { private ProgressDialog progressBar; // Progressbar private int fileType = -1; public AsyncUpdateDBTask(Context context) { // Do nothing } // Setup the task, invoked before task is executed @Override protected void onPreExecute() { if (Constants.DEBUG) Log.d(TAG, "onPreExecute(): progressDialog"); // Initialize the dialog progressBar = new ProgressDialog(MainActivity.this); progressBar.setMessage("Initializing SQLite database..."); // progressBar.setIndeterminate(true); progressBar.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); progressBar.setProgress(0); progressBar.setMax(100); progressBar.setCancelable(false); progressBar.show(); } // Used to perform background computation, invoked on the background UI thread @Override protected Void doInBackground(Void... voids) { if (Constants.DEBUG) Log.d(TAG, "doInBackground: open/overwrite database"); // Creates, opens or overwrites database (singleton) if (mMediDataSource == null) { mMediDataSource = new DBAdapter(MainActivity.this); } else { // Case 2: mMediDataSource is initialized // Database adapter already exists, reuse it! try { // First, move report file to appropriate folder mMediDataSource.copyReportFile(); } catch (IOException e) { Log.d(TAG, "Error copying report file!"); } } try { // Attach observer to totally unzipped bytes (for the progress bar) mMediDataSource.addObserver(new Observer() { @Override @SuppressWarnings({ "unchecked" }) public void update(Observable o, Object arg) { // Method will call onProgressUpdate(Progress...) fileType = ((List<Integer>) arg).get(1); publishProgress(((List<Integer>) arg).get(0)); } }); // Creates database, interactions, and report file // or overwrites them if they already exists mMediDataSource.create(); } catch (IOException e) { Log.d(TAG, "Unable to create database!"); throw new Error("Unable to create database"); } // Opens SQLite database mSQLiteDBInitialized = mMediDataSource.openSQLiteDB(); // Open drug interactions csv file mMedInteractionBasket.loadCsv(); return null; } // Used to display any form of progress, invoked on UI thread after call of "publishProgress(Progress...)" @Override protected void onProgressUpdate(Integer... progress) { super.onProgressUpdate(progress); if (progress != null) { if (progress[0] < 1) { if (fileType == 1) progressBar.setMessage("Initializing SQLite database..."); else if (fileType == 2) progressBar.setMessage("Initializing drug interactions..."); } int percentCompleted = progress[0]; progressBar.setProgress(percentCompleted); } } // Used to clean up, invoked on UI thread after background computation ends @Override protected void onPostExecute(Void result) { if (Constants.DEBUG) Log.d(TAG, "mMediDataSource open!"); if (progressBar.isShowing()) progressBar.dismiss(); // Reset view resetView(true); // Friendly message if (mSQLiteDBInitialized) mToastObject.show("Databases initialized successfully", Toast.LENGTH_LONG); } } /** * Asynchronous thread launched to search in the SQLite database * @author Max * */ private class AsyncSearchTask extends AsyncTask<String, Void, Void> { private ProgressDialog progressBar; private List<Medication> medis = null; @Override protected void onCancelled() { super.onCancelled(); mAsyncSearchTask.cancel(true); mSearchInProgress = false; } // Setup the task, invoked before task is executed @Override protected void onPreExecute() { // Initialize progressbar progressBar = new ProgressDialog(MainActivity.this); progressBar.setProgressStyle(ProgressDialog.STYLE_SPINNER); progressBar.setMessage("Daten werden geladen..."); progressBar.setIndeterminate(true); if (mSearch != null && mSearch.getText().length() < 1) progressBar.show(); } // Used to perform background computation, invoked on the background UI thread @Override protected Void doInBackground(String... search_key) { // Do the expensive work in the background here try { // Thread.sleep(1000L); if (!isCancelled() && !mSearchInProgress) { if (search_key[0].length() >= mMinCharSearch) { mSearchInProgress = true; medis = mMedis = getResults(search_key[0]); } } } catch (Exception e) { if (progressBar.isShowing()) progressBar.dismiss(); } return null; } // Used to display any form of progress, invoked on UI thread after call of "publishProgress(Progress...)" @Override protected void onProgressUpdate(Void... progress_info) { // } // Used to clean up, invoked on UI thread after background computation ends @Override protected void onPostExecute(Void r) { if (medis != null) { showResults(medis); } if (progressBar.isShowing()) progressBar.dismiss(); mSearchInProgress = false; } } @Override protected void onSaveInstanceState(Bundle savedInstanceState) { super.onSaveInstanceState(savedInstanceState); // Save UI state changes to the savedInstanceState. // This bundle will be passed to onCreate if the process is killed and restarted. savedInstanceState.putInt("mode", getSupportActionBar().getNavigationMode()); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (mWebView != null) { // Checks the orientation of the screen if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { mWebView.getSettings().setTextZoom(125); } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { mWebView.getSettings().setTextZoom(175); } } } @TargetApi(16) void findAll(String key, WebView webView) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { webView.findAllAsync(key); try { Method m = WebView.class.getMethod("setFindIsUp", Boolean.TYPE); m.setAccessible(true); m.invoke(webView, true); } catch (Exception ignored) { // Exception is ignored } } } void performSearch(String search_key) { if (!search_key.isEmpty()) { if (mCurrentView == mSuggestView) { long t0 = System.currentTimeMillis(); // For this case, we don't need to discriminate between aips and favorites here // The distinction is made in the function getResults mAsyncSearchTask = new AsyncSearchTask(); mAsyncSearchTask.execute(search_key); mSearchQuery = search_key; if (Constants.DEBUG) Log.d(TAG, "Time for performing search: " + Long.toString(System.currentTimeMillis() - t0) + "ms"); } else if (mCurrentView == mShowView) { if (mWebView != null) { // Native solution if (search_key.length() > 2) { findAll(search_key, mWebView); } else { mSearchHitsCntView.setVisibility(View.GONE); mWebView.clearMatches(); } } } } else { if (mCurrentView == mSuggestView) { long t0 = System.currentTimeMillis(); if (mDatabaseUsed.equals("aips")) { // If "complete search" is already in progress, don't do anything! if (mSearchInProgress == false) { mAsyncSearchTask = new AsyncSearchTask(); mAsyncSearchTask.execute(search_key); } } else if (mDatabaseUsed.equals("favorites")) { // Save time stamp mTimer = System.currentTimeMillis(); // Clear the search container List<Medication> medis = new ArrayList<Medication>(); for (String regnr : mFavoriteMedsSet) { List<Medication> meds = mMediDataSource .searchRegnr((regnr != null ? regnr.toString() : "@@@@")); if (!meds.isEmpty()) medis.add(meds.get(0)); } // Sort list of meds Collections.sort(medis, new Comparator<Medication>() { @Override public int compare(final Medication m1, final Medication m2) { return m1.getTitle().compareTo(m2.getTitle()); } }); if (medis != null) { mMedis = medis; showResults(medis); } } if (Constants.DEBUG) Log.d(TAG, "Time performing search (empty search_key): " + Long.toString(System.currentTimeMillis() - t0) + "ms"); } } } private List<Medication> getResults(String query) { List<Medication> medis = null; mTimer = System.currentTimeMillis(); if (mActionName.equals(getString(R.string.tab_name_1))) medis = mMediDataSource.searchTitle((query != null ? query.toString() : "@@@@")); else if (mActionName.equals(getString(R.string.tab_name_2))) medis = mMediDataSource.searchAuth((query != null ? query.toString() : "@@@@")); else if (mActionName.equals(getString(R.string.tab_name_3))) medis = mMediDataSource.searchATC((query != null ? query.toString() : "@@@@")); else if (mActionName.equals(getString(R.string.tab_name_4))) medis = mMediDataSource.searchRegnr((query != null ? query.toString() : "@@@@")); else if (mActionName.equals(getString(R.string.tab_name_5))) medis = mMediDataSource.searchApplication((query != null ? query.toString() : "@@@@")); // Filter according to favorites if (mDatabaseUsed.equals("favorites")) { // This list contains all filtered medis List<Medication> favMedis = new ArrayList<Medication>(); // Loop through all found medis for (Medication m : medis) { // If found medi is not in "favorites", remove it if (mFavoriteMedsSet.contains(m.getRegnrs())) favMedis.add(m); } // Filtered medis medis = favMedis; // Sort list of meds Collections.sort(medis, new Comparator<Medication>() { @Override public int compare(final Medication m1, final Medication m2) { return m1.getTitle().compareTo(m2.getTitle()); } }); } if (Constants.DEBUG) Log.d(TAG, "getResults() - " + medis.size() + " medis found in " + Long.toString(System.currentTimeMillis() - mTimer) + "ms"); return medis; } private void showResults(List<Medication> medis) { // if (medis!=null) { // Create simple cursor adapter CustomListAdapter<Medication> custom_adapter = new CustomListAdapter<Medication>(this, R.layout.medi_result, medis); // Set adapter to listview mListView.setAdapter(custom_adapter); // Give some feedback about the search to the user (could be done better!) /* if (!mRestoringState) { if (Constants.appLanguage().equals("de")) { mToastObject.show(medis.size() + " Suchresultate in " + (System.currentTimeMillis()-mTimer) + "ms", Toast.LENGTH_SHORT); } else if (Constants.appLanguage().equals("fr")) { mToastObject.show(medis.size() + " rsultats de la recherche en " + (System.currentTimeMillis()-mTimer) + "ms", Toast.LENGTH_SHORT); } } */ } protected boolean isAlwaysExpanded() { return false; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case (R.id.menu_search): { showSoftKeyboard(300); return true; } case (R.id.menu_pref2): { Intent intent = new Intent(this, ReportActivity.class); startActivity(intent); return true; } case (R.id.menu_pref3): { // Download new database (blocking call) if (!mUpdateInProgress) requestPermissionAndDownloadUpdates(); mToastObject.show(getString(R.string.menu_pref3), Toast.LENGTH_SHORT); return true; } case (R.id.menu_share): { // Remove softkeyboard hideSoftKeyboard(10); // Take screenshot and start email activity after 500ms (wait for the keyboard to disappear) final Handler handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { mToastObject.show(getString(R.string.menu_share), Toast.LENGTH_SHORT); sendFeedbackScreenshot(MainActivity.this, 1); } }, 500); return true; } case (R.id.menu_rate): { Intent intent = new Intent(Intent.ACTION_VIEW); // Try Google play String uri_str = "market://details?id=com.ywesee.amiko.de"; if (Constants.appLanguage().equals("fr")) uri_str = "market://details?id=com.ywesee.amiko.fr"; intent.setData(Uri.parse(uri_str)); if (myStartActivity(intent) == false) { // Market (Google play) app seems not installed, let's try to open a webbrowser uri_str = "https://play.google.com/store/apps/details?id=com.ywesee.amiko.de"; if (Constants.appLanguage().equals("fr")) uri_str = "https://play.google.com/store/apps/details?id=com.ywesee.amiko.fr&hl=fr"; intent.setData(Uri.parse(uri_str)); if (myStartActivity(intent) == false) { // Well if this also fails, we have run out of options, inform the user. mToastObject.show("Could not open Android market, please install the market app.", Toast.LENGTH_SHORT); } } return true; } case (R.id.menu_help): { mToastObject.show(getString(R.string.menu_help), Toast.LENGTH_SHORT); if (Constants.appOwner().equals("ywesee")) { Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.ywesee.com/AmiKo/Index")); startActivity(browserIntent); } return true; } default: break; } return true; } /** * */ public void updateInteractionBasket() { // Add medi to drug interaction basket if (mMedIndex > 0) { Medication m = mMediDataSource.searchId(mMedIndex); mMedInteractionBasket.addToBasket(m.getTitle(), m); } // Update it mMedInteractionBasket.updateInteractionsHtml(); // Get all section titles mMedInteractionBasket.getInteractionsTitles(); // Add section title view List<String> section_ids = mMedInteractionBasket.getInteractionTitleIds(); List<String> section_titles = mMedInteractionBasket.getInteractionsTitles(); // Get reference to listview in DrawerLayout // Implements swiping mechanism! mSectionView = (ListView) findViewById(R.id.section_title_view); // Make it clickable mSectionView.setClickable(true); SectionTitlesAdapter sectionTitles = new SectionTitlesAdapter(this, R.layout.section_item, section_ids, section_titles); mSectionView.setAdapter(sectionTitles); } /** * Takes screenshot of the whole screen * @param activity */ public void sendFeedbackScreenshot(final Activity activity, int mode) { try { // Get root windows final View rootView = activity.getWindow().getDecorView().findViewById(android.R.id.content) .getRootView(); rootView.setDrawingCacheEnabled(true); Bitmap bitmap = rootView.getDrawingCache(); // The file be saved to the download folder File outputFile = new File( Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/amiko_screenshot.png"); FileOutputStream fOutStream = new FileOutputStream(outputFile); // Picture is then compressed bitmap.compress(Bitmap.CompressFormat.PNG, 100, fOutStream); rootView.setDrawingCacheEnabled(false); fOutStream.close(); // Start email activity if (mode == 1) startEmailActivity(activity, Uri.fromFile(outputFile), "", "AmiKo for Android"); else if (mode == 2) startEmailActivity(activity, Uri.fromFile(outputFile), "zdavatz@ywesee.com", "Interaction notification"); } catch (Exception e) { e.printStackTrace(); } } /** * Starts email activity * @param context * @param attachment */ public void startEmailActivity(Context context, Uri attachment, String emailAddr, String subject) { Intent sendEmailIntent = new Intent(Intent.ACTION_SEND); sendEmailIntent.setType("message/rfc822"); sendEmailIntent.putExtra(Intent.EXTRA_EMAIL, new String[] { emailAddr }); sendEmailIntent.putExtra(Intent.EXTRA_SUBJECT, subject); sendEmailIntent.putExtra(Intent.EXTRA_TEXT, "AmiKo for Android\r\n\nGet it now: https://play.google.com/store/apps/details?id=com.ywesee.amiko.de\r\n\nEnjoy!"); sendEmailIntent.putExtra(Intent.EXTRA_STREAM, attachment); context.startActivity(Intent.createChooser(sendEmailIntent, "Send email")); } private boolean myStartActivity(Intent aIntent) { try { startActivity(aIntent); return true; } catch (ActivityNotFoundException e) { return false; } } /** * Converts given picture to a bitmap * @param picture * @return */ private static Bitmap pictureDrawable2Bitmap(Picture picture) { PictureDrawable pictureDrawable = new PictureDrawable(picture); Bitmap bitmap = Bitmap.createBitmap(pictureDrawable.getIntrinsicWidth(), pictureDrawable.getIntrinsicHeight(), Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); canvas.drawPicture(pictureDrawable.getPicture()); return bitmap; } /** * Downloads and updates the SQLite database and the error report file */ public void downloadUpdates() { // Signal that update is in progress mUpdateInProgress = true; mDownloadedFileCount = 0; // First check network connection ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); // Fetch data... if (networkInfo != null && networkInfo.isConnected()) { // File URIs Uri databaseUri = Uri.parse("http://pillbox.oddb.org/" + Constants.appZippedDatabase()); Uri reportUri = Uri.parse("http://pillbox.oddb.org/" + Constants.appReportFile()); Uri interactionsUri = Uri.parse("http://pillbox.oddb.org/" + Constants.appZippedInteractionsFile()); // NOTE: the default download destination is a shared volume where the system might delete your file if // it needs to reclaim space for system use DownloadManager.Request requestDatabase = new DownloadManager.Request(databaseUri); DownloadManager.Request requestReport = new DownloadManager.Request(reportUri); DownloadManager.Request requestInteractions = new DownloadManager.Request(interactionsUri); // Allow download only over WIFI and Mobile network requestDatabase.setAllowedNetworkTypes(Request.NETWORK_WIFI | Request.NETWORK_MOBILE); requestReport.setAllowedNetworkTypes(Request.NETWORK_WIFI | Request.NETWORK_MOBILE); requestInteractions.setAllowedNetworkTypes(Request.NETWORK_WIFI | Request.NETWORK_MOBILE); // Download visible and shows in notifications while in progress and after completion requestDatabase.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); requestReport.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); requestInteractions .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); // Set the title of this download, to be displayed in notifications (if enabled). requestDatabase.setTitle("AmiKo SQLite database update"); requestReport.setTitle("AmiKo report update"); requestInteractions.setTitle("AmiKo drug interactions update"); // Set a description of this download, to be displayed in notifications (if enabled) requestDatabase.setDescription("Updating the AmiKo database."); requestReport.setDescription("Updating the AmiKo error report."); requestInteractions.setDescription("Updating the AmiKo drug interactions."); // Set local destination to standard directory (place where files downloaded by the user are placed) /* String main_expansion_file_path = Utilities.expansionFileDir(getPackageName()); requestDatabase.setDestinationInExternalPublicDir(main_expansion_file_path, Constants.appZippedDatabase()); */ requestDatabase.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, Constants.appZippedDatabase()); requestReport.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, Constants.appReportFile()); requestInteractions.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, Constants.appZippedInteractionsFile()); // Check if file exist on non persistent store. If yes, delete it. if (Utilities.isExternalStorageReadable() && Utilities.isExternalStorageWritable()) { Utilities.deleteFile(Constants.appZippedDatabase(), Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)); Utilities.deleteFile(Constants.appReportFile(), Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)); Utilities.deleteFile(Constants.appZippedInteractionsFile(), Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)); } // The downloadId is unique across the system. It is used to make future calls related to this download. mDatabaseId = mDownloadManager.enqueue(requestDatabase); mReportId = mDownloadManager.enqueue(requestReport); mInteractionsId = mDownloadManager.enqueue(requestInteractions); mProgressBar = new ProgressDialog(MainActivity.this); mProgressBar.setMessage("Downloading SQLite database..."); mProgressBar.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); mProgressBar.setProgress(0); mProgressBar.setMax(100); mProgressBar.setCancelable(false); mProgressBar.show(); new Thread(new Runnable() { @Override public void run() { boolean downloading = true; while (downloading) { DownloadManager.Query q = new DownloadManager.Query(); q.setFilterById(mDatabaseId); Cursor cursor = mDownloadManager.query(q); cursor.moveToFirst(); int bytes_downloaded = cursor .getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)); int bytes_total = cursor .getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)); if (cursor.getInt(cursor.getColumnIndex( DownloadManager.COLUMN_STATUS)) == DownloadManager.STATUS_SUCCESSFUL) { downloading = false; if (mProgressBar.isShowing()) mProgressBar.dismiss(); } final int dl_progress = (int) ((bytes_downloaded * 100l) / bytes_total); runOnUiThread(new Runnable() { @Override public void run() { mProgressBar.setProgress((int) dl_progress); } }); cursor.close(); } } }).start(); } else { // Display error report } } /** * Implements view holder design pattern * @author Max * */ private static class ViewHolder { public ImageView owner_logo; public TextView text_title; public TextView text_subtitle; } /** * Displays a customized list of items * @author Max * * @param <T> */ private class CustomListAdapter<T> extends ArrayAdapter<T> { // TODO: add starring mechanisms plus list of favorites private Context mContext; private int id; private List<T> items; String title_str = "k.A."; // tab_name_1 = Prparat String auth_str = "k.A."; // tab_name_2 = Inhaber String atc_code_str = "k.A."; // tab_name_3 = Wirkstoff / ATC Code String atc_title_str = "k.A."; String atc_class_str = "k.A."; // = ATC Klasse String regnr_str = "k.A."; // tab_name_4 = Reg.Nr. String application_str = "k.A."; // tab_name_5 = Therapie / Indications String pack_info_str = ""; public CustomListAdapter(Context context, int textViewResourceId, List<T> list) { super(context, textViewResourceId, list); mContext = context; id = textViewResourceId; items = list; } @Override public int getCount() { return items != null ? items.size() : 0; } /** * Every time ListView needs to show a new row on screen, it will call getView(). * Its goal is to return single list row. The row is recreated each time. * There is a performance issue. Must be optimized. */ @Override public View getView(int position, View convertView, ViewGroup parent) { // viewHolder is instantiated only once! ViewHolder viewHolder; // convertView is a "ScrapView" (non-visible view used for going on-screen) View mView = convertView; // Trick 1: if convertView is null, inflate it, otherwise only update its content! if (mView == null) { // Inflations and findViewById are expensive operations... LayoutInflater vi = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mView = vi.inflate(id, null); // viewHolder is a static variable and is instantiated only here viewHolder = new ViewHolder(); viewHolder.owner_logo = (ImageView) mView.findViewById(R.id.mlogo); viewHolder.owner_logo.setImageResource(R.drawable.logo_desitin); viewHolder.text_title = (TextView) mView.findViewById(R.id.mtitle); viewHolder.text_subtitle = (TextView) mView.findViewById(R.id.mauth); // Set text sizes if (!Utilities.isTablet(MainActivity.this)) { viewHolder.text_title.setTextSize(14); viewHolder.text_subtitle.setTextSize(12); } // Store view mView.setTag(viewHolder); } else { // Recycle existing view. // We've just avoided calling findViewById() on resource, just call viewHolder viewHolder = (ViewHolder) mView.getTag(); } final Medication med = (Medication) items.get(position); // Get reference to customer logo // ImageView image_logo = (ImageView) mView.findViewById(R.id.mlogo); // viewHolder.image_logo.setImageResource(R.drawable.logo_desitin); if (Constants.appLanguage().equals("de")) { title_str = "k.A."; auth_str = "k.A."; regnr_str = "k.A."; atc_code_str = "k.A."; atc_title_str = "k.A."; atc_class_str = "k.A."; application_str = "k.A."; } else if (Constants.appLanguage().equals("fr")) { title_str = "n.s."; auth_str = "n.s."; regnr_str = "n.s."; atc_code_str = "n.s."; atc_title_str = "n.s."; atc_class_str = "n.s."; application_str = "n.s."; } pack_info_str = ""; if (mActionName.equals(getString(R.string.tab_name_1))) { // Display "Prparatname" and "Therapie/Kurzbeschrieb" if (med != null) { if (med.getTitle() != null) title_str = med.getTitle(); if (med.getPackInfo() != null) pack_info_str = med.getPackInfo(); viewHolder.text_title.setText(title_str); viewHolder.text_title.setTextColor(Color.argb(255, 10, 10, 10)); // text_auth.setText(pack_info_str); // --> Original solution // text_auth.setText(Html.fromHtml(pack_info_str)); // --> Solution with fromHtml (slow) viewHolder.text_subtitle.setTextColor(Color.argb(255, 128, 128, 128)); Pattern p_red = Pattern.compile(".*O]"); Pattern p_green = Pattern.compile(".*G]"); Matcher m_red = p_red.matcher(pack_info_str); Matcher m_green = p_green.matcher(pack_info_str); SpannableStringBuilder spannable = new SpannableStringBuilder(pack_info_str); if (spannable != null) { while (m_red.find()) { spannable.setSpan(new ForegroundColorSpan(Color.rgb(205, 0, 0)), m_red.start(), m_red.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } while (m_green.find()) { spannable.setSpan(new ForegroundColorSpan(Color.rgb(50, 188, 50)), m_green.start(), m_green.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } viewHolder.text_subtitle.setText(spannable, BufferType.SPANNABLE); } if (med.getCustomerId() == 1) { viewHolder.owner_logo.setVisibility(View.VISIBLE); } else { viewHolder.owner_logo.setVisibility(View.GONE); } } } else if (mActionName.equals(getString(R.string.tab_name_2))) { // Display "Prparatname" and "Inhaber" if (med != null) { if (med.getTitle() != null) title_str = med.getTitle(); if (med.getAuth() != null) auth_str = med.getAuth(); viewHolder.text_title.setText(title_str); viewHolder.text_subtitle.setText(auth_str); viewHolder.text_title.setTextColor(Color.argb(255, 10, 10, 10)); viewHolder.text_subtitle.setTextColor(Color.argb(255, 128, 128, 128)); if (med.getCustomerId() == 1) { viewHolder.owner_logo.setVisibility(View.VISIBLE); } else { viewHolder.owner_logo.setVisibility(View.GONE); } } } else if (mActionName.equals(getString(R.string.tab_name_3))) { // Display "Wirkstoff" and "ATC Code" (atccode) if (med != null) { if (med.getTitle() != null) title_str = med.getTitle(); List<String> m_atc = Arrays.asList(med.getAtcCode().split("\\s*;\\s*")); if (m_atc.size() > 1) { if (m_atc.get(0) != null) atc_code_str = m_atc.get(0); if (m_atc.get(1) != null) atc_title_str = m_atc.get(1); } String[] m_class = med.getAtcClass().split(";"); if (m_class.length == 2) { // *** Ver.<1.2 atc_class_str = m_class[1]; viewHolder.text_title.setText(title_str); viewHolder.text_subtitle .setText(atc_code_str + " - " + atc_title_str + "\n" + atc_class_str); viewHolder.text_title.setTextColor(Color.argb(255, 10, 10, 10)); viewHolder.text_subtitle.setTextColor(Color.argb(255, 128, 128, 128)); } else if (m_class.length == 3) { // *** Ver.>=1.2 String[] atc_class_l4_and_l5 = m_class[2].split("#"); int n = atc_class_l4_and_l5.length; if (n > 0) atc_class_str = atc_class_l4_and_l5[n - 1]; viewHolder.text_title.setText(title_str); viewHolder.text_subtitle.setText( atc_code_str + " - " + atc_title_str + "\n" + atc_class_str + "\n" + m_class[1]); viewHolder.text_title.setTextColor(Color.argb(255, 10, 10, 10)); viewHolder.text_subtitle.setTextColor(Color.argb(255, 128, 128, 128)); } if (med.getCustomerId() == 1) { viewHolder.owner_logo.setVisibility(View.VISIBLE); } else { viewHolder.owner_logo.setVisibility(View.GONE); } } } else if (mActionName.equals(getString(R.string.tab_name_4))) { // Display name, registration number (swissmedicno5) and author if (med != null) { if (med.getTitle() != null) title_str = med.getTitle(); if (med.getRegnrs() != null) regnr_str = med.getRegnrs(); if (med.getAuth() != null) auth_str = med.getAuth(); viewHolder.text_title.setText(title_str); viewHolder.text_subtitle.setText(regnr_str + " - " + auth_str); viewHolder.text_title.setTextColor(Color.argb(255, 10, 10, 10)); viewHolder.text_subtitle.setTextColor(Color.argb(255, 128, 128, 128)); if (med.getCustomerId() == 1) { viewHolder.owner_logo.setVisibility(View.VISIBLE); } else { viewHolder.owner_logo.setVisibility(View.GONE); } } } else if (mActionName.equals(getString(R.string.tab_name_5))) { // Display name and "Therapy/Kurzbeschrieb" if (med != null) { if (med.getTitle() != null) title_str = med.getTitle(); if (med.getApplication() != null) { // 29/09/2013: fix for Heparin bug application_str = med.getApplication().replaceAll(";", "\n"); } viewHolder.text_title.setText(title_str); viewHolder.text_subtitle.setText(application_str); viewHolder.text_title.setTextColor(Color.argb(255, 10, 10, 10)); viewHolder.text_subtitle.setTextColor(Color.argb(255, 128, 128, 128)); if (med.getCustomerId() == 1) { viewHolder.owner_logo.setVisibility(View.VISIBLE); } else { viewHolder.owner_logo.setVisibility(View.GONE); } } } // Get reference to favorite's star final ImageView favorite_star = (ImageView) mView.findViewById(R.id.mfavorite); // Retrieve information from hash set if (mFavoriteMedsSet.contains(med.getRegnrs())) { favorite_star.setImageResource(R.drawable.star_small_ye); favorite_star.setVisibility(View.VISIBLE); } else { favorite_star.setImageResource(R.drawable.star_small_gy); favorite_star.setVisibility(View.VISIBLE); } // Make star clickable favorite_star.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { String regnrs = med.getRegnrs(); // Update star if (mFavoriteMedsSet.contains(regnrs)) mFavoriteMedsSet.remove(regnrs); else mFavoriteMedsSet.add(regnrs); mFavoriteData.save(mFavoriteMedsSet); // Refreshes the listview notifyDataSetChanged(); } }); // ClickListener mView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // Change content view if (mSuggestView != null) { setCurrentView(mShowView, true); } // Adapt the zoom settings depending on the device's orientation int orientation = getResources().getConfiguration().orientation; if (orientation == Configuration.ORIENTATION_PORTRAIT) { mWebView.getSettings().setTextZoom(175); } else if (orientation == Configuration.ORIENTATION_LANDSCAPE) { mWebView.getSettings().setTextZoom(125); } mMedIndex = med.getId(); if (Constants.DEBUG) Log.d(TAG, "medi id = " + mMedIndex); Medication m = mMediDataSource.searchId(mMedIndex); if (m != null && mSearchInteractions == false) { // Reset and change search hint if (mSearch != null) { if (mSearch.length() > 0) mSearch.getText().clear(); mSearch.setHint( getString(R.string.search) + " " + getString(R.string.full_text_search)); } // mHtmlString = createHtml(m.getStyle(), m.getContent()); mHtmlString = createHtml(mCSS_str, m.getContent()); if (mWebView != null) { // Checks the orientation of the screen Configuration mConfig = mContext.getResources().getConfiguration(); if (mConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { mWebView.getSettings().setTextZoom(125); } else if (mConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { mWebView.getSettings().setTextZoom(175); } } mWebView.loadDataWithBaseURL("file:///android_res/drawable/", mHtmlString, "text/html", "utf-8", null); // Add NavigationDrawer, get handle to DrawerLayout mDrawerLayout = (DrawerLayout) findViewById(R.id.show_view_container); // Set a custom shadow that overlays the main content when the drawer opens mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, Gravity.START); // Close any open drawers if (mDrawerLayout != null) mDrawerLayout.closeDrawers(); // Add section title view String[] id_items = m.getSectionIds().split(","); List<String> section_ids = Arrays.asList(id_items); String[] title_items = m.getSectionTitles().split(";"); List<String> section_titles = Arrays.asList(title_items); // Get reference to listview in DrawerLayout // Implements swiping mechanism! mSectionView = (ListView) findViewById(R.id.section_title_view); // Make it clickable mSectionView.setClickable(true); SectionTitlesAdapter sectionTitles = new SectionTitlesAdapter(mContext, R.layout.section_item, section_ids, section_titles); mSectionView.setAdapter(sectionTitles); } else { // Update interaction basket updateInteractionBasket(); // Reset and change search hint if (mSearch != null) { if (mSearch.length() > 0) mSearch.getText().clear(); mSearch.setHint( getString(R.string.search) + " " + getString(R.string.interactions_search)); } // Update webview String html_str = mMedInteractionBasket.getInteractionsHtml(); mWebView.loadDataWithBaseURL("file:///android_res/drawable/", html_str, "text/html", "utf-8", null); // Change view setCurrentView(mShowView, true); } } }); // Long click listener mView.setOnLongClickListener(new OnLongClickListener() { @Override public boolean onLongClick(View v) { if (mActionName.equals(getString(R.string.tab_name_3))) { Medication m = mMediDataSource.searchId(med.getId()); String[] atc_items = m.getAtcCode().split(";"); if (atc_items != null) { mSearch.setText(atc_items[0]); Editable s = mSearch.getText(); // Set cursor at the end Selection.setSelection(s, s.length()); } } return true; } }); return mView; } } private String createHtml(String style_str, String content_str) { // Old Javascript-based solution... superseeded -> maybe useful for older version of android! /* String js_str = loadFromAssetsFolder("jshighlight.js", "UTF-8"); // loadJS("jshighlight.js"); String html_str = "<html><head>" + "<script type=\"text/javascript\">" + js_str + "</script>" + "<style type=\"text/css\">" + style_str + "</style>" + "</head><body>" + content_str + "</body></html>"; */ String html_str = "<html><head>" + "<style type=\"text/css\">" + style_str + "</style>" + "</head><body>" + content_str + "</body></html>"; return html_str; } /** * This class implements the adapter for the section titles * @author Max * */ private class SectionTitlesAdapter extends ArrayAdapter<String> { private Context mContext; private int id; private List<String> title_items; private List<String> id_items; public SectionTitlesAdapter(Context context, int textViewResourceId, List<String> s_ids, List<String> s_titles) { super(context, textViewResourceId, s_titles); mContext = context; id = textViewResourceId; id_items = s_ids; title_items = s_titles; } @Override public View getView(int position, View convertView, ViewGroup parent) { View mView = convertView; if (mView == null) { LayoutInflater vi = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mView = vi.inflate(id, null); } TextView text_title = (TextView) mView.findViewById(R.id.absTitle); // R.id.textView if (!Utilities.isTablet(MainActivity.this)) { text_title.setTextSize(12); // in "scaled pixel units" (sp) } else { // Defined in "section_item.xml" as 14sp } String title = (String) title_items.get(position); final String id = (String) id_items.get(position); if (title != null) { // Use German abbreviation if possible if (Constants.appLanguage().equals("de")) { Locale german = Locale.GERMAN; for (String s : SectionTitle_DE) { if (title.toLowerCase(german).contains(s.toLowerCase(german))) { title = s; break; } } } else if (Constants.appLanguage().equals("fr")) { // Use French abbreviation if possible Locale french = Locale.FRENCH; for (String s : SectionTitle_FR) { if (title.toLowerCase(french).contains(s.toLowerCase(french))) { title = s; break; } } } text_title.setText(title); // See section_item.xml for settings!! // text_title.setTextColor(Color.argb(255,240,240,240)); } mView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { String scrollingJS = "document.getElementById('" + id + "').scrollIntoView(true);"; mWebView.evaluateJavascript(scrollingJS, null); // Close section title view if (mDrawerLayout != null) mDrawerLayout.closeDrawers(); } }); return mView; } } /** * Web view has record of all pages visited so you can go back and forth just override * back button to go back in history if there is page available for display */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (mWebView != null) { if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()) { mWebView.goBack(); return true; } } return super.onKeyDown(keyCode, event); } }