android - How can I sent multiple data types with Intent.ACTION_SEND_MULTIPLE when using a shareIntent?

android - How can I sent multiple data types with Intent.ACTION_SEND_MULTIPLE when using a shareIntent?

Elise Pankaj Author: Elise Pankaj Date: 2022-10-05
android - How can I sent multiple data types with Intent.ACTION_SEND_MULTIPLE when using a shareIntent?

All you need to know about android - How can I sent multiple data types with Intent.ACTION_SEND_MULTIPLE when using a shareIntent? , in addintion to How can I pass multiple arguments via xml for a custom setter when using Android data binding , rx java - Using clean MVP on android with RxJava: How can you keep the presenter free of any android knowledge while still observing on the UI thread? , android - How can I dim the background when Bottomsheet is displayed, without using Dialog? , database - How can I avoid concurrency problems when using SQLite on Android?

  1. android - How can I sent multiple data types with Intent.ACTION_SEND_MULTIPLE when using a shareIntent?
  2. Question:

    Its pretty clear in the documentation that you can send multiple pieces of data with:

    Intent shareIntent = new Intent();
    shareIntent.setAction(Intent.ACTION_SEND_MULTIPLE);
    shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, imageUris);
    shareIntent.setType("image/*");
    startActivity(Intent.createChooser(shareIntent, "Share images to.."));
    

    but it seems, from that one line: shareIntent.setType("image/*"); that all pieces have to be the same data type. What If I wanted to send a picture(image/jpeg) and a hashtag that should go along with in the caption (text/plain)?

    How would I handle multiple kinds of content in one shareIntent? Is it possible to send 2 shareIntents to the same activity? How would I handle this?


    Solution 1:

    If your goal is to share one picture with text, this is the code I would suggest:

    String text = "Look at my awesome picture";
    Uri pictureUri = Uri.parse("file://my_picture");
    Intent shareIntent = new Intent();
    shareIntent.setAction(Intent.ACTION_SEND);
    shareIntent.putExtra(Intent.EXTRA_TEXT, text);
    shareIntent.putExtra(Intent.EXTRA_STREAM, pictureUri);
    shareIntent.setType("image/*");
    shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    startActivity(Intent.createChooser(shareIntent, "Share images..."));
    

    Solution 2:

    It's not exactly clear from the question whether you want to send multiple images or just a single image, but with an associated text.

    In the first case (multiple images):

    Use ACTION_SEND_MULTIPLE and provide the list of uris as EXTRA_STREAM, as in:

    Intent shareIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
    shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, imageUris);
    shareIntent.setType("image/*");
    

    If it's the second case (image plus text):

    Use just ACTION_SEND and provide both EXTRA_STREAM and EXTRA_TEXT, for example:

    Intent shareIntent = new Intent(Intent.ACTION_SEND);
    shareIntent.putExtra(Intent.EXTRA_TEXT, text);
    shareIntent.putExtra(Intent.EXTRA_STREAM, imageUri);
    shareIntent.setType("image/*");
    

    If, however, you need to share streams of varying MIME types (such as both pictures and other attachments) just use a more generic MIME type, such as */*. For example:

    shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
    shareIntent.setType("*/*");
    

    From the documentation of ACTION_SEND_MULTIPLE (emphasis mine):

    Multiple types are supported, and receivers should handle mixed types whenever possible. The right way for the receiver to check them is to use the content resolver on each URI. The intent sender should try to put the most concrete mime type in the intent type, but it can fall back to <type>/* or */* as needed.

    e.g. if you are sending image/jpg and image/jpg, the intent's type can be image/jpg, but if you are sending image/jpg and image/png, then the intent's type should be image/*.

    This works when mixing, say, images and downloaded files.

  3. How can I pass multiple arguments via xml for a custom setter when using Android data binding
  4. Question:

    Suppose I have a custom setter method where I need two arguments:

    @BindingAdapter({"imageUrl", "placeholder"})
    public static void loadImage(ImageView imageView, String imageUrl, Drawable drawable) {
         Picasso.with(imageView.getContext()).load(imageUrl).placeholder(drawable).into(imageView);
    }
    

    What would the corresponding xml look like?


    Solution 1:

    Looks like it can be done like this:

    <ImageView app:imageUrl=“@{data.imageUrl}” app:placeholder=“@{@drawable/placeholder}”/>

    see http://developer.android.com/intl/es/tools/data-binding/guide.html

  5. rx java - Using clean MVP on android with RxJava: How can you keep the presenter free of any android knowledge while still observing on the UI thread?
  6. Question:

    In an effort to implement 'clean' architecture on android with the mvp pattern it is advised to treat the android framework as a plugin and not leak any android aware dependencies into the presenter layer. Using rxjava, if I have a presenter that is designed to 'push' data to the view layer I may want to have logic like this:

    public interface SearchPresenter {
    
      interface ViewLayer {
      void updateResults(List<SearchResult> searchResults)
      }
      void bind(ViewLayer viewLayer);
      void unbind();
    }
    
    public class SearchPresenterImpl implements SearchPresenter {
    
       ViewLayer viewLayer;
       CompositeDisposable compositeDisposable;
    
        @Override
        public void bind(ViewLayer viewLayer) {
          this.viewLayer = viewLayer;
    
          compositeDisposable = new CompositeDisposable();
          compositeDisposable.add(
                searchInteractor.getSearchResults()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(this::refreshView));
    
    }
    
    private void refreshView(List<SearchResult> searchResults) {
        //send the results back to the view layer to update a RecyclerView      
        viewLayer.updateResults(searchResults)
    }
    
    @Override
    public void unbind() {
        compositeDisposable.dispose();
    }
    

    However, by observing on 'AndroidSchedulers.mainThread()' this forces a dependency on:

    import io.reactivex.android.schedulers.AndroidSchedulers
    

    at which point, my presenter now knows about android and is tied to it; which I want to avoid.

    What is the advised way to handle this so that the result is guaranteed to be delivered to the ViewLayer on android's UI (Main) thread while the presenter maintains no dependencies on anything android related?


    Solution 1:

    AndroidSchedulers.mainThread() uses Android code to schedule actions on the main thread, don't use it directly in your presenter. instead You can refactor the presenter to take Schedulers in its constructor, in production code use the regular AndroidSchedulers.mainThread() and Schedulers.io(), in testing you can just send Schedulers.trampoline() or Schedulers.immediate() .

    see this pattern in this example: https://github.com/riggaroo/GithubUsersSearchApp/blob/master/app/src/main/java/za/co/riggaroo/gus/presentation/search/UserSearchPresenter.java

    and its test class here: https://github.com/riggaroo/GithubUsersSearchApp/blob/8b83095d7a2cc8f3cb69a945224ab4c37cf54a37/app/src/test/java/za/co/riggaroo/gus/presentation/search/UserSearchPresenterTest.java

    Solution 2:

  7. android - How can I dim the background when Bottomsheet is displayed, without using Dialog?
  8. Question:

    I know BottomSheetDialog does so already, but I need to use the regular BottomSheet and behavior generated from BottomSheetBehavior.from(). This BottomSheet doesn't dim the background and also touch outside would not close it. Is there a way to dim the background when BottomSheet is displayed? and maybe dismiss when touch outside. Basically the behavior just like BottomSheetDialog but I must use BottomSheet BottomSheetBehavior directly.


    Solution 1:

    You can use this code 1. MainActivity.xml

    <android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/main_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">
    
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            android:paddingTop="24dp">
    
            <Button
                android:id="@+id/button_1"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Button 1"
                android:padding="16dp"
                android:layout_margin="8dp"
                android:textColor="@android:color/white"
                android:background="@android:color/holo_green_dark"/>
    
        </LinearLayout>
    
    </ScrollView>
    
    <View
        android:visibility="gone"
        android:id="@+id/bg"
        android:background="#99000000"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    
    <android.support.v4.widget.NestedScrollView
        android:id="@+id/bottom_sheet"
        android:layout_width="match_parent"
        android:layout_height="350dp"
        android:clipToPadding="true"
        android:background="@android:color/holo_orange_light"
        app:layout_behavior="android.support.design.widget.BottomSheetBehavior"
        >
    
        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="aefwea"
            android:padding="16dp"
            android:textSize="16sp"/>
    
    </android.support.v4.widget.NestedScrollView>
    
     </android.support.design.widget.CoordinatorLayout>
    
    1. MAinActivity.java

      public class MainActivity extends AppCompatActivity implements View.OnClickListener {
      
      private static final String TAG = "MainActivity";
      private BottomSheetBehavior mBottomSheetBehavior;
      View bottomSheet;
      View mViewBg;
      
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
          bottomSheet = findViewById(R.id.bottom_sheet);
          mViewBg = findViewById(R.id.mViewBg);
          Button button1 = (Button) findViewById(R.id.button_1);
          button1.setOnClickListener(this);
          mViewBg.setOnClickListener(this);
          mBottomSheetBehavior = BottomSheetBehavior.from(bottomSheet);
          mBottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
              @Override
              public void onStateChanged(@NonNull View bottomSheet, int newState) {
                  if (newState == BottomSheetBehavior.STATE_COLLAPSED)
                      mViewBg.setVisibility(View.GONE);
              }
      
              @Override
              public void onSlide(@NonNull View bottomSheet, float slideOffset) {
                  Log.d(TAG, "onSlide: slideOffset" + slideOffset + "");
                 mViewBg.setVisibility(View.VISIBLE);
                  mViewBg.setAlpha(slideOffset);
              }
          });
      
      }
      
      
      @Override
      public void onClick(View v) {
          switch (v.getId()) {
              case R.id.button_1: {
                  mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
                  break;
              }
              case R.id.bg: {
                  mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
                  break;
              }
          }
      }
      
      
      @Override
      public boolean dispatchTouchEvent(MotionEvent event) {
          if (event.getAction() == MotionEvent.ACTION_DOWN) {
              if (mBottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
                  Rect outRect = new Rect();
                  bottomSheet.getGlobalVisibleRect(outRect);
                  if (!outRect.contains((int) event.getRawX(), (int) event.getRawY())) {
                      mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
                      return true;
                  }
      
              }
          }
          return super.dispatchTouchEvent(event);
      }
      
      }
      

    Solution 2:

    You can create a custom fragment that has layout (kind of bottomSheet) attached to bottom and make the background transparent_black & when touching on that BG remove that fragment. Ex :

    activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#ff2020"
        android:orientation="vertical"
        tools:context="com.example.jiffysoftwaresolutions.copypastesampleapp.MainActivity">
    
        <Button
            android:id="@+id/show"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Show" />
    
        <FrameLayout
            android:id="@+id/bottom_sheet_fragment_container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"></FrameLayout>
    
    </RelativeLayout>
    

    MainActivity.java

    public class MainActivity extends AppCompatActivity {
    
        private BottomSheetFragment bottomSheetFragment;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            findViewById(R.id.show).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
    
                    if (bottomSheetFragment == null) {
                        bottomSheetFragment = new BottomSheetFragment();
                    }
                    getSupportFragmentManager().beginTransaction().add(R.id.bottom_sheet_fragment_container, bottomSheetFragment).addToBackStack(null).commit();
    
                }
            });
    
        }
    
    
        public void removeBottomSheet() {
            try {
                getSupportFragmentManager().beginTransaction().remove(bottomSheetFragment).addToBackStack(null).commit();
            } catch (Exception e) {
            }
        }
    
    }
    

    BottomSheetFragment.java

    public class BottomSheetFragment extends Fragment {
    
    
        private View rootView;
        private LayoutInflater layoutInflater;
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            layoutInflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    
        }
    
    
        @Override
        public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
            rootView = inflater.inflate(R.layout.bottom_sheet_layout, container, false);
            rootView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    // remove sheet on BG touch
                    ((MainActivity) getActivity()).removeBottomSheet();
                }
            });
            return rootView;
        }
    
    }
    

    bottom_sheet_layout.xml

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#6d000000"
        android:gravity="bottom">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="#fff"
            android:orientation="vertical"
            android:padding="5dp">
    
            <Button
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Button1"
                android:textColor="#000" />
    
            <Button
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Button2"
                android:textColor="#000" />
    
        </LinearLayout>
    
    </RelativeLayout>
    

    To Add that fragment with bottom_top/animation.. you can follow this link : Android Fragments and animation

    Solution 3:

    Using the Interface - onSlide which as a parameter slideOffSet of type float , can be used to dim the background. The new offset of this bottom sheet within [-1,1] range. Offset increases as this bottom sheet is moving upward. From 0 to 1 the sheet is between collapsed and expanded states and from -1 to 0 it is between hidden and collapsed states.

    if ( slideOffSet>=0 && slideOffSet<=1 ) {
        mainActivityLayoutView.setAlpha( 1f - slideOffSet ); 
    }
    

    where mainActivityLayoutView is the id for NestedScrollView , holding the main contents of the activity.

  9. database - How can I avoid concurrency problems when using SQLite on Android?
  10. Question:

    What would be considered the best practices when executing queries on an SQLite database within an Android app?

    Is it safe to run inserts, deletes and select queries from an AsyncTask's doInBackground? Or should I use the UI Thread? I suppose that database queries can be "heavy" and should not use the UI thread as it can lock up the app - resulting in an Application Not Responding (ANR).

    If I have several AsyncTasks, should they share a connection or should they open a connection each?

    Are there any best practices for these scenarios?


    Solution 1:

    Inserts, updates, deletes and reads are generally OK from multiple threads, but Brad's answer is not correct. You have to be careful with how you create your connections and use them. There are situations where your update calls will fail, even if your database doesn't get corrupted.

    The basic answer.

    The SqliteOpenHelper object holds on to one database connection. It appears to offer you a read and write connection, but it really doesn't. Call the read-only, and you'll get the write database connection regardless.

    So, one helper instance, one db connection. Even if you use it from multiple threads, one connection at a time. The SqliteDatabase object uses java locks to keep access serialized. So, if 100 threads have one db instance, calls to the actual on-disk database are serialized.

    So, one helper, one db connection, which is serialized in java code. One thread, 1000 threads, if you use one helper instance shared between them, all of your db access code is serial. And life is good (ish).

    If you try to write to the database from actual distinct connections at the same time, one will fail. It will not wait till the first is done and then write. It will simply not write your change. Worse, if you don’t call the right version of insert/update on the SQLiteDatabase, you won’t get an exception. You’ll just get a message in your LogCat, and that will be it.

    So, multiple threads? Use one helper. Period. If you KNOW only one thread will be writing, you MAY be able to use multiple connections, and your reads will be faster, but buyer beware. I haven't tested that much.

    Here's a blog post with far more detail and an example app.

    • Android Sqlite Locking (Updated link 6/18/2012)
    • Android-Database-Locking-Collisions-Example by touchlab on GitHub

    Gray and I are actually wrapping up an ORM tool, based off of his Ormlite, that works natively with Android database implementations, and follows the safe creation/calling structure I describe in the blog post. That should be out very soon. Take a look.


    In the meantime, there is a follow up blog post:

    • Single SQLite connection

    Also checkout the fork by 2point0 of the previously mentioned locking example:

    • Android-Database-Locking-Collisions-Example by 2point0 on GitHub

    Solution 2:

    Concurrent Database Access

    Same article on my blog(I like formatting more)

    I wrote small article which describe how to make access to your android database thread safe.


    Assuming you have your own SQLiteOpenHelper.

    public class DatabaseHelper extends SQLiteOpenHelper { ... }
    

    Now you want to write data to database in separate threads.

     // Thread 1
     Context context = getApplicationContext();
     DatabaseHelper helper = new DatabaseHelper(context);
     SQLiteDatabase database = helper.getWritableDatabase();
     database.insert(…);
     database.close();
    
     // Thread 2
     Context context = getApplicationContext();
     DatabaseHelper helper = new DatabaseHelper(context);
     SQLiteDatabase database = helper.getWritableDatabase();
     database.insert(…);
     database.close();
    

    You will get following message in your logcat and one of your changes will not be written.

    android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
    

    This is happening because every time you create new SQLiteOpenHelper object you are actually making new database connection. If you try to write to the database from actual distinct connections at the same time, one will fail. (from answer above)

    To use database with multiple threads we need to make sure we are using one database connection.

    Let’s make singleton class Database Manager which will hold and return single SQLiteOpenHelper object.

    public class DatabaseManager {
    
        private static DatabaseManager instance;
        private static SQLiteOpenHelper mDatabaseHelper;
    
        public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
            if (instance == null) {
                instance = new DatabaseManager();
                mDatabaseHelper = helper;
            }
        }
    
        public static synchronized DatabaseManager getInstance() {
            if (instance == null) {
                throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
                        " is not initialized, call initialize(..) method first.");
            }
    
            return instance;
        }
    
        public SQLiteDatabase getDatabase() {
            return new mDatabaseHelper.getWritableDatabase();
        }
    
    }
    

    Updated code which write data to database in separate threads will look like this.

     // In your application class
     DatabaseManager.initializeInstance(new MySQLiteOpenHelper());
     // Thread 1
     DatabaseManager manager = DatabaseManager.getInstance();
     SQLiteDatabase database = manager.getDatabase()
     database.insert(…);
     database.close();
    
     // Thread 2
     DatabaseManager manager = DatabaseManager.getInstance();
     SQLiteDatabase database = manager.getDatabase()
     database.insert(…);
     database.close();
    

    This will bring you another crash.

    java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase
    

    Since we are using only one database connection, method getDatabase() return same instance of SQLiteDatabase object for Thread1 and Thread2. What is happening, Thread1 may close database, while Thread2 is still using it. That’s why we have IllegalStateException crash.

    We need to make sure no-one is using database and only then close it. Some folks on stackoveflow recommended to never close your SQLiteDatabase. This will result in following logcat message.

    Leak found
    Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed
    

    Working sample

    public class DatabaseManager {
    
        private int mOpenCounter;
    
        private static DatabaseManager instance;
        private static SQLiteOpenHelper mDatabaseHelper;
        private SQLiteDatabase mDatabase;
    
        public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
            if (instance == null) {
                instance = new DatabaseManager();
                mDatabaseHelper = helper;
            }
        }
    
        public static synchronized DatabaseManager getInstance() {
            if (instance == null) {
                throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
                        " is not initialized, call initializeInstance(..) method first.");
            }
    
            return instance;
        }
    
        public synchronized SQLiteDatabase openDatabase() {
            mOpenCounter++;
            if(mOpenCounter == 1) {
                // Opening new database
                mDatabase = mDatabaseHelper.getWritableDatabase();
            }
            return mDatabase;
        }
    
        public synchronized void closeDatabase() {
            mOpenCounter--;
            if(mOpenCounter == 0) {
                // Closing database
                mDatabase.close();
    
            }
        }
    
    }
    

    Use it as follows.

    SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();
    database.insert(...);
    // database.close(); Don't close it directly!
    DatabaseManager.getInstance().closeDatabase(); // correct way
    

    Every time you need database you should call openDatabase() method of DatabaseManager class. Inside this method, we have a counter, which indicate how many times database is opened. If it equals to one, it means we need to create new database connection, if not, database connection is already created.

    The same happens in closeDatabase() method. Every time we call this method, counter is decreased, whenever it goes to zero, we are closing database connection.


    Now you should be able to use your database and be sure it's thread safe.

    Solution 3:

    • Use a Thread or AsyncTask for long-running operations (50ms+). Test your app to see where that is. Most operations (probably) don't require a thread, because most operations (probably) only involve a few rows. Use a thread for bulk operations.
    • Share one SQLiteDatabase instance for each DB on disk between threads and implement a counting system to keep track of open connections.

    Are there any best practices for these scenarios?

    Share a static field between all your classes. I used to keep a singleton around for that and other things that need to be shared. A counting scheme (generally using AtomicInteger) also should be used to make sure you never close the database early or leave it open.

    My solution:

    The old version I wrote is available at https://github.com/Taeluf/dev/tree/main/archived/databasemanager and is not maintained. If you want to understand my solution, look at the code and read my notes. My notes are usually pretty helpful.

    1. copy/paste the code into a new file named DatabaseManager. (or download it from github)
    2. extend DatabaseManager and implement onCreate and onUpgrade like you normally would. You can create multiple subclasses of the one DatabaseManager class in order to have different databases on disk.
    3. Instantiate your subclass and call getDb() to use the SQLiteDatabase class.
    4. Call close() for each subclass you instantiated

    The code to copy/paste:

    import android.content.Context;
    import android.database.sqlite.SQLiteDatabase;
    
    import java.util.concurrent.ConcurrentHashMap;
    
    /** Extend this class and use it as an SQLiteOpenHelper class
     *
     * DO NOT distribute, sell, or present this code as your own. 
     * for any distributing/selling, or whatever, see the info at the link below
     *
     * Distribution, attribution, legal stuff,
     * See https://github.com/JakarCo/databasemanager
     * 
     * If you ever need help with this code, contact me at support@androidsqlitelibrary.com (or support@jakar.co )
     * 
     * Do not sell this. but use it as much as you want. There are no implied or express warranties with this code. 
     *
     * This is a simple database manager class which makes threading/synchronization super easy.
     *
     * Extend this class and use it like an SQLiteOpenHelper, but use it as follows:
     *  Instantiate this class once in each thread that uses the database. 
     *  Make sure to call {@link #close()} on every opened instance of this class
     *  If it is closed, then call {@link #open()} before using again.
     * 
     * Call {@link #getDb()} to get an instance of the underlying SQLiteDatabse class (which is synchronized)
     *
     * I also implement this system (well, it's very similar) in my <a href="http://androidslitelibrary.com">Android SQLite Libray</a> at http://androidslitelibrary.com
     * 
     *
     */
    abstract public class DatabaseManager {
        
        /**See SQLiteOpenHelper documentation
        */
        abstract public void onCreate(SQLiteDatabase db);
        /**See SQLiteOpenHelper documentation
         */
        abstract public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);
        /**Optional.
         * *
         */
        public void onOpen(SQLiteDatabase db){}
        /**Optional.
         * 
         */
        public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
        /**Optional
         * 
         */
        public void onConfigure(SQLiteDatabase db){}
    
    
    
        /** The SQLiteOpenHelper class is not actually used by your application.
         *
         */
        static private class DBSQLiteOpenHelper extends SQLiteOpenHelper {
    
            DatabaseManager databaseManager;
            private AtomicInteger counter = new AtomicInteger(0);
    
            public DBSQLiteOpenHelper(Context context, String name, int version, DatabaseManager databaseManager) {
                super(context, name, null, version);
                this.databaseManager = databaseManager;
            }
    
            public void addConnection(){
                counter.incrementAndGet();
            }
            public void removeConnection(){
                counter.decrementAndGet();
            }
            public int getCounter() {
                return counter.get();
            }
            @Override
            public void onCreate(SQLiteDatabase db) {
                databaseManager.onCreate(db);
            }
    
            @Override
            public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
                databaseManager.onUpgrade(db, oldVersion, newVersion);
            }
    
            @Override
            public void onOpen(SQLiteDatabase db) {
                databaseManager.onOpen(db);
            }
    
            @Override
            public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
                databaseManager.onDowngrade(db, oldVersion, newVersion);
            }
    
            @Override
            public void onConfigure(SQLiteDatabase db) {
                databaseManager.onConfigure(db);
            }
        }
    
        private static final ConcurrentHashMap<String,DBSQLiteOpenHelper> dbMap = new ConcurrentHashMap<String, DBSQLiteOpenHelper>();
    
        private static final Object lockObject = new Object();
    
    
        private DBSQLiteOpenHelper sqLiteOpenHelper;
        private SQLiteDatabase db;
        private Context context;
    
        /** Instantiate a new DB Helper. 
         * <br> SQLiteOpenHelpers are statically cached so they (and their internally cached SQLiteDatabases) will be reused for concurrency
         *
         * @param context Any {@link android.content.Context} belonging to your package.
         * @param name The database name. This may be anything you like. Adding a file extension is not required and any file extension you would like to use is fine.
         * @param version the database version.
         */
        public DatabaseManager(Context context, String name, int version) {
            String dbPath = context.getApplicationContext().getDatabasePath(name).getAbsolutePath();
            synchronized (lockObject) {
                sqLiteOpenHelper = dbMap.get(dbPath);
                if (sqLiteOpenHelper==null) {
                    sqLiteOpenHelper = new DBSQLiteOpenHelper(context, name, version, this);
                    dbMap.put(dbPath,sqLiteOpenHelper);
                }
                //SQLiteOpenHelper class caches the SQLiteDatabase, so this will be the same SQLiteDatabase object every time
                db = sqLiteOpenHelper.getWritableDatabase();
            }
            this.context = context.getApplicationContext();
        }
        /**Get the writable SQLiteDatabase
         */
        public SQLiteDatabase getDb(){
            return db;
        }
    
        /** Check if the underlying SQLiteDatabase is open
         *
         * @return whether the DB is open or not
         */
        public boolean isOpen(){
            return (db!=null&&db.isOpen());
        }
    
    
        /** Lowers the DB counter by 1 for any {@link DatabaseManager}s referencing the same DB on disk
         *  <br />If the new counter is 0, then the database will be closed.
         *  <br /><br />This needs to be called before application exit.
         * <br />If the counter is 0, then the underlying SQLiteDatabase is <b>null</b> until another DatabaseManager is instantiated or you call {@link #open()}
         *
         * @return true if the underlying {@link android.database.sqlite.SQLiteDatabase} is closed (counter is 0), and false otherwise (counter > 0)
         */
        public boolean close(){
            sqLiteOpenHelper.removeConnection();
            if (sqLiteOpenHelper.getCounter()==0){
                synchronized (lockObject){
                    if (db.inTransaction())db.endTransaction();
                    if (db.isOpen())db.close();
                    db = null;
                }
                return true;
            }
            return false;
        }
        /** Increments the internal db counter by one and opens the db if needed
        *
        */
        public void open(){
            sqLiteOpenHelper.addConnection();
            if (db==null||!db.isOpen()){
                    synchronized (lockObject){
                        db = sqLiteOpenHelper.getWritableDatabase();
                    }
            } 
        }
    }