From f8e4b1a3f8ee3744dd2fc65b524e5c883fea5138 Mon Sep 17 00:00:00 2001 From: Genius Date: Sun, 28 Jun 2015 11:57:53 -0700 Subject: [PATCH 1/3] added comments --- .../src/vandy/mooc/operations/InsertContactsCommand.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ex/ContactsContentProviderSimple/src/vandy/mooc/operations/InsertContactsCommand.java b/ex/ContactsContentProviderSimple/src/vandy/mooc/operations/InsertContactsCommand.java index c8f9abc..04a5c0a 100644 --- a/ex/ContactsContentProviderSimple/src/vandy/mooc/operations/InsertContactsCommand.java +++ b/ex/ContactsContentProviderSimple/src/vandy/mooc/operations/InsertContactsCommand.java @@ -60,6 +60,8 @@ public void execute(Iterator contactsIter) { new GenericAsyncTask<>(this); // Execute the GenericAsyncTask. + //this uses active object pattern and inversion control{DIP ie dependency injection } to execute in the context of InsertContactsCommand + //and in a background thread asyncTask.execute(contactsIter); } @@ -96,7 +98,7 @@ private Integer insertAllContacts(Iterator contactsIter) { // ContentProvider. while (contactsIter.hasNext()) addContact(contactsIter.next(), - batchOperation); + batchOperation);//pass batch operation by reference try { // Apply all the batched operations synchronously. From e159e0077282946ec6ed7be8ec00ca43b95f58c5 Mon Sep 17 00:00:00 2001 From: Genius Date: Sun, 28 Jun 2015 18:24:08 -0700 Subject: [PATCH 2/3] fixed menu issues and cleaned up some code --- ex/HobbitContentProvider/.idea/.name | 1 + ex/HobbitContentProvider/.idea/compiler.xml | 22 + .../.idea/copyright/profiles_settings.xml | 3 + ex/HobbitContentProvider/.idea/encodings.xml | 6 + ex/HobbitContentProvider/.idea/gradle.xml | 18 + ex/HobbitContentProvider/.idea/misc.xml | 62 + ex/HobbitContentProvider/.idea/modules.xml | 9 + .../.idea/runConfigurations.xml | 12 + ex/HobbitContentProvider/.idea/vcs.xml | 6 + ex/HobbitContentProvider/.idea/workspace.xml | 2240 +++++++++++++++++ .../HobbitContentProvider.iml | 19 + ex/HobbitContentProvider/app/app.iml | 92 + ex/HobbitContentProvider/app/build.gradle | 19 + .../app/src/main/AndroidManifest.xml | 29 + .../vandy/mooc/activities/HobbitActivity.java | 277 ++ .../vandy/mooc/common/ConfigurableOps.java | 24 + .../vandy/mooc/common/GenericActivity.java | 146 ++ .../mooc/common/LifecycleLoggingActivity.java | 137 + .../mooc/common/RetainedFragmentManager.java | 164 ++ .../java/vandy/mooc/operations/HobbitOps.java | 188 ++ .../HobbitOpsContentProviderClient.java | 137 + .../operations/HobbitOpsContentResolver.java | 119 + .../vandy/mooc/operations/HobbitOpsImpl.java | 299 +++ .../mooc/provider/CharacterContract.java | 163 ++ .../vandy/mooc/provider/CharacterRecord.java | 75 + .../mooc/provider/HobbitDatabaseHelper.java | 81 + .../vandy/mooc/provider/HobbitProvider.java | 145 ++ .../mooc/provider/HobbitProviderHashMap.java | 329 +++ .../mooc/provider/HobbitProviderImpl.java | 309 +++ .../mooc/provider/HobbitProviderSQLite.java | 298 +++ .../app/src/main/res/drawable-hdpi/icon.png | Bin 0 -> 4147 bytes .../app/src/main/res/drawable-ldpi/icon.png | Bin 0 -> 1723 bytes .../app/src/main/res/drawable-mdpi/icon.png | Bin 0 -> 2574 bytes .../src/main/res/layout/hobbit_activity.xml | 43 + .../app/src/main/res/layout/list_layout.xml | 23 + .../src/main/res/menu/ops_options_menu.xml | 8 + .../app/src/main/res/values/strings.xml | 10 + ex/HobbitContentProvider/build.gradle | 15 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 49896 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + ex/HobbitContentProvider/gradlew | 164 ++ ex/HobbitContentProvider/gradlew.bat | 90 + ex/HobbitContentProvider/import-summary.txt | 26 + ex/HobbitContentProvider/settings.gradle | 1 + 44 files changed, 5815 insertions(+) create mode 100644 ex/HobbitContentProvider/.idea/.name create mode 100644 ex/HobbitContentProvider/.idea/compiler.xml create mode 100644 ex/HobbitContentProvider/.idea/copyright/profiles_settings.xml create mode 100644 ex/HobbitContentProvider/.idea/encodings.xml create mode 100644 ex/HobbitContentProvider/.idea/gradle.xml create mode 100644 ex/HobbitContentProvider/.idea/misc.xml create mode 100644 ex/HobbitContentProvider/.idea/modules.xml create mode 100644 ex/HobbitContentProvider/.idea/runConfigurations.xml create mode 100644 ex/HobbitContentProvider/.idea/vcs.xml create mode 100644 ex/HobbitContentProvider/.idea/workspace.xml create mode 100644 ex/HobbitContentProvider/HobbitContentProvider.iml create mode 100644 ex/HobbitContentProvider/app/app.iml create mode 100644 ex/HobbitContentProvider/app/build.gradle create mode 100644 ex/HobbitContentProvider/app/src/main/AndroidManifest.xml create mode 100644 ex/HobbitContentProvider/app/src/main/java/vandy/mooc/activities/HobbitActivity.java create mode 100644 ex/HobbitContentProvider/app/src/main/java/vandy/mooc/common/ConfigurableOps.java create mode 100644 ex/HobbitContentProvider/app/src/main/java/vandy/mooc/common/GenericActivity.java create mode 100644 ex/HobbitContentProvider/app/src/main/java/vandy/mooc/common/LifecycleLoggingActivity.java create mode 100644 ex/HobbitContentProvider/app/src/main/java/vandy/mooc/common/RetainedFragmentManager.java create mode 100644 ex/HobbitContentProvider/app/src/main/java/vandy/mooc/operations/HobbitOps.java create mode 100644 ex/HobbitContentProvider/app/src/main/java/vandy/mooc/operations/HobbitOpsContentProviderClient.java create mode 100644 ex/HobbitContentProvider/app/src/main/java/vandy/mooc/operations/HobbitOpsContentResolver.java create mode 100644 ex/HobbitContentProvider/app/src/main/java/vandy/mooc/operations/HobbitOpsImpl.java create mode 100644 ex/HobbitContentProvider/app/src/main/java/vandy/mooc/provider/CharacterContract.java create mode 100644 ex/HobbitContentProvider/app/src/main/java/vandy/mooc/provider/CharacterRecord.java create mode 100644 ex/HobbitContentProvider/app/src/main/java/vandy/mooc/provider/HobbitDatabaseHelper.java create mode 100644 ex/HobbitContentProvider/app/src/main/java/vandy/mooc/provider/HobbitProvider.java create mode 100644 ex/HobbitContentProvider/app/src/main/java/vandy/mooc/provider/HobbitProviderHashMap.java create mode 100644 ex/HobbitContentProvider/app/src/main/java/vandy/mooc/provider/HobbitProviderImpl.java create mode 100644 ex/HobbitContentProvider/app/src/main/java/vandy/mooc/provider/HobbitProviderSQLite.java create mode 100644 ex/HobbitContentProvider/app/src/main/res/drawable-hdpi/icon.png create mode 100644 ex/HobbitContentProvider/app/src/main/res/drawable-ldpi/icon.png create mode 100644 ex/HobbitContentProvider/app/src/main/res/drawable-mdpi/icon.png create mode 100644 ex/HobbitContentProvider/app/src/main/res/layout/hobbit_activity.xml create mode 100644 ex/HobbitContentProvider/app/src/main/res/layout/list_layout.xml create mode 100644 ex/HobbitContentProvider/app/src/main/res/menu/ops_options_menu.xml create mode 100644 ex/HobbitContentProvider/app/src/main/res/values/strings.xml create mode 100644 ex/HobbitContentProvider/build.gradle create mode 100644 ex/HobbitContentProvider/gradle/wrapper/gradle-wrapper.jar create mode 100644 ex/HobbitContentProvider/gradle/wrapper/gradle-wrapper.properties create mode 100644 ex/HobbitContentProvider/gradlew create mode 100644 ex/HobbitContentProvider/gradlew.bat create mode 100644 ex/HobbitContentProvider/import-summary.txt create mode 100644 ex/HobbitContentProvider/settings.gradle diff --git a/ex/HobbitContentProvider/.idea/.name b/ex/HobbitContentProvider/.idea/.name new file mode 100644 index 0000000..ca91b58 --- /dev/null +++ b/ex/HobbitContentProvider/.idea/.name @@ -0,0 +1 @@ +HobbitContentProvider \ No newline at end of file diff --git a/ex/HobbitContentProvider/.idea/compiler.xml b/ex/HobbitContentProvider/.idea/compiler.xml new file mode 100644 index 0000000..9a8b7e5 --- /dev/null +++ b/ex/HobbitContentProvider/.idea/compiler.xml @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/ex/HobbitContentProvider/.idea/copyright/profiles_settings.xml b/ex/HobbitContentProvider/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..e7bedf3 --- /dev/null +++ b/ex/HobbitContentProvider/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/ex/HobbitContentProvider/.idea/encodings.xml b/ex/HobbitContentProvider/.idea/encodings.xml new file mode 100644 index 0000000..97626ba --- /dev/null +++ b/ex/HobbitContentProvider/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ex/HobbitContentProvider/.idea/gradle.xml b/ex/HobbitContentProvider/.idea/gradle.xml new file mode 100644 index 0000000..1bbc21d --- /dev/null +++ b/ex/HobbitContentProvider/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/ex/HobbitContentProvider/.idea/misc.xml b/ex/HobbitContentProvider/.idea/misc.xml new file mode 100644 index 0000000..dae7a60 --- /dev/null +++ b/ex/HobbitContentProvider/.idea/misc.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.7 + + + + + + + + \ No newline at end of file diff --git a/ex/HobbitContentProvider/.idea/modules.xml b/ex/HobbitContentProvider/.idea/modules.xml new file mode 100644 index 0000000..ebbfbbb --- /dev/null +++ b/ex/HobbitContentProvider/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/ex/HobbitContentProvider/.idea/runConfigurations.xml b/ex/HobbitContentProvider/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/ex/HobbitContentProvider/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/ex/HobbitContentProvider/.idea/vcs.xml b/ex/HobbitContentProvider/.idea/vcs.xml new file mode 100644 index 0000000..b2bdec2 --- /dev/null +++ b/ex/HobbitContentProvider/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ex/HobbitContentProvider/.idea/workspace.xml b/ex/HobbitContentProvider/.idea/workspace.xml new file mode 100644 index 0000000..c1b785a --- /dev/null +++ b/ex/HobbitContentProvider/.idea/workspace.xml @@ -0,0 +1,2240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Android Lint + + + + + AndroidLintMissingSuperCall + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + localhost + 5050 + + + + + + + + + + 1435443986383 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ex/HobbitContentProvider/HobbitContentProvider.iml b/ex/HobbitContentProvider/HobbitContentProvider.iml new file mode 100644 index 0000000..cd0672d --- /dev/null +++ b/ex/HobbitContentProvider/HobbitContentProvider.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ex/HobbitContentProvider/app/app.iml b/ex/HobbitContentProvider/app/app.iml new file mode 100644 index 0000000..1b8f996 --- /dev/null +++ b/ex/HobbitContentProvider/app/app.iml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ex/HobbitContentProvider/app/build.gradle b/ex/HobbitContentProvider/app/build.gradle new file mode 100644 index 0000000..435ffa0 --- /dev/null +++ b/ex/HobbitContentProvider/app/build.gradle @@ -0,0 +1,19 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 22 + buildToolsVersion "21.1.2" + + defaultConfig { + applicationId "vandy.mooc" + minSdkVersion 15 + targetSdkVersion 22 + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + } +} diff --git a/ex/HobbitContentProvider/app/src/main/AndroidManifest.xml b/ex/HobbitContentProvider/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..f0b2d90 --- /dev/null +++ b/ex/HobbitContentProvider/app/src/main/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + diff --git a/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/activities/HobbitActivity.java b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/activities/HobbitActivity.java new file mode 100644 index 0000000..250ab89 --- /dev/null +++ b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/activities/HobbitActivity.java @@ -0,0 +1,277 @@ +package vandy.mooc.activities; + +import android.annotation.SuppressLint; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.RemoteException; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.ListView; +import android.widget.SimpleCursorAdapter; +import android.widget.Toast; + +import vandy.mooc.R; +import vandy.mooc.common.GenericActivity; +import vandy.mooc.operations.HobbitOps; + +/** + * This Activity illustrates how to use the HobbitContentProvider to + * perform various "CRUD" (i.e., insert, query, update, and delete) + * operations using characters from Tolkien's classic book "The + * Hobbit." It plays the role of the "View" in the + * Model-View-Presenter pattern. + */ +public class HobbitActivity extends GenericActivity { + + /** + * Uri for the "Necromancer". + */ + private Uri mNecromancerUri; + + /** + * Used to display the results of contacts queried from the + * HobbitContentProvider. + */ + private SimpleCursorAdapter mAdapter; + + + /** + * Hook method called when a new instance of Activity is created. + * One time initialization code goes here, e.g., initializing + * views. + * + * @param savedInstanceState + * object that contains saved state information. + */ + @SuppressLint("MissingSuperCall") + @Override + public void onCreate(Bundle savedInstanceState) { + // Call up to the special onCreate() method in + // GenericActivity, passing in the HobbitOps class to + // instantiate and manage. + super.onCreate(savedInstanceState, HobbitOps.class); + + + // Set the content view for this Activity. + setContentView(R.layout.hobbit_activity); + + // Initialize the List View. + /* + ListView displays the Hobbit character information. + */ + ListView mListView = (ListView) findViewById(R.id.list); + + // Initialize the SimpleCursorAdapter. + mAdapter = getOps().makeCursorAdapter(); + + // Connect the ListView with the SimpleCursorAdapter. + mListView.setAdapter(mAdapter); + } + + /** + * Hook method that gives a final chance to release resources and + * stop spawned threads. This method may not always be called + * when the Android system kills the hosting process. + */ + @Override + public void onDestroy() { + // Call up to the superclass's onDestroy() hook method. + super.onDestroy(); + + // Close down the HobbitOps. + getOps().close(); + } + + /** + * This method is run when the user clicks the "Add All" button. + * It insert various characters from the Hobbit book into the + * "database". + */ + public void addAll(View v) { + try { + // Insert the main protagonist. + getOps().insert("Bilbo", + "Hobbit"); + + // Insert the main wizard. + getOps().insert("Gandalf", + "Maia"); + + // Insert all the dwarves. + getOps().bulkInsert(new String[] { + "Thorin", "Kili", "Fili", + "Balin", "Dwalin", "Oin", "Gloin", + "Dori", "Nori", "Ori", + "Bifur", "Bofur", "Bombur" + }, + "Dwarf"); + + // Insert the main antagonist. + getOps().insert("Smaug", + "Dragon"); + + // Insert Beorn. + getOps().insert("Beorn", + "Man"); + + // Insert the Master of Laketown + getOps().insert("Master", + "Man"); + + // Insert another antagonist. + mNecromancerUri = + getOps().insert("Necromancer", + "Maia"); + + // Display the results; + getOps().displayAll(); + } catch (RemoteException e) { + Log.d(TAG, + "exception " + + e); + } + } + + /** + * This method is run when the user clicks the "Modify All" button + * to modify certain Hobbit characters from the "database." + */ + public void modifyAll(View v) { + try { + // Update Beorn's race since he's a skinchanger. + getOps().updateRaceByName("Beorn", + "Bear"); + + if (mNecromancerUri != null) + // The Necromancer is really Sauron the Deceiver. + getOps().updateByUri(mNecromancerUri, + "Sauron", + "Maia"); + + // Delete dwarves who get killed in the Battle of Five + // Armies. + getOps().deleteByName(new String[] { + "Thorin", + "Kili", + "Fili" + }); + + // Delete Smaug since he gets killed by Bard the Bowman + // and the "Master" (who's a man) since he's killed later + // in the book. + getOps().deleteByRace(new String[] { + "Dragon", + "Man" + }); + + // Display the results; + getOps().displayAll(); + } catch (RemoteException e) { + Log.d(TAG, + "exception " + + e); + } + } + + /** + * This method is run when the user clicks the "Delete All" button + * to remove all Hobbit characters from the "database." + */ + public void deleteAll(View v) { + try { + // Clear out the database. + int numDeleted = getOps().deleteAll(); + + // Inform the user how many characters were deleted. + Toast.makeText(this, + "Deleted " + + numDeleted + + " Hobbit characters", + Toast.LENGTH_SHORT).show(); + + // Display the results; + getOps().displayAll(); + } catch (RemoteException e) { + Log.d(TAG, + "exception " + + e); + } + } + + /** + * This method is run when the user clicks the "Display All" + * button to display all races of Hobbit characters from the + * "database." + */ + public void displayAll(View v) { + try { + // Display the results. + getOps().displayAll(); + } catch (RemoteException e) { + Log.d(TAG, + "exception " + + e); + } + } + + /** + * Display the contents of the cursor as a ListView. + */ + public void displayCursor(Cursor cursor) { + // Display the designated columns in the cursor as a List in + // the ListView connected to the SimpleCursorAdapter. + mAdapter.changeCursor(cursor); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + return chooseOpsOption(item); + } + + + + /** + * Called by Android framework when menu option is clicked. + * + * @param item + * @return true + */ + private boolean chooseOpsOption(MenuItem item) { + switch (item.getItemId()) { + case R.id.contentResolver: + getOps().setContentProviderAccessMeans + (HobbitOps.ContentProviderAccessMeans.CONTENT_RESOLVER); + Toast.makeText(this, + "ContentResolver selected", + Toast.LENGTH_SHORT).show(); + return true; + + case R.id.contentProviderClient: + getOps().setContentProviderAccessMeans + (HobbitOps.ContentProviderAccessMeans.CONTENT_PROVIDER_CLIENT); + Toast.makeText(this, + "ContentProviderClient selected", + Toast.LENGTH_SHORT).show(); + + return true; + } + getOps().onConfiguration(this,true);//after the selection is performed allow the content resolver to reconnect with the + //// content provider + return false;//return super.onOptionsItemSelected(item); + } + + /** + * Inflates the Operations ("Ops") Option Menu. + */ + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.ops_options_menu, + menu); + return true; + } +} diff --git a/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/common/ConfigurableOps.java b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/common/ConfigurableOps.java new file mode 100644 index 0000000..e258f5b --- /dev/null +++ b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/common/ConfigurableOps.java @@ -0,0 +1,24 @@ +package vandy.mooc.common; + +import android.app.Activity; + +/** + * The base interface that an operations ("Ops") class must implement + * so that it can be notified automatically by the GenericActivity + * framework when runtime configuration changes occur. + */ +public interface ConfigurableOps { + /** + * Hook method dispatched by the GenericActivity framework to + * initialize an operations ("Ops") object after it's been + * created. + * + * @param activity The currently active Activity. + * @param firstTimeIn Set to "true" if this is the first time the + * Ops class is initialized, else set to + * "false" if called after a runtime + * configuration change. + */ + void onConfiguration(Activity activity, + boolean firstTimeIn); +} diff --git a/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/common/GenericActivity.java b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/common/GenericActivity.java new file mode 100644 index 0000000..e732eab --- /dev/null +++ b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/common/GenericActivity.java @@ -0,0 +1,146 @@ +package vandy.mooc.common; + + +import android.annotation.TargetApi; +import android.os.Build; +import android.os.Bundle; +import android.util.Log; + +/** + * This Activity provides a framework that automatically handles + * runtime configuration changes in conjunction with an instance of + * OpsType, which must implement the ConfigurableOps interface. It + * also extends LifecycleLoggingActivity so that all lifecycle hook + * method calls are automatically logged. + */ +public class GenericActivity + extends LifecycleLoggingActivity { + /** + * Used to retain the OpsType state between runtime configuration + * changes. + */ + private final RetainedFragmentManager mRetainedFragmentManager + = new RetainedFragmentManager(this.getFragmentManager(), + TAG); + + /** + * Class type of the operations ("Ops") type. + */ + private Class mOpsType; + + /** + * Instance of the operations ("Ops") type. + */ + private OpsType mOpsInstance; + + /** + * Lifecycle hook method that's called when this Activity is + * created. + * + * @param savedInstanceState + * Object that contains saved state information. + * @param opsType + * Class object that's used to create an operations + * ("Ops") object. + */ + @TargetApi(Build.VERSION_CODES.KITKAT) + public void onCreate(Bundle savedInstanceState, + Class opsType) { + // Call up to the super class. + super.onCreate(savedInstanceState); + + try { + // Handle configuration-related events, including the + // initial creation of an Activity and any subsequent + // runtime configuration changes. + handleConfiguration(opsType); + } catch (InstantiationException | IllegalAccessException e) { + Log.d(TAG, + "handleConfiguration " + + e); + // Propagate this as a runtime exception. + throw new RuntimeException(e); + } + } + + /** + * Handle hardware (re)configurations, such as rotating the + * display. + * + * @throws IllegalAccessException + * @throws InstantiationException + */ + public void handleConfiguration(Class opsType) + throws InstantiationException, IllegalAccessException { + + // If this method returns true it's the first time the + // Activity has been created. + if (mRetainedFragmentManager.firstTimeIn()) { + Log.d(TAG, + "First time onCreate() call"); + + // Initialize the GenericActivity fields. + initialize(opsType); + } else { + // The RetainedFragmentManager was previously initialized, + // which means that a runtime configuration change + // occured. + Log.d(TAG, + "Second or subsequent onCreate() call"); + + // Try to obtain the OpsType instance from the + // RetainedFragmentManager. + mOpsInstance = + mRetainedFragmentManager.get(opsType.getSimpleName()); + + // This check shouldn't be necessary under normal + // circumstances, but it's better to lose state than to + // crash! + if (mOpsInstance == null) + // Initialize the GenericActivity fields. + initialize(opsType); + else + // Inform it that the runtime configuration change has + // completed. + mOpsInstance.onConfiguration(this, + false); + } + } + + /** + * Initialize the GenericActivity fields. + * @throws IllegalAccessException + * @throws InstantiationException + */ + private void initialize(Class opsType) + throws InstantiationException, IllegalAccessException { + // Create the OpsType object. + mOpsInstance = opsType.newInstance(); + + // Put the OpsInstance into the RetainedFragmentManager under + // the simple name. + mRetainedFragmentManager.put(opsType.getSimpleName(), + mOpsInstance); + + // Perform the first initialization. + mOpsInstance.onConfiguration(this, + true); + } + + /** + * Return the initialized OpsType instance for use by the + * application. + */ + public OpsType getOps() { + return mOpsInstance; + } + + /** + * Return the initialized OpsType instance for use by the + * application. + */ + public RetainedFragmentManager getRetainedFragmentManager() { + return mRetainedFragmentManager; + } +} + diff --git a/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/common/LifecycleLoggingActivity.java b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/common/LifecycleLoggingActivity.java new file mode 100644 index 0000000..a88c1d7 --- /dev/null +++ b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/common/LifecycleLoggingActivity.java @@ -0,0 +1,137 @@ +package vandy.mooc.common; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; + +/** + * This abstract class extends the Activity class and overrides lifecycle + * callbacks for logging various lifecycle events. + */ +public abstract class LifecycleLoggingActivity extends Activity { + /** + * Debugging tag used by the Android logger. + */ + protected final String TAG = getClass().getSimpleName(); + + /** + * Hook method called when a new instance of Activity is created. One time + * initialization code should go here e.g. UI layout, some class scope + * variable initialization. if finish() is called from onCreate no other + * lifecycle callbacks are called except for onDestroy(). + * + * @param savedInstanceState + * object that contains saved state information. + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + // Always call super class for necessary + // initialization/implementation. + super.onCreate(savedInstanceState); + + if (savedInstanceState != null) { + // The activity is being re-created. Use the + // savedInstanceState bundle for initializations either + // during onCreate or onRestoreInstanceState(). + Log.d(TAG, "onCreate(): activity re-created"); + + } else { + // Activity is being created anew. No prior saved + // instance state information available in Bundle object. + Log.d(TAG, "onCreate(): activity created anew"); + } + + } + + /** + * Hook method called after onCreate() or after onRestart() (when the + * activity is being restarted from stopped state). Should re-acquire + * resources relinquished when activity was stopped (onStop()) or acquire + * those resources for the first time after onCreate(). + */ + @Override + protected void onStart() { + // Always call super class for necessary + // initialization/implementation. + super.onStart(); + Log.d(TAG, "onStart() - the activity is about to become visible"); + } + + /** + * Hook method called after onRestoreStateInstance(Bundle) only if there is + * a prior saved instance state in Bundle object. onResume() is called + * immediately after onStart(). onResume() is called when user resumes + * activity from paused state (onPause()) User can begin interacting with + * activity. Place to start animations, acquire exclusive resources, such as + * the camera. + */ + @Override + protected void onResume() { + // Always call super class for necessary + // initialization/implementation and then log which lifecycle + // hook method is being called. + super.onResume(); + Log.d(TAG, + "onResume() - the activity has become visible (it is now \"resumed\")"); + } + + /** + * Hook method called when an Activity loses focus but is still visible in + * background. May be followed by onStop() or onResume(). Delegate more CPU + * intensive operation to onStop for seamless transition to next activity. + * Save persistent state (onSaveInstanceState()) in case app is killed. + * Often used to release exclusive resources. + */ + @Override + protected void onPause() { + // Always call super class for necessary + // initialization/implementation and then log which lifecycle + // hook method is being called. + super.onPause(); + Log.d(TAG, + "onPause() - another activity is taking focus (this activity is about to be \"paused\")"); + } + + /** + * Called when Activity is no longer visible. Release resources that may + * cause memory leak. Save instance state (onSaveInstanceState()) in case + * activity is killed. + */ + @Override + protected void onStop() { + // Always call super class for necessary + // initialization/implementation and then log which lifecycle + // hook method is being called. + super.onStop(); + Log.d(TAG, + "onStop() - the activity is no longer visible (it is now \"stopped\")"); + } + + /** + * Hook method called when user restarts a stopped activity. Is followed by + * a call to onStart() and onResume(). + */ + @Override + protected void onRestart() { + // Always call super class for necessary + // initialization/implementation and then log which lifecycle + // hook method is being called. + super.onRestart(); + Log.d(TAG, "onRestart() - the activity is about to be restarted()"); + } + + /** + * Hook method that gives a final chance to release resources and + * stop spawned threads. This method may not always be called + * when the Android system kills the hosting process. + */ + @Override + protected void onDestroy() { + // Always call super class for necessary + // initialization/implementation and then log which lifecycle + // hook method is being called. + super.onDestroy(); + Log.d(TAG, "onDestroy() - the activity is about to be destroyed"); + } + +} diff --git a/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/common/RetainedFragmentManager.java b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/common/RetainedFragmentManager.java new file mode 100644 index 0000000..fed975d --- /dev/null +++ b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/common/RetainedFragmentManager.java @@ -0,0 +1,164 @@ +package vandy.mooc.common; + +import android.app.Activity; +import android.app.Fragment; +import android.app.FragmentManager; +import android.os.Bundle; +import android.util.Log; + +import java.lang.ref.WeakReference; +import java.util.HashMap; + +/** + * Retains and manages state information between runtime configuration changes + * to an Activity. Plays the role of the "Originator" in the Memento pattern. + */ +public class RetainedFragmentManager { + /** + * Debugging tag used by the Android logger. + */ + protected final String TAG = getClass().getSimpleName(); + + /** + * Name used to identify the RetainedFragment. + */ + private final String mRetainedFragmentTag; + + /** + * WeakReference to the FragmentManager. + */ + private final WeakReference mFragmentManager; + + /** + * Reference to the RetainedFragment. + */ + private RetainedFragment mRetainedFragment; + + /** + * Constructor initializes fields. + */ + public RetainedFragmentManager(FragmentManager fragmentManager, + String retainedFragmentTag) { + // Store a WeakReference to the Activity. + mFragmentManager = new WeakReference(fragmentManager); + + // Store the tag used to identify the RetainedFragment. + mRetainedFragmentTag = retainedFragmentTag; + } + + /** + * Initializes the RetainedFragment the first time it's called. + * + * @returns true if it's first time the method's been called, else false. + */ + public boolean firstTimeIn() { + try { + // Find the RetainedFragment on Activity restarts. The + // RetainedFragment has no UI so it must be referenced via + // a tag. + mRetainedFragment = (RetainedFragment) mFragmentManager.get() + .findFragmentByTag(mRetainedFragmentTag); + + // A value of null means it's the first time in, so there's + // extra work to do. + if (mRetainedFragment == null) { + Log.d(TAG, "Creating new RetainedFragment " + + mRetainedFragmentTag); + + // Create a new RetainedFragment. + mRetainedFragment = new RetainedFragment(); + + // Commit this RetainedFragment to the FragmentManager. + mFragmentManager.get().beginTransaction() + .add(mRetainedFragment, mRetainedFragmentTag).commit(); + return true; + } + // A value of non-null means it's not first time in. + else { + Log.d(TAG, "Returning existing RetainedFragment " + + mRetainedFragmentTag); + return false; + } + } catch (NullPointerException e) { + Log.d(TAG, "NPE in firstTimeIn()"); + return false; + } + } + + /** + * Add the @a object with the @a key. + */ + public void put(String key, Object object) { + mRetainedFragment.put(key, object); + } + + /** + * Add the @a object with its class name. + */ + public void put(Object object) { + put(object.getClass().getName(), object); + } + + /** + * Get the object with @a key. + */ + @SuppressWarnings("unchecked") + public T get(String key) { + return (T) mRetainedFragment.get(key); + } + + /** + * Return the Activity the RetainedFragment is attached to or null if it's + * not currently attached. + */ + public Activity getActivity() { + return mRetainedFragment.getActivity(); + } + + /** + * "Headless" Fragment that retains state information between configuration + * changes. Plays the role of the "Memento" in the Memento pattern. + */ + public static class RetainedFragment extends Fragment { + /** + * Maps keys to objects. + */ + private HashMap mData = new HashMap(); + + /** + * Hook method called when a new instance of Fragment is created. + * + * @param savedInstanceState + * object that contains saved state information. + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Ensure the data survives runtime configuration changes. + setRetainInstance(true); + } + + /** + * Add the @a object with the @a key. + */ + public void put(String key, Object object) { + mData.put(key, object); + } + + /** + * Add the @a object with its class name. + */ + public void put(Object object) { + put(object.getClass().getName(), object); + } + + /** + * Get the object with @a key. + */ + @SuppressWarnings("unchecked") + public T get(String key) { + return (T) mData.get(key); + } + } +} diff --git a/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/operations/HobbitOps.java b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/operations/HobbitOps.java new file mode 100644 index 0000000..a972c50 --- /dev/null +++ b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/operations/HobbitOps.java @@ -0,0 +1,188 @@ +package vandy.mooc.operations; + +import android.app.Activity; +import android.net.Uri; +import android.os.RemoteException; +import android.widget.SimpleCursorAdapter; + +import vandy.mooc.common.ConfigurableOps; + +/** + * Class that defines operations for inserting, querying, updating, + * and deleting characters from the HobbitContentProvider. This class + * plays the role of the "Abstraction" in the Bridge pattern. It + * implements ConfigurableOps so it can be managed by the + * GenericActivity framework. This class and the hierarchy it + * abstracts play the role of the "Presenter" in the + * Model-View-Presenter pattern. + */ +public class HobbitOps implements ConfigurableOps { + /** + * Means for accessing the ContentProvider (i.e., CONTENT_RESOLVER + * or CONTENT_PROVIDER_CLIENT) for the HobbitOps implementation. + */ + public enum ContentProviderAccessMeans { + CONTENT_RESOLVER, + CONTENT_PROVIDER_CLIENT + } + + /** + * innitialize the access means to content resolver + */ + private ContentProviderAccessMeans mAccessMeans = + ContentProviderAccessMeans.CONTENT_RESOLVER; + + /** + * Reference to the designed Concrete Implementor (i.e., either + * HobbitOpsContentResolver or HobbitOpsContentProviderClient). + */ + private HobbitOpsImpl mHobbitOpsImpl; + + /** + * This default constructor must be public for the GenericOps + * class to work properly. + */ + public HobbitOps() { + accessMeansImplTrigger();//pick the default implementation of the implementation for the access means + + } + + private void accessMeansImplTrigger() { + // Select the appropriate means of accessing the Content + // Provider. + switch(mAccessMeans) { + case CONTENT_RESOLVER: + mHobbitOpsImpl = + new HobbitOpsContentResolver();//at the first time, the default will be the content resolver + break; + case CONTENT_PROVIDER_CLIENT: + mHobbitOpsImpl = + new HobbitOpsContentProviderClient(); + break; + } + } + + /** + * Hook method dispatched by the GenericActivity framework to + * initialize the HobbitOps object after it's been created. + * + * @param activity The currently active Activity. + * @param firstTimeIn Set to "true" if this is the first time the + * Ops class is initialized, else set to + * "false" if called after a runtime + * configuration change. + */ + @Override + public void onConfiguration(Activity activity, + boolean firstTimeIn) { + mHobbitOpsImpl.onConfiguration(activity, + firstTimeIn); + } + + /** + * Release resources to prevent leaks. + */ + public void close() { + mHobbitOpsImpl.close(); + } + + /** + * Return a @a SimpleCursorAdapter that can be used to display the + * contents of the Hobbit ContentProvider. + * it uses factory method pattern under the hood + */ + public SimpleCursorAdapter makeCursorAdapter() { + return mHobbitOpsImpl.makeCursorAdapter(); + } + + /** + * Insert a Hobbit @a character of a particular @a race into the + * HobbitContentProvider. + */ + public Uri insert(String character, + String race) throws RemoteException { + return mHobbitOpsImpl.insert(character, + race); + } + + /** + * Insert an array of Hobbit @a characters of a particular @a race + * into the HobbitContentProvider. + */ + public int bulkInsert(String[] characters, + String race) throws RemoteException { + return mHobbitOpsImpl.bulkInsert(characters, + race); + } + + /** + * Update the @a name and @a race of a Hobbit character at a designated + * @a uri from the HobbitContentProvider. + */ + public int updateByUri(Uri uri, + String name, + String race) throws RemoteException { + return mHobbitOpsImpl.updateByUri(uri, + name, + race); + } + + /** + * Update the @a race of a Hobbit character with the given @a + * name. + */ + public int updateRaceByName(String name, + String race) throws RemoteException { + return mHobbitOpsImpl.updateRaceByName(name, + race); + } + + /** + * Delete an array of Hobbit @a characterNames from the + * HobbitContentProvider. + */ + public int deleteByName(String[] characterNames) + throws RemoteException { + return mHobbitOpsImpl.deleteByName(characterNames); + } + + /** + * Delete an array of Hobbit @a characterRaces from the + * HobbitContentProvider. + */ + public int deleteByRace(String[] characterRaces) + throws RemoteException { + return mHobbitOpsImpl.deleteByRace(characterRaces); + } + + /** + * Delete all characters in the HobbitContentProvider. + */ + public int deleteAll() + throws RemoteException { + return mHobbitOpsImpl.deleteAll(); + } + + /** + * Display all the current contents of the HobbitContentProvider. + */ + public void displayAll() + throws RemoteException { + mHobbitOpsImpl.displayAll(); + } + + /** + * Sets the means for accessing the ContentProvider (i.e., + * CONTENT_RESOLVER or CONTENT_PROVIDER_CLIENT) for the HobbitOps + * implementation. + */ + public void setContentProviderAccessMeans(ContentProviderAccessMeans accessMeans) { + if(this.mAccessMeans!=accessMeans){ + mAccessMeans = accessMeans; + accessMeansImplTrigger();//trigger to change the implementation fo the content provider based on the access Means + + } + System.out.println("name of hobit ops impl"+mHobbitOpsImpl.getClass().getSimpleName()); + + } +} diff --git a/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/operations/HobbitOpsContentProviderClient.java b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/operations/HobbitOpsContentProviderClient.java new file mode 100644 index 0000000..f7bb2f8 --- /dev/null +++ b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/operations/HobbitOpsContentProviderClient.java @@ -0,0 +1,137 @@ +package vandy.mooc.operations; + +import vandy.mooc.provider.CharacterContract; +import android.app.Activity; +import android.content.ContentProviderClient; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.os.RemoteException; + +/** + * Class that uses a ContentProviderClient to insert, query, update, + * and delete characters from the HobbitContentProvider. This class + * plays the role of the "Concrete Implementor" in the Bridge pattern + * and the "Concrete Class" in the TemplateMethod pattern. It's also + * an example of the "External Polymorphism" pattern. + */ +public class HobbitOpsContentProviderClient + extends HobbitOpsImpl { + /** + * Define an optimized Proxy for accessing the + * HobbitContentProvider. + */ + private ContentProviderClient mCpc; + + /** + * Hook method dispatched by the GenericActivity framework to + * initialize the HobbitOpsContentProviderClient object after it's + * been created. + * + * @param activity The currently active Activity. + * @param firstTimeIn Set to "true" if this is the first time the + * Ops class is initialized, else set to + * "false" if called after a runtime + * configuration change. + */ + @Override + public void onConfiguration(Activity activity, + boolean firstTimeIn) { + super.onConfiguration(activity, + firstTimeIn); + + if (firstTimeIn) { + // Get this Application context's ContentResolver. + ContentResolver cr = + activity.getApplicationContext().getContentResolver(); + + // Get the ContentProviderClient associated with this + // ContentResolver. + mCpc = cr.acquireContentProviderClient + (CharacterContract.CharacterEntry.CONTENT_URI); + } + } + + /** + * Release the ContentProviderClient to prevent leaks. + */ + public void close() { + mCpc.release(); + } + + /** + * Insert @a ContentValues into the HobbitContentProvider at + * the @a uri. Plays the role of an "concrete hook method" in the + * Template Method pattern. + */ + public Uri insert(Uri uri, + ContentValues cvs) + throws RemoteException { + return mCpc.insert(uri, + cvs); + } + + /** + * Insert an array of @a ContentValues into the + * HobbitContentProvider at the @a uri. Plays the role of an + * "concrete hook method" in the Template Method pattern. + */ + protected int bulkInsert(Uri uri, + ContentValues[] cvsArray) + throws RemoteException { + return mCpc.bulkInsert(uri, + cvsArray); + } + + /** + * Return a Cursor from a query on the HobbitContentProvider at + * the @a uri. Plays the role of an "concrete hook method" in the + * Template Method pattern. + */ + public Cursor query(Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String sortOrder) + throws RemoteException { + // Query for all the characters in the HobbitContentProvider. + return mCpc.query(uri, + projection, + selection, + selectionArgs, + sortOrder); + } + + /** + * Delete the @a selection and @a selectionArgs with the @a + * ContentValues in the HobbitContentProvider at the @a uri. + * Plays the role of an "concrete hook method" in the Template + * Method pattern. + */ + public int update(Uri uri, + ContentValues cvs, + String selection, + String[] selectionArgs) + throws RemoteException { + return mCpc.update(uri, + cvs, + selection, + selectionArgs); + } + + /** + * Delete the @a selection and @a selectionArgs from the + * HobbitContentProvider at the @a uri. Plays the role of an + * "concrete hook method" in the Template Method pattern. + */ + protected int delete(Uri uri, + String selection, + String[] selectionArgs) + throws RemoteException { + return mCpc.delete + (uri, + selection, + selectionArgs); + } +} diff --git a/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/operations/HobbitOpsContentResolver.java b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/operations/HobbitOpsContentResolver.java new file mode 100644 index 0000000..519500f --- /dev/null +++ b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/operations/HobbitOpsContentResolver.java @@ -0,0 +1,119 @@ +package vandy.mooc.operations; + +import android.app.Activity; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.os.RemoteException; + +/** + * Class that uses a ContentResolver to insert, query, update, and + * delete characters from the HobbitContentProvider. This class plays + * the role of the "Concrete Implementor" in the Bridge pattern and + * the "Concrete Class" in the TemplateMethod pattern. It's also an + * example of the "External Polymorphism" pattern. + */ +public class HobbitOpsContentResolver + extends HobbitOpsImpl { + /** + * Define the Proxy for accessing the HobbitContentProvider. + */ + private ContentResolver mCr; + + /** + * Hook method dispatched by the GenericActivity framework to + * initialize the HobbitOpsContentProviderClient object after it's + * been created. + * + * @param activity The currently active Activity. + * @param firstTimeIn Set to "true" if this is the first time the + * Ops class is initialized, else set to + * "false" if called after a runtime + * configuration change. + */ + @Override + public void onConfiguration(Activity activity, + boolean firstTimeIn) { + super.onConfiguration(activity, + firstTimeIn); + + if (firstTimeIn) + // Store the Application context's ContentResolver. + mCr = + activity.getApplicationContext().getContentResolver(); + } + + /** + * Insert @a ContentValues into the HobbitContentProvider at + * the @a uri. Plays the role of an "concrete hook method" in the + * Template Method pattern. + */ + public Uri insert(Uri uri, ContentValues cvs) throws RemoteException { + return mCr.insert(uri, + cvs); + } + + /** + * Insert an array of @a ContentValues into the + * HobbitContentProvider at the @a uri. Plays the role of an + * "concrete hook method" in the Template Method pattern. + */ + protected int bulkInsert(Uri uri, + ContentValues[] cvsArray) + throws RemoteException { + return mCr.bulkInsert(uri, + cvsArray); + } + + /** + * Return a Cursor from a query on the HobbitContentProvider at + * the @a uri. Plays the role of an "concrete hook method" in the + * Template Method pattern. + */ + public Cursor query(Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String sortOrder) + throws RemoteException { + // Query for all the characters in the HobbitContentProvider. + return mCr.query(uri, + projection, + selection, + selectionArgs, + sortOrder); + } + + /** + * Update the @ selection and @a selectionArgs with the @a + * ContentValues in the HobbitContentProvider at the @a uri. + * Plays the role of an "concrete hook method" in the Template + * Method pattern. + */ + public int update(Uri uri, + ContentValues cvs, + String selection, + String[] selectionArgs) + throws RemoteException { + return mCr.update(uri, + cvs, + selection, + selectionArgs); + } + + /** + * Delete the @a selection and @a selectionArgs from the + * HobbitContentProvider at the @a uri. Plays the role of an + * "concrete hook method" in the Template Method pattern. + */ + protected int delete(Uri uri, + String selection, + String[] selectionArgs) + throws RemoteException { + return mCr.delete + (uri, + selection, + selectionArgs); + } +} diff --git a/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/operations/HobbitOpsImpl.java b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/operations/HobbitOpsImpl.java new file mode 100644 index 0000000..82cbbc6 --- /dev/null +++ b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/operations/HobbitOpsImpl.java @@ -0,0 +1,299 @@ +package vandy.mooc.operations; + +import android.app.Activity; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.os.RemoteException; +import android.widget.SimpleCursorAdapter; +import android.widget.Toast; + +import java.lang.ref.WeakReference; + +import vandy.mooc.R; +import vandy.mooc.activities.HobbitActivity; +import vandy.mooc.provider.CharacterContract; + +/** + * Class that implements the operations for inserting, querying, + * updating, and deleting characters from the HobbitContentProvider. + * This class plays the role of the "Implementor" in the Bridge + * pattern and the "Abstract Class" in the Template Method pattern. + * It's also an example of the "External Polymorphism" pattern. + */ +public abstract class HobbitOpsImpl { + /** + * Debugging tag used by the Android logger. + */ + protected final static String TAG = + HobbitOpsImpl.class.getSimpleName(); + + /** + * Stores a Weak Reference to the HobbitActivity so the garbage + * collector can remove it when it's not in use. + */ + protected WeakReference mActivity; + + /** + * Contains the most recent result from a query so the display can + * be updated after a runtime configuration change.uses cursor for the purpose + */ + private Cursor mCursor; + + /** + * Hook method dispatched by the GenericActivity framework to + * initialize the HobbitOpsImpl object after it's been created. + * + * @param activity The currently active Activity. + * @param firstTimeIn Set to "true" if this is the first time the + * Ops class is initialized, else set to + * "false" if called after a runtime + * configuration change. + */ + + public void onConfiguration(Activity activity, + boolean firstTimeIn) { + // Create a WeakReference to the activity. + mActivity = new WeakReference<>((HobbitActivity) activity); + + if (firstTimeIn == false + && mCursor != null) + // Redisplay the contents of the cursor after a runtime + // configuration change. + mActivity.get().displayCursor(mCursor); + } + + /** + * Release resources to prevent leaks. + */ + public void close() { + // No-op. + } + + /** + * Return a @a SimpleCursorAdapter that can be used to display the + * contents of the Hobbit ContentProvider. + */ + public SimpleCursorAdapter makeCursorAdapter() { + return new SimpleCursorAdapter + (mActivity.get(), + R.layout.list_layout, + null, + CharacterContract.CharacterEntry.sColumnsToDisplay, + CharacterContract.CharacterEntry.sColumnResIds, + 1); + } + + /** + * Insert a Hobbit @a character of a particular @a race into the + * HobbitContentProvider. Plays the role of a "template method" + * in the Template Method pattern. + */ + public Uri insert(String character, + String race) throws RemoteException { + final ContentValues cvs = new ContentValues(); + + // Insert data. + cvs.put(CharacterContract.CharacterEntry.COLUMN_NAME, + character); + cvs.put(CharacterContract.CharacterEntry.COLUMN_RACE, + race); + + // Call to the hook method. + return insert(CharacterContract.CharacterEntry.CONTENT_URI, + cvs); + } + + /** + * Insert @a ContentValues into the HobbitContentProvider at + * the @a uri. Plays the role of an "abstract hook method" in the + * Template Method pattern. + */ + protected abstract Uri insert(Uri uri, + ContentValues cvs) + throws RemoteException; + + /** + * Insert an array of Hobbit @a characters of a particular @a race + * into the HobbitContentProvider. Plays the role of a "template + * method" in the Template Method pattern. + */ + public int bulkInsert(String[] characters, + String race) throws RemoteException { + // Use ContentValues to store the values in appropriate + // columns, so that ContentResolver can process it. Since + // more than one rows needs to be inserted, an Array of + // ContentValues is needed. + ContentValues[] cvsArray = + new ContentValues[characters.length]; + + // Index counter. + int i = 0; + + // Insert all the characters into the ContentValues array. + for (String character : characters) { + ContentValues cvs = new ContentValues(); + cvs.put(CharacterContract.CharacterEntry.COLUMN_NAME, + character); + cvs.put(CharacterContract.CharacterEntry.COLUMN_RACE, + race); + cvsArray[i++] = cvs; + } + + return bulkInsert + (CharacterContract.CharacterEntry.CONTENT_URI, + cvsArray); + } + + /** + * Insert an array of @a ContentValues into the + * HobbitContentProvider at the @a uri. Plays the role of an + * "abstract hook method" in the Template Method pattern. + */ + protected abstract int bulkInsert(Uri uri, + ContentValues[] cvsArray) + throws RemoteException; + + /** + * Return a Cursor from a query on the HobbitContentProvider at + * the @a uri. Plays the role of an "abstract hook method" in the + * Template Method pattern. + */ + public abstract Cursor query(Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String sortOrder) + throws RemoteException; + + /** + * Update the @a name and @a race of a Hobbit character at a + * designated @a uri from the HobbitContentProvider. Plays the + * role of a "template method" in the Template Method pattern. + */ + public int updateByUri(Uri uri, + String name, + String race) throws RemoteException { + final ContentValues cvs = new ContentValues(); + cvs.put(CharacterContract.CharacterEntry.COLUMN_NAME, + name); + cvs.put(CharacterContract.CharacterEntry.COLUMN_RACE, + race); + return update(uri, + cvs, + null, + null); + } + + /** + * Update the @a race of a Hobbit character with a given + * @a name in the HobbitContentProvider. Plays the role of a + * "template method" in the Template Method pattern. + */ + public int updateRaceByName(String name, + String race) throws RemoteException { + final ContentValues cvs = new ContentValues(); + cvs.put(CharacterContract.CharacterEntry.COLUMN_NAME, + name); + cvs.put(CharacterContract.CharacterEntry.COLUMN_RACE, + race); + return update(CharacterContract.CharacterEntry.CONTENT_URI, + cvs, + CharacterContract.CharacterEntry.COLUMN_NAME, + new String[] { name }); + } + + /** + * Delete the @a selection and @a selectionArgs with the @a + * ContentValues in the HobbitContentProvider at the @a uri. + * Plays the role of an "abstract hook method" in the Template + * Method pattern. + */ + public abstract int update(Uri uri, + ContentValues cvs, + String selection, + String[] selectionArgs) + throws RemoteException; + + /** + * Delete an array of Hobbit @a characterNames from the + * HobbitContentProvider. Plays the role of a "template method" + * in the Template Method pattern. + */ + public int deleteByName(String[] characterNames) + throws RemoteException { + return delete(CharacterContract.CharacterEntry.CONTENT_URI, + CharacterContract.CharacterEntry.COLUMN_NAME, + characterNames); + } + + /** + * Delete an array of Hobbit @a characterRaces from the + * HobbitContentProvider. Plays the role of a "template method" + * in the Template Method pattern. + */ + public int deleteByRace(String[] characterRaces) + throws RemoteException { + return delete(CharacterContract.CharacterEntry.CONTENT_URI, + CharacterContract.CharacterEntry.COLUMN_RACE, + characterRaces); + } + + /** + * Delete the @a selection and @a selectionArgs from the + * HobbitContentProvider at the @a uri. Plays the role of an + * "abstract hook method" in the Template Method pattern. + */ + protected abstract int delete(Uri uri, + String selection, + String[] selectionArgs) + throws RemoteException; + + /** + * Delete all characters from the HobbitContentProvider. Plays + * the role of a "template method" in the Template Method pattern. + */ + public int deleteAll() + throws RemoteException { + return delete(CharacterContract.CharacterEntry.CONTENT_URI, + null, + null); + } + + /** + * Display the current contents of the HobbitContentProvider. + */ + public void displayAll() + throws RemoteException { + // Query for all the characters in the HobbitContentProvider. + mCursor = query(CharacterContract.CharacterEntry.CONTENT_URI, + CharacterContract.CharacterEntry.sColumnsToDisplay, + CharacterContract.CharacterEntry.COLUMN_RACE, + new String[] { + "Dwarf", + "Maia", + "Hobbit", + "Dragon", + "Man", + "Bear" + }, + /* The following three null parameters could + also be this: + + null, + null, + null, + */ + null); + if (mCursor.getCount() == 0) { + Toast.makeText(mActivity.get(), + "No items to display", + Toast.LENGTH_SHORT).show(); + // Remove the display if there's nothing left to show. + mActivity.get().displayCursor (mCursor = null); + } else + // Display the results of the query. + mActivity.get().displayCursor + (mCursor); + } +} diff --git a/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/provider/CharacterContract.java b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/provider/CharacterContract.java new file mode 100644 index 0000000..b6db323 --- /dev/null +++ b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/provider/CharacterContract.java @@ -0,0 +1,163 @@ +package vandy.mooc.provider; + +import android.content.ContentUris; +import android.content.UriMatcher; +import android.net.Uri; +import android.provider.BaseColumns; + +import vandy.mooc.R; + +/** + * This contract defines the metadata for the HobbitContentProvider, + * including the provider's access URIs and its "database" constants. + */ +public final class CharacterContract { + /** + * This ContentProvider's unique identifier. + */ + public static final String CONTENT_AUTHORITY = + "vandy.mooc.hobbitprovider"; + + /** + * Use CONTENT_AUTHORITY to create the base of all URI's which + * apps will use to contact the content provider. + */ + public static final Uri BASE_CONTENT_URI = + Uri.parse("content://" + + CONTENT_AUTHORITY); + + /** + * Possible paths (appended to base content URI for possible + * URI's). For instance, content://vandy.mooc/character_table/ is + * a valid path for looking at Character data. Conversely, + * content://vandy.mooc/givemeroot/ will fail, as the + * ContentProvider hasn't been given any information on what to do + * with "givemeroot". + */ + public static final String PATH_CHARACTER = + CharacterEntry.TABLE_NAME; + + /* + * Columns + */ + + /** + * Inner class that defines the table contents of the Hobbit + * table. + */ + public static final class CharacterEntry implements BaseColumns { + /** + * Use BASE_CONTENT_URI to create the unique URI for Acronym + * Table that apps will use to contact the content provider. + */ + public static final Uri CONTENT_URI = + BASE_CONTENT_URI.buildUpon() + .appendPath(PATH_CHARACTER).build();// create a path of this form content://vandy.mooc/character_table/ + + /** + * When the Cursor returned for a given URI by the + * ContentProvider contains 0..x items. + */ + public static final String CONTENT_ITEMS_TYPE = + "vnd.android.cursor.dir/" + + CONTENT_AUTHORITY + + "/" + + PATH_CHARACTER; + + /** + * When the Cursor returned for a given URI by the + * ContentProvider contains 1 item. + */ + public static final String CONTENT_ITEM_TYPE = + "vnd.android.cursor.item/" + + CONTENT_AUTHORITY + + "/" + + PATH_CHARACTER; + + /** + * Columns to display. + */ + public static final String sColumnsToDisplay [] = + new String[] { + CharacterContract.CharacterEntry._ID, + CharacterContract.CharacterEntry.COLUMN_NAME, + CharacterContract.CharacterEntry.COLUMN_RACE + }; + + /** + * Resource Ids of the columns to display. + */ + public static final int[] sColumnResIds = + new int[] { + R.id.idString, + R.id.name, + R.id.race + }; + + /** + * Name of the database table. + */ + public static final String TABLE_NAME = "character_table"; + + /** + * Columns to store data. + */ + public static final String COLUMN_NAME = "name"; + public static final String COLUMN_RACE = "race"; + + /** + * Return a Uri that points to the row containing a given id. + * + * @param id + * @return Uri + */ + public static Uri buildUri(Long id) { + return ContentUris.withAppendedId(CONTENT_URI, + id); + } + } + + /** + * The code that is returned when a URI for more than 1 items is + * matched against the given components. Must be positive. + */ + public static final int CHARACTERS = 100; + + /** + * The code that is returned when a URI for exactly 1 item is + * matched against the given components. Must be positive. + */ + public static final int CHARACTER = 101; + + /** + * The URI Matcher used by this content provider. + */ + public static final UriMatcher sUriMatcher = + buildUriMatcher(); + + /** + * Helper method to match each URI to the ACRONYM integers + * constant defined above. + * + * @return UriMatcher + */ + protected static UriMatcher buildUriMatcher() { + // All paths added to the UriMatcher have a corresponding code + // to return when a match is found. The code passed into the + // constructor represents the code to return for the rootURI. + // It's common to use NO_MATCH as the code for this case. + final UriMatcher matcher = + new UriMatcher(UriMatcher.NO_MATCH);//default consttructor can also be used + + // For each type of URI that is added, a corresponding code is + // created. + matcher.addURI(CharacterContract.CONTENT_AUTHORITY, + CharacterContract.PATH_CHARACTER, + CHARACTERS); + matcher.addURI(CharacterContract.CONTENT_AUTHORITY, + CharacterContract.PATH_CHARACTER + + "/#", + CHARACTER); + return matcher; + } +} diff --git a/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/provider/CharacterRecord.java b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/provider/CharacterRecord.java new file mode 100644 index 0000000..c6b4dbc --- /dev/null +++ b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/provider/CharacterRecord.java @@ -0,0 +1,75 @@ +package vandy.mooc.provider; + +/** + * A simple POJO that stores information about Hobbit characters. + */ +class CharacterRecord { + /** + * Start at 0. + */ + static long sInitialId = 0; + + /** + * Id of the character in the map. + */ + private final long mId; + + /** + * Name of the character. + */ + private final String mName; + + /** + * Race of the character. + */ + private String mRace; + + /** + * Constructor initializes all the name and race fields. + */ + CharacterRecord(String name, + String race) { + mName = name; + mRace = race; + mId = ++sInitialId; + } + + /** + * Constructor initializes all the fields. + */ + CharacterRecord(long id, + String name, + String race) { + mId = id; + mName = name; + mRace = race; + } + + /** + * @return the id of the character. + */ + long getId() { + return mId; + } + + /** + * @return the name of the character. + */ + String getName() { + return mName; + } + + /** + * @return the race of the character. + */ + String getRace() { + return mRace; + } + + /** + * Set the race of the character. + */ + void setRace(String race) { + mRace = race; + } +} diff --git a/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/provider/HobbitDatabaseHelper.java b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/provider/HobbitDatabaseHelper.java new file mode 100644 index 0000000..27030b5 --- /dev/null +++ b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/provider/HobbitDatabaseHelper.java @@ -0,0 +1,81 @@ +package vandy.mooc.provider; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +import java.io.File; + +/** + * The database helper used by the Hobbit Content Provider to create + * and manage its underling database. + */ +public class HobbitDatabaseHelper extends SQLiteOpenHelper { + /** + * Database name. + */ + private static String DATABASE_NAME = + "vandy_mooc_hobbit_db"; + + /** + * Database version number, which is updated with each schema + * change. + */ + private static int DATABASE_VERSION = 1; + + /* + * SQL create table statements. + */ + + /** + * SQL statement used to create the Hobbit table. + */ + final String SQL_CREATE_HOBBIT_TABLE = + "CREATE TABLE " + + CharacterContract.CharacterEntry.TABLE_NAME + " (" + + CharacterContract.CharacterEntry._ID + " INTEGER PRIMARY KEY AUTO INCREMENT, " + + CharacterContract.CharacterEntry.COLUMN_NAME + " TEXT NOT NULL, " + + CharacterContract.CharacterEntry.COLUMN_RACE + " TEXT NOT NULL " + + " );"; + + /** + * Constructor - initialize database name and version, but don't + * actually construct the database (which is done in the + * onCreate() hook method). It places the database in the + * application's cache directory, which will be automatically + * cleaned up by Android if the device runs low on storage space. + * + * @param context + */ + public HobbitDatabaseHelper(Context context) { + super(context, + context.getCacheDir() + + File.separator + + DATABASE_NAME, + null, + DATABASE_VERSION); + } + + /** + * Hook method called when the database is created. + */ + @Override + public void onCreate(SQLiteDatabase db) { + // Create the table. + db.execSQL(SQL_CREATE_HOBBIT_TABLE); + } + + /** + * Hook method called when the database is upgraded. + */ + @Override + public void onUpgrade(SQLiteDatabase db, + int oldVersion, + int newVersion) { + // Delete the existing tables. + db.execSQL("DROP TABLE IF EXISTS " + + CharacterContract.CharacterEntry.TABLE_NAME); + // Create the new tables. + onCreate(db); + } +} diff --git a/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/provider/HobbitProvider.java b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/provider/HobbitProvider.java new file mode 100644 index 0000000..62208c0 --- /dev/null +++ b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/provider/HobbitProvider.java @@ -0,0 +1,145 @@ +package vandy.mooc.provider; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; + +/** + * Content Provider interface used to manage Hobbit characters. This + * class plays the role of the "Abstraction" in the Bridge pattern. + * It and the hierarchy it abstracts play the role of the "Model" in + * the Model-View-Presenter pattern. + */ +public class HobbitProvider extends ContentProvider { + /** + * Debugging tag used by the Android logger. + */ + protected final static String TAG = + HobbitProvider.class.getSimpleName(); + + /** + * Different concrete implementations supported by the Hobbit + * ContentProvider. + */ + public enum ContentProviderType { + HASH_MAP, + SQLITE + } + + /** + * Stores the concrete implementation used by the Hobbit + * ContentProvider. + */ + private ContentProviderType mContentProviderType = + ContentProviderType.SQLITE; + // ContentProviderType.HASH_MAP; + + /** + * Implementation of the HobbitProvider, which is either + * HobbitProviderHashMap or HobbiProviderSQLite. + */ + private HobbitProviderImpl mImpl; + + /** + * Method called to handle type requests from client applications. + * It returns the MIME type of the data associated with each + * URI. */ + @Override + public String getType(Uri uri) { + return mImpl.getType(uri); + } + + /** + * Method called to handle insert requests from client + * applications. + */ + @Override + public Uri insert(Uri uri, + ContentValues cvs) { + return mImpl.insert(uri, cvs); + } + + /** + * Method that handles bulk insert requests. + */ + @Override + public int bulkInsert(Uri uri, + ContentValues[] cvsArray) { + return mImpl.bulkInsert(uri, cvsArray); + } + + /** + * Method called to handle query requests from client + * applications. + */ + @Override + public Cursor query(Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String sortOrder) { + return mImpl.query(uri, + projection, + selection, + selectionArgs, + sortOrder); + } + + /** + * Method called to handle update requests from client + * applications. + */ + @Override + public int update(Uri uri, + ContentValues cvs, + String selection, + String[] selectionArgs) { + return mImpl.update(uri, + cvs, + selection, + selectionArgs); + } + + /** + * Method called to handle delete requests from client + * applications. + */ + @Override + public int delete(Uri uri, + String selection, + String[] selectionArgs) { + return mImpl.delete(uri, + selection, + selectionArgs); + } + + /** + * Return true if successfully started. + */ + @Override + public boolean onCreate() { + return innitializeHobitProvider(); + + } + + private boolean innitializeHobitProvider() { + // Select the concrete implementor. + switch(mContentProviderType) { + case HASH_MAP: + mImpl = + new HobbitProviderHashMap(getContext()); + break; + case SQLITE: + mImpl = new HobbitProviderSQLite(getContext()); + break; + } + + return mImpl.onCreate();//proterly startup the provider by carring out inhouse innitialization + } + + public void setContentProviderType(ContentProviderType type){ + this.mContentProviderType=type; + innitializeHobitProvider(); + } +} diff --git a/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/provider/HobbitProviderHashMap.java b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/provider/HobbitProviderHashMap.java new file mode 100644 index 0000000..0b7dd3a --- /dev/null +++ b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/provider/HobbitProviderHashMap.java @@ -0,0 +1,329 @@ +package vandy.mooc.provider; + +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.net.Uri; + +import java.util.HashMap; + +import vandy.mooc.provider.CharacterContract.CharacterEntry; + +/** + * Content Provider implementation that uses a HashMap to manage + * Hobbit characters. This class plays the role of the "Concrete + * Implementor" in the Bridge pattern and the "Concrete Class" in the + * TemplateMethod pattern. + */ +public class HobbitProviderHashMap extends HobbitProviderImpl { + /** + * This implementation uses a simple HashMap to map IDs to + * CharacterRecords. + */ + private static final HashMap mCharacterMap = + new HashMap<>();//remember to hold lock for both insertion and deletion of objects + + /** + * Constructor initializes the super class. + */ + public HobbitProviderHashMap(Context context) { + super(context); + } + + /** + * Method called to handle insert requests from client + * applications. This plays the role of the "concrete hook + * method" in the Template Method pattern. + */ + @Override + public Uri insertCharacters(Uri uri, + ContentValues cvs) { + synchronized (this) {//hold lock to insert a + if (cvs.containsKey(CharacterEntry.COLUMN_NAME)) { + CharacterRecord rec = + new CharacterRecord(cvs.getAsString + (CharacterEntry.COLUMN_NAME), + cvs.getAsString + (CharacterEntry.COLUMN_RACE)); + mCharacterMap.put(rec.getId(), + rec); + return CharacterContract.CharacterEntry.buildUri(rec.getId()); + } else + throw new RuntimeException("Failed to insert row into " + + uri); + } + } + + /** + * Method that handles bulk insert requests. This plays the role + * of the "concrete hook method" in the Template Method pattern. + */ + @Override + public int bulkInsertCharacters(Uri uri, + ContentValues[] cvsArray) { + int returnCount = 0; + synchronized (this) { + for (ContentValues cvs : cvsArray) { + if (cvs.containsKey(CharacterEntry.COLUMN_NAME)) { + CharacterRecord rec = + new CharacterRecord(cvs.getAsString + (CharacterEntry.COLUMN_NAME), + cvs.getAsString + (CharacterEntry.COLUMN_RACE)); + mCharacterMap.put(rec.getId(), + rec); + returnCount++; + } else + throw new RuntimeException("Failed to insert row into " + + uri); + } + } + return returnCount; + } + + /** + * Method called to handle query requests from client + * applications. This plays the role of the "concrete hook + * method" in the Template Method pattern. + */ + @Override + public Cursor queryCharacters(Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String sortOrder) { + final MatrixCursor cursor = + new MatrixCursor(CharacterContract.CharacterEntry.sColumnsToDisplay); + + synchronized (this) { + // Implement a simple query mechanism for the table. + for (CharacterRecord cr : mCharacterMap.values()) + buildCursorConditionally(cursor, + cr, + selection, + selectionArgs); + } + + return cursor; + } + + /** + * Method called to handle query requests from client + * applications. This plays the role of the "concrete hook + * method" in the Template Method pattern. + */ + @Override + public Cursor queryCharacter(Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String sortOrder) { + final MatrixCursor cursor = + new MatrixCursor(CharacterContract.CharacterEntry.sColumnsToDisplay); + + // Just return a single item from the database. + long requestId = ContentUris.parseId(uri); + + synchronized (this) { + CharacterRecord cr = + mCharacterMap.get(requestId); + if (cr != null) { + buildCursorConditionally(cursor, + cr, + selection, + selectionArgs); + } + } + + return cursor; + } + + /** + * Build a MatrixCursor that matches the parameters. This plays + * the role of the "concrete hook method" in the Template Method + * pattern. + */ + private void buildCursorConditionally(MatrixCursor cursor, + CharacterRecord cr, + String selection, + String[] selectionArgs) { + if (selectionArgs == null) + cursor.addRow(new Object[] { + cr.getId(), + cr.getName(), + cr.getRace() + }); + else + for (String item : selectionArgs) + if ((selection.equals(CharacterContract.CharacterEntry.COLUMN_NAME) + && item.equals(cr.getName())) + || (selection.equals(CharacterContract.CharacterEntry.COLUMN_RACE) + && item.equals(cr.getRace()))) { + cursor.addRow(new Object[] { + cr.getId(), + cr.getName(), + cr.getRace() + }); + } + } + + /** + * Method called to handle update requests from client + * applications. This plays the role of the "concrete hook + * method" in the Template Method pattern. + */ + @Override + public int updateCharacters(Uri uri, + ContentValues cvs, + String selection, + String[] selectionArgs) { + int recsUpdated = 0; + + synchronized (this) { + // Implement a simple update mechanism for the table. + for (CharacterRecord cr : + mCharacterMap.values().toArray + (new CharacterRecord[mCharacterMap.values().size()])) + recsUpdated += + updateEntryConditionally(cr, + cvs, + selection, + selectionArgs); + } + + return recsUpdated; + } + + /** + * Method called to handle update requests from client + * applications. This plays the role of the "concrete hook + * method" in the Template Method pattern. + */ + @Override + public int updateCharacter(Uri uri, + ContentValues cvs, + String selection, + String[] selectionArgs) { + // Just update a single item in the database. + final long requestId = ContentUris.parseId(uri); + synchronized (this) { + CharacterRecord cr = mCharacterMap.get(requestId); + if (cr != null) + return updateEntryConditionally(cr, + cvs, + selection, + selectionArgs); + else + return 0; + } + } + + /** + * Update @a rec in the HashMap with the contents of the @a + * ContentValues if it matches the @a selection criteria. + */ + private int updateEntryConditionally(CharacterRecord rec, + ContentValues cvs, + String selection, + String[] selectionArgs) { + if (selectionArgs == null) { + final CharacterRecord updatedRec = + new CharacterRecord(rec.getId(), + cvs.getAsString + (CharacterEntry.COLUMN_NAME), + cvs.getAsString + (CharacterEntry.COLUMN_RACE)); + mCharacterMap.put(updatedRec.getId(), + updatedRec); + return 1; + } else + for (String character : selectionArgs) + if ((selection.equals(CharacterContract.CharacterEntry.COLUMN_NAME) + && character.equals(rec.getName())) + || (selection.equals(CharacterContract.CharacterEntry.COLUMN_RACE) + && character.equals(rec.getRace()))) { + + final CharacterRecord updatedRec = + new CharacterRecord(rec.getId(), + cvs.getAsString + (CharacterEntry.COLUMN_NAME), + cvs.getAsString + (CharacterEntry.COLUMN_RACE)); + mCharacterMap.put(updatedRec.getId(), + updatedRec); + return 1; + } + + return 0; + } + + /** + * Method called to handle delete requests from client + * applications. This plays the role of the "concrete hook + * method" in the Template Method pattern. + */ + @Override + public int deleteCharacters(Uri uri, + String selection, + String[] selectionArgs) { + int recsDeleted = 0; + + // Implement a simple delete mechanism for the table. + synchronized (this) { + for (CharacterRecord cr : + mCharacterMap.values().toArray + (new CharacterRecord[mCharacterMap.values().size()])) + recsDeleted += + deleteEntryConditionally(cr, + selection, + selectionArgs); + } + return recsDeleted; + } + /** + * Method called to handle delete requests from client + * applications. This plays the role of the "concrete hook + * method" in the Template Method pattern. + */ + @Override + public int deleteCharacter(Uri uri, + String selection, + String[] selectionArgs) { + // Just delete a single item in the database. + final long requestId = ContentUris.parseId(uri); + synchronized (this) { + CharacterRecord rec = mCharacterMap.get(requestId); + if (rec != null) + return deleteEntryConditionally(rec, + selection, + selectionArgs); + else + return 0; + } + } + + /** + * Delete @a rec from the HashMap if it matches the @a selection + * criteria. + */ + private int deleteEntryConditionally(CharacterRecord rec, + String selection, + String[] selectionArgs) { + if (selectionArgs == null) { + mCharacterMap.remove(rec.getId()); + return 1; + } + else + for (String character : selectionArgs) + if ((selection.equals(CharacterContract.CharacterEntry.COLUMN_NAME) + && character.equals(rec.getName())) + || (selection.equals(CharacterContract.CharacterEntry.COLUMN_RACE) + && character.equals(rec.getRace()))) { + mCharacterMap.remove(rec.getId()); + return 1; + } + return 0; + } +} diff --git a/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/provider/HobbitProviderImpl.java b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/provider/HobbitProviderImpl.java new file mode 100644 index 0000000..8d9d25b --- /dev/null +++ b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/provider/HobbitProviderImpl.java @@ -0,0 +1,309 @@ +package vandy.mooc.provider; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; + +/** + * Content Provider implementation used to manage Hobbit characters. + * This class plays the role of the "Implementor" in the Bridge + * pattern and the "Abstract Class" in the Template Method pattern. + */ +public abstract class HobbitProviderImpl { + /** + * Debugging tag used by the Android logger. + */ + protected final static String TAG = + HobbitProvider.class.getSimpleName(); + + /** + * Context used for various ContentResolver activities. + */ + protected Context mContext; + + /** + * Constructor initializes the Context field. + */ + public HobbitProviderImpl(Context context) { + mContext = context; + } + + /** + * Method called to handle type requests from client applications. + * It returns the MIME type of the data associated with each URI. + */ + public String getType(Uri uri) { + // Match the id returned by UriMatcher to return appropriate + // MIME_TYPE. + switch (CharacterContract.sUriMatcher.match(uri)) { + case CharacterContract.CHARACTERS: + return CharacterContract.CharacterEntry.CONTENT_ITEMS_TYPE; + case CharacterContract.CHARACTER: + return CharacterContract.CharacterEntry.CONTENT_ITEM_TYPE; + default: + throw new UnsupportedOperationException("Unknown uri: " + + uri); + } + } + + /** + * Method called to handle insert requests from client + * applications. This method plays the role of the "template + * method" in the Template Method pattern. + */ + public Uri insert(Uri uri, + ContentValues cvs) { + Uri returnUri; + + // Try to match against the path in a url. It returns the + // code for the matched node (added using addURI), or -1 if + // there is no matched node. If there's a match insert a new + // row. + switch (CharacterContract.sUriMatcher.match(uri)) { + case CharacterContract.CHARACTERS: + returnUri = insertCharacters(uri, + cvs); + break; + default: + throw new UnsupportedOperationException("Unknown uri: " + + uri); + } + + // Notifies registered observers that a row was inserted. + mContext.getContentResolver().notifyChange(uri, + null); + return returnUri; + } + + /** + * Inserts @a ContentValues into the table. This method plays the + * role of the "abstract hook method" in the Template Method pattern. + */ + protected abstract Uri insertCharacters(Uri uri, + ContentValues cvs); + + /** + * Method that handles bulk insert requests. This method plays + * the role of the "template method" in the Template Method + * pattern. + */ + public int bulkInsert(Uri uri, + ContentValues[] cvsArray) { + // Try to match against the path in a url. It returns the + // code for the matched node (added using addURI), or -1 if + // there is no matched node. If there's a match insert new + // rows. + switch (CharacterContract.sUriMatcher.match(uri)) { + case CharacterContract.CHARACTERS: + int returnCount = bulkInsertCharacters(uri, + cvsArray); + + if (returnCount > 0) + // Notifies registered observers that row(s) were + // inserted. + mContext.getContentResolver().notifyChange(uri, + null); + return returnCount; + default: + throw new UnsupportedOperationException(); + } + } + + /** + * Inserts an array of @a ContentValues into the table. This + * method plays the role of the "abstract hook method" in the + * Template Method pattern. + */ + public abstract int bulkInsertCharacters(Uri uri, + ContentValues[] cvsArray); + + /** + * Method called to handle query requests from client + * applications. This method plays the role of the "template + * method" in the Template Method pattern. + */ + public Cursor query(Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String sortOrder) { + Cursor cursor; + + // Match the id returned by UriMatcher to query appropriate + // rows. + switch (CharacterContract.sUriMatcher.match(uri)) { + case CharacterContract.CHARACTERS: + cursor = queryCharacters(uri, + projection, + selection, + selectionArgs, + sortOrder); + break; + case CharacterContract.CHARACTER: + cursor = queryCharacter(uri, + projection, + selection, + selectionArgs, + sortOrder); + break; + default: + throw new UnsupportedOperationException("Unknown uri: " + + uri); + } + + // Register to watch a content URI for changes. + cursor.setNotificationUri(mContext.getContentResolver(), + uri); + return cursor; + } + + /** + * Queries for a @a selection in the entire table, relative to + * the @a selectionArgs. This method plays the role of the + * "abstract hook method" in the Template Method pattern. + */ + public abstract Cursor queryCharacters + (Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String sortOrder); + + /** + * Queries for a @a selection in a particular row of the table, + * relative to the @a selectionArgs. This method plays the role + * of the "abstract hook method" in the Template Method pattern. + */ + public abstract Cursor queryCharacter + (Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String sortOrder); + + /** + * Method called to handle update requests from client + * applications. This method plays the role of the "template + * method" in the Template Method pattern. + */ + public int update(Uri uri, + ContentValues cvs, + String selection, + String[] selectionArgs) { + int recsUpdated; + + // Match the id returned by UriMatcher to update appropriate + // rows. + switch (CharacterContract.sUriMatcher.match(uri)) { + case CharacterContract.CHARACTERS: + recsUpdated = updateCharacters(uri, + cvs, + selection, + selectionArgs); + break; + case CharacterContract.CHARACTER: + recsUpdated = updateCharacter(uri, + cvs, + selection, + selectionArgs); + break; + default: + throw new UnsupportedOperationException("Unknown uri: " + + uri); + } + + if (recsUpdated > 0) + // Notifies registered observers that row(s) were + // inserted. + mContext.getContentResolver().notifyChange(uri, + null); + return recsUpdated; + } + + /** + * Update a @a selection in the entire table, relative to the @a + * selectionArgs. This method plays the role of the "abstract hook method" + * in the Template Method pattern. + */ + public abstract int updateCharacters + (Uri uri, + ContentValues cvs, + String selection, + String[] selectionArgs); + + /** + * Update a @a selection in a particular row of the table, + * relative to the @a selectionArgs. This method plays the role + * of the "abstract hook method" in the Template Method pattern. + */ + public abstract int updateCharacter + (Uri uri, + ContentValues cvs, + String selection, + String[] selectionArgs); + + /** + * Method called to handle delete requests from client + * applications. This method plays the role of the "template + * method" in the Template Method pattern. + */ + public int delete(Uri uri, + String selection, + String[] selectionArgs) { + int recsDeleted; + + // Try to match against the path in a url. It returns the + // code for the matched node (added using addURI) or -1 if + // there is no matched node. If a match is found delete the + // appropriate rows. + switch (CharacterContract.sUriMatcher.match(uri)) { + case CharacterContract.CHARACTERS: + recsDeleted = deleteCharacters(uri, + selection, + selectionArgs); + break; + case CharacterContract.CHARACTER: + recsDeleted = deleteCharacter(uri, + selection, + selectionArgs); + break; + default: + throw new UnsupportedOperationException("Unknown uri: " + + uri); + } + + // Notifies registered observers that row(s) were deleted. + if (selection == null + || recsDeleted != 0) + mContext.getContentResolver().notifyChange(uri, + null); + return recsDeleted; + } + + /** + * Delete a @a selection in the entire table, relative to the @a + * selectionArgs. This method plays the role of the "abstract + * hook method" in the Template Method pattern. + */ + public abstract int deleteCharacters(Uri uri, + String selection, + String[] selectionArgs); + + /** + * Delete a @a selection in a particular row of the table, + * relative to the @a selectionArgs. This method plays the role + * of the "abstract hook method" in the Template Method pattern. + */ + public abstract int deleteCharacter(Uri uri, + String selection, + String[] selectionArgs); + + /** + * Return true if successfully started. + * here you can carry out ihouse innitialization and etc + */ + public boolean onCreate() { + return true; + } +} diff --git a/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/provider/HobbitProviderSQLite.java b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/provider/HobbitProviderSQLite.java new file mode 100644 index 0000000..7d85259 --- /dev/null +++ b/ex/HobbitContentProvider/app/src/main/java/vandy/mooc/provider/HobbitProviderSQLite.java @@ -0,0 +1,298 @@ +package vandy.mooc.provider; + +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.text.TextUtils; +import android.net.Uri; +import android.util.Log; + +/** + * Content Provider implementation that uses SQLite to manage Hobbit + * characters. This class plays the role of the "Concrete + * Implementor" in the Bridge pattern and the "Concrete Class" in the + * TemplateMethod pattern. + */ +public class HobbitProviderSQLite extends HobbitProviderImpl { + /** + * Use HobbitDatabaseHelper to manage database creation and version + * management. + */ + private HobbitDatabaseHelper mOpenHelper; + + /** + * Constructor initializes the super class. + */ + public HobbitProviderSQLite(Context context) { + super(context); + } + + /** + * Return true if successfully started. + */ + public boolean onCreate() { + // Create the HobbitDatabaseHelper. + mOpenHelper = + new HobbitDatabaseHelper(mContext); + return true; + } + + /** + * Method called to handle insert requests from client + * applications. This plays the role of the "concrete hook + * method" in the Template Method pattern. + */ + @Override + public Uri insertCharacters(Uri uri, + ContentValues cvs) { + final SQLiteDatabase db = + mOpenHelper.getWritableDatabase();//this is a snchronous call and will wait to create and perform operatios + + long id = + db.insert(CharacterContract.CharacterEntry.TABLE_NAME, + null, + cvs); + + // Check if a new row is inserted or not. + if (id > 0) + return CharacterContract.CharacterEntry.buildUri(id); + else + throw new android.database.SQLException + ("Failed to insert row into " + + uri); + } + + /** + * Method that handles bulk insert requests. This plays the role + * of the "concrete hook method" in the Template Method pattern. + */ + @Override + public int bulkInsertCharacters(Uri uri, + ContentValues[] cvsArray) { + // Create and/or open a database that will be used for reading + // and writing. Once opened successfully, the database is + // cached, so you can call this method every time you need to + // write to the database. + final SQLiteDatabase db = + mOpenHelper.getWritableDatabase(); + + int returnCount = 0; + + // Begins a transaction in EXCLUSIVE mode. + db.beginTransaction(); + try { + for (ContentValues cvs : cvsArray) { + final long id = + db.insert(CharacterContract.CharacterEntry.TABLE_NAME, + null, + cvs); + if (id != -1) + returnCount++; + } + + // Marks the current transaction as successful. + db.setTransactionSuccessful(); + } finally { + // End a transaction. + db.endTransaction(); + } + return returnCount; + } + + /** + * Method called to handle query requests from client + * applications. This plays the role of the "concrete hook + * method" in the Template Method pattern. + */ + @Override + public Cursor queryCharacters(Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String sortOrder) { + // Expand the selection if necessary. + selection = addSelectionArgs(selection, + selectionArgs, + "OR"); + return mOpenHelper.getReadableDatabase().query + (CharacterContract.CharacterEntry.TABLE_NAME, + projection, + selection, + selectionArgs, + null, + null, + sortOrder); + } + + /** + * Method called to handle query requests from client + * applications. This plays the role of the "concrete hook + * method" in the Template Method pattern. + */ + @Override + public Cursor queryCharacter(Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String sortOrder) { + // Query the SQLite database for the particular rowId based on + // (a subset of) the parameters passed into the method. + return mOpenHelper.getReadableDatabase().query + (CharacterContract.CharacterEntry.TABLE_NAME, + projection, + addKeyIdCheckToWhereStatement(selection, + ContentUris.parseId(uri)), + selectionArgs, + null, + null, + sortOrder); + } + + /** + * Method called to handle update requests from client + * applications. This plays the role of the "concrete hook + * method" in the Template Method pattern. + */ + @Override + public int updateCharacters(Uri uri, + ContentValues cvs, + String selection, + String[] selectionArgs) { + // Expand the selection if necessary. + selection = addSelectionArgs(selection, + selectionArgs, + " OR "); + return mOpenHelper.getWritableDatabase().update + (CharacterContract.CharacterEntry.TABLE_NAME, + cvs, + selection, + selectionArgs); + } + + /** + * Method called to handle update requests from client + * applications. This plays the role of the "concrete hook + * method" in the Template Method pattern. + */ + @Override + public int updateCharacter(Uri uri, + ContentValues cvs, + String selection, + String[] selectionArgs) { + // Expand the selection if necessary. + selection = addSelectionArgs(selection, + selectionArgs, + " OR "); + // Just update a single row in the database. + return mOpenHelper.getWritableDatabase().update + (CharacterContract.CharacterEntry.TABLE_NAME, + cvs, + addKeyIdCheckToWhereStatement(selection, + ContentUris.parseId(uri)), + selectionArgs); + } + + /** + * Method called to handle delete requests from client + * applications. This plays the role of the "concrete hook + * method" in the Template Method pattern. + */ + @Override + public int deleteCharacters(Uri uri, + String selection, + String[] selectionArgs) { + // Expand the selection if necessary. + selection = addSelectionArgs(selection, + selectionArgs, + " OR "); + return mOpenHelper.getWritableDatabase().delete + (CharacterContract.CharacterEntry.TABLE_NAME, + selection, + selectionArgs); + } + + /** + * Method called to handle delete requests from client + * applications. This plays the role of the "concrete hook + * method" in the Template Method pattern. + */ + @Override + public int deleteCharacter(Uri uri, + String selection, + String[] selectionArgs) { + // Expand the selection if necessary. + selection = addSelectionArgs(selection, + selectionArgs, + " OR "); + // Just delete a single row in the database. + return mOpenHelper.getWritableDatabase().delete + (CharacterContract.CharacterEntry.TABLE_NAME, + addKeyIdCheckToWhereStatement(selection, + ContentUris.parseId(uri)), + selectionArgs); + } + + /** + * Return a selection string that concatenates all the + * @a selectionArgs for a given @a selection using the given @a + * operation. + */ + private String addSelectionArgs(String selection, + String [] selectionArgs, + String operation) { + // Handle the "null" case. + if (selection == null + || selectionArgs == null) + return null; + else { + String selectionResult = ""; + + // Properly add the selection args to the selectionResult. + for (int i = 0; + i < selectionArgs.length - 1; + ++i) + selectionResult += (selection + + " = ? " + + operation + + " "); + + // Handle the final selection case. + selectionResult += (selection + + " = ?"); + + // Output the selectionResults to Logcat. + Log.d(TAG, + "selection = " + + selectionResult + + " selectionArgs = "); + for (String args : selectionArgs) + Log.d(TAG, + args + + " "); + + return selectionResult; + } + } + + /** + * Helper method that appends a given key id to the end of the + * WHERE statement parameter. + */ + private static String addKeyIdCheckToWhereStatement(String whereStatement, + long id) { + String newWhereStatement; + if (TextUtils.isEmpty(whereStatement)) + newWhereStatement = ""; + else + newWhereStatement = whereStatement + " AND "; + + // Append the key id to the end of the WHERE statement. + return newWhereStatement + + CharacterContract.CharacterEntry._ID + + " = '" + + id + + "'"; + } +} diff --git a/ex/HobbitContentProvider/app/src/main/res/drawable-hdpi/icon.png b/ex/HobbitContentProvider/app/src/main/res/drawable-hdpi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8074c4c571b8cd19e27f4ee5545df367420686d7 GIT binary patch literal 4147 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 literal 0 HcmV?d00001 diff --git a/ex/HobbitContentProvider/app/src/main/res/drawable-ldpi/icon.png b/ex/HobbitContentProvider/app/src/main/res/drawable-ldpi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1095584ec21f71cd0afc9e0993aa2209671b590c GIT binary patch literal 1723 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 + + + + +