Samples: Fix bugs in Note Pad sample app.

Change-Id: I859cbc73eaaa77a9ff89484f33c7150d76a4e2c2
This commit is contained in:
Joe Malin
2010-09-24 15:17:56 -07:00
parent 3fe7e69b2d
commit 390d67679c
6 changed files with 335 additions and 67 deletions

View File

@@ -115,6 +115,6 @@
</application>
<uses-sdk android:targetSdkVersion="8" android:minSdkVersion="8"/>
<uses-sdk android:minSdkVersion="Froyo"/>
</manifest>

View File

@@ -584,6 +584,7 @@ public class NotePadProvider extends ContentProvider implements PipeDataWriter<C
// Opens the database object in "write" mode.
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
String finalWhere;
int count;
@@ -604,34 +605,27 @@ public class NotePadProvider extends ContentProvider implements PipeDataWriter<C
// incoming data, but modifies the where clause to restrict it to the
// particular note ID.
case NOTE_ID:
// From the incoming URI, get the note ID
String noteId = uri.getPathSegments().get(NotePad.Notes.NOTE_ID_PATH_POSITION);
// If no where clause was passed in, uses the note ID column name
// for a column and the note ID for a value.
if (TextUtils.isEmpty(where)) {
where = NotePad.Notes._ID + " = ?";
whereArgs[0] = noteId;
} else {
/*
* If where clause columns were passed in, appends the note ID column name to
* the list of columns using a replaceable parameter. This works even if the
* other columns have actual values.
* Starts a final WHERE clause by restricting it to the
* desired note ID.
*/
// Appends the note ID column name as an AND condition using a replaceable
// parameter.
where = where + " AND " + NotePad.Notes._ID + " = ?";
finalWhere =
NotePad.Notes._ID + // The ID column name
" = " + // test for equality
uri.getPathSegments(). // the incoming note ID
get(NotePad.Notes.NOTE_ID_PATH_POSITION)
;
// Appends the note ID value to the end of the where clause values.
whereArgs[whereArgs.length] = noteId;
// If there were additional selection criteria, append them to the final
// WHERE clause
if (where != null) {
finalWhere = finalWhere + " AND " + where;
}
// Performs the delete.
count = db.delete(
NotePad.Notes.TABLE_NAME, // The database table name.
where, // The incoming where clause column names.
finalWhere, // The final WHERE clause
whereArgs // The incoming where clause values.
);
break;
@@ -677,6 +671,7 @@ public class NotePadProvider extends ContentProvider implements PipeDataWriter<C
// Opens the database object in "write" mode.
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int count;
String finalWhere;
// Does the update based on the incoming URI pattern
switch (sUriMatcher.match(uri)) {
@@ -700,29 +695,29 @@ public class NotePadProvider extends ContentProvider implements PipeDataWriter<C
// From the incoming URI, get the note ID
String noteId = uri.getPathSegments().get(NotePad.Notes.NOTE_ID_PATH_POSITION);
// If no where clause was passed in, uses the note ID column name
// for a column and the note ID for a value.
if (TextUtils.isEmpty(where)) {
where = NotePad.Notes._ID + " = " + noteId;
// If where clause columns were passed in, appends the note ID to the where
// clause
} else {
/*
* Prepends the note ID column name to the search criteria. This handles two
* cases: a) whereArgs contains values b) whereArgs is null.
*
* Starts creating the final WHERE clause by restricting it to the incoming
* note ID.
*/
where = NotePad.Notes._ID + " = " + noteId + " AND " + where;
finalWhere =
NotePad.Notes._ID + // The ID column name
" = " + // test for equality
uri.getPathSegments(). // the incoming note ID
get(NotePad.Notes.NOTE_ID_PATH_POSITION)
;
// If there were additional selection criteria, append them to the final WHERE
// clause
if (where !=null) {
finalWhere = finalWhere + " AND " + where;
}
// Does the update and returns the number of rows updated.
count = db.update(
NotePad.Notes.TABLE_NAME, // The database table name.
values, // A map of column names and new values to use.
where, // The where clause column names. May contain
finalWhere, // The final WHERE clause to use
// placeholders for whereArgs
whereArgs // The where clause column values to select on, or
// null if the values are in the where argument.

View File

@@ -84,6 +84,7 @@ public class TitleEditor extends Activity implements View.OnClickListener {
* the block will be momentary, but in a real app you should use
* android.content.AsyncQueryHandler or android.os.AsyncTask.
*/
mCursor = managedQuery(
mUri, // The URI for the note that is to be retrieved.
PROJECTION, // The columns to retrieve
@@ -141,6 +142,7 @@ public class TitleEditor extends Activity implements View.OnClickListener {
// Verifies that the query made in onCreate() actually worked. If it worked, then the
// Cursor object is not null. If it is *empty*, then mCursor.getCount() == 0.
if (mCursor != null) {
// Creates a values map for updating the provider.
@@ -163,6 +165,7 @@ public class TitleEditor extends Activity implements View.OnClickListener {
null, // No selection criteria is used, so no "where" columns are needed.
null // No "where" columns are used, so no "where" values are needed.
);
}
}

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2008 The Android Open Source Project
<!--
Copyright (C) 2007 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,20 +14,36 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<!--
To run this test package, install it and the NotesList application to a device or emulator and
then run
adb shell am instrument -w com.example.android.notepad.tests/android.test.InstrumentationTestRunner
-->
<!--
The Android package name differs from the package ID of the code. The code package ID
'com.example.android.notepad' allows the test code to use declarations from the application
under test, while the Android package name identifies this as a separate test package.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.notepad.tests">
<!-- We add an application tag here just so that we can indicate that
this package needs to link against the android.test library,
which is needed when building test cases. -->
package="com.example.android.notepad.tests"
android:versionCode="1"
android:versionName="1.0">
<!--
The application element indicates that this package must be linked against the library
android.test.runner, which is not part of the normal link path. The library contains
code for test cases.
-->
<application>
<uses-library android:name="android.test.runner" />
</application>
<!--
The instrumentation element tells Android to use instrumentation to run this package.
The target Android package 'com.example.android.notepad' is loaded along with the
test package 'com.example.android.notepad.tests'. Android then starts the class
'android.test.InstrumentationTestRunner', which loads the test case classes in the package.
-->
<instrumentation android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.example.android.notepad"
android:label="NotePad sample tests">
</instrumentation>
android:label="Tests for com.example.android.notepad"/>
<uses-sdk android:minSdkVersion="10"></uses-sdk>
</manifest>

View File

@@ -17,7 +17,6 @@
package com.example.android.notepad;
import android.test.ActivityInstrumentationTestCase2;
import com.example.android.notepad.NotesList;
/**
@@ -30,7 +29,7 @@ public class NotePadActivityTest extends ActivityInstrumentationTestCase2<NotesL
* Creates an {@link ActivityInstrumentationTestCase2} for the {@link NotesList} activity.
*/
public NotePadActivityTest() {
super("com.example.android.notepad.test",NotesList.class);
super(NotesList.class);
}
/**

View File

@@ -18,12 +18,19 @@ package com.example.android.notepad;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.test.ProviderTestCase2;
import android.test.mock.MockContentResolver;
import java.io.BufferedReader;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Calendar;
import java.util.GregorianCalendar;
@@ -73,6 +80,18 @@ public class NotePadProviderTest extends ProviderTestCase2<NotePadProvider> {
// Stores a timestamp value, set to an arbitrary starting point
private final static long START_DATE = TEST_CALENDAR.getTimeInMillis();
// Sets a MIME type filter, used to test provider methods that return more than one MIME type
// for a particular note. The filter will retrieve any MIME types supported for the content URI.
private final static String MIME_TYPES_ALL = "*/*";
// Sets a MIME type filter, used to test provider methods that return more than one MIME type
// for a particular note. The filter is nonsense, so it will not retrieve any MIME types.
private final static String MIME_TYPES_NONE = "qwer/qwer";
// Sets a MIME type filter for plain text, used to the provider's methods that only handle
// plain text
private final static String MIME_TYPE_TEXT = "text/plain";
/*
* Constructor for the test case class.
* Calls the super constructor with the class name of the provider under test and the
@@ -82,7 +101,6 @@ public class NotePadProviderTest extends ProviderTestCase2<NotePadProvider> {
super(NotePadProvider.class, NotePad.AUTHORITY);
}
/*
* Sets up the test environment before each test method. Creates a mock content resolver,
* gets the provider under test, and creates a new database for the provider.
@@ -162,6 +180,241 @@ public class NotePadProviderTest extends ProviderTestCase2<NotePadProvider> {
mimeType = mMockResolver.getType(INVALID_URI);
}
/*
* Tests the provider's stream MIME types returned by getStreamTypes(). If the provider supports
* stream data for the URI, the MIME type is returned. Otherwise, the provider returns null.
*/
public void testGetStreamTypes() {
// Tests the notes table URI. This should return null, since the content provider does
// not provide a stream MIME type for multiple notes.
assertNull(mMockResolver.getStreamTypes(NotePad.Notes.CONTENT_URI, MIME_TYPES_ALL));
// Tests the live folders URI. This should return null, since the content provider does not
// provide a stream MIME type for multiple notes.
assertNull(mMockResolver.getStreamTypes(NotePad.Notes.LIVE_FOLDER_URI, MIME_TYPES_ALL));
/*
* Tests the note id URI for a single note, using _ID value "1" which is a valid ID. Uses a
* valid MIME type filter that will return all the supported MIME types for a content URI.
* The result should be "text/plain".
*/
// Constructs the note id URI
Uri testUri = Uri.withAppendedPath(NotePad.Notes.CONTENT_ID_URI_BASE, "1");
// Gets the MIME types for the URI, with the filter that selects all MIME types.
String mimeType[] = mMockResolver.getStreamTypes(testUri, MIME_TYPES_ALL);
// Tests that the result is not null and is equal to the expected value. Also tests that
// only one MIME type is returned.
assertNotNull(mimeType);
assertEquals(mimeType[0],"text/plain");
assertEquals(mimeType.length,1);
/*
* Tests with the same URI but with a filter that should not return any URIs.
*/
mimeType = mMockResolver.getStreamTypes(testUri, MIME_TYPES_NONE);
assertNull(mimeType);
/*
* Tests with a URI that should not have any associated stream MIME types, but with a
* filter that returns all types. The result should still be null.
*/
mimeType = mMockResolver.getStreamTypes(NotePad.Notes.CONTENT_URI, MIME_TYPES_ALL);
assertNull(mimeType);
}
/*
* Tests the provider's public API for opening a read-only pipe of data for a note ID URI
* and MIME type filter matching "text/plain".
* This method throws a FileNotFoundException if the URI isn't for a note ID or the MIME type
* filter isn't "text/plain". It throws an IOException if it can't close a file descriptor.
*/
public void testOpenTypedAssetFile() throws FileNotFoundException, IOException {
// A URI to contain a note ID content URI.
Uri testNoteIdUri;
// A handle for the file descriptor returned by openTypedAssetFile().
AssetFileDescriptor testAssetDescriptor;
// Inserts data into the provider, so that the note ID URI will be recognized.
insertData();
// Constructs a URI with a note ID of 1. This matches the note ID URI pattern that
// openTypedAssetFile can handle.
testNoteIdUri = ContentUris.withAppendedId(NotePad.Notes.CONTENT_ID_URI_BASE, 1);
// Opens the pipe. The opts argument is for passing options from a caller to the provider,
// but the NotePadProvider does not use it.
testAssetDescriptor = mMockResolver.openTypedAssetFileDescriptor(
testNoteIdUri, // the URI for a single note. The pipe points to this
// note's data
MIME_TYPE_TEXT, // a MIME type of "text/plain"
null // the "opts" argument
);
// Gets the parcel file handle from the asset file handle.
ParcelFileDescriptor testParcelDescriptor = testAssetDescriptor.getParcelFileDescriptor();
// Gets the file handle from the asset file handle.
FileDescriptor testDescriptor = testAssetDescriptor.getFileDescriptor();
// Tests that the asset file handle is not null.
assertNotNull(testAssetDescriptor);
// Tests that the parcel file handle is not null.
assertNotNull(testParcelDescriptor);
// Tests that the file handle is not null.
assertNotNull(testDescriptor);
// Tests that the file handle is valid.
assertTrue(testDescriptor.valid());
// Closes the file handles.
testParcelDescriptor.close();
testAssetDescriptor.close();
/*
* Changes the URI to a notes URI for multiple notes, and re-test. This should fail, since
* the provider does not support this type of URI. A FileNotFound exception is expected,
* so call fail() if it does *not* occur.
*/
try {
testAssetDescriptor = mMockResolver.openTypedAssetFileDescriptor(
NotePad.Notes.CONTENT_URI,
MIME_TYPE_TEXT,
null
);
fail();
} catch (FileNotFoundException e) {
// continue
}
/*
* Changes back to the note ID URI, but changes the MIME type filter to one that is not
* supported by the provider. This should also fail, since the provider will only open a
* pipe for MIME type "text/plain". A FileNotFound exception is expected, so calls
* fail() if it does *not* occur.
*/
try {
testAssetDescriptor = mMockResolver.openTypedAssetFileDescriptor(
testNoteIdUri,
MIME_TYPES_NONE,
null
);
fail();
} catch (FileNotFoundException e) {
// continue
}
}
/*
* Tests the provider's method for actually returning writing data into a pipe. The method is
* writeDataToPipe, but this method is not called directly. Instead, a caller invokes
* openTypedAssetFile(). That method uses ContentProvider.openPipeHelper(), which has as one of
* its arguments a ContentProvider.PipeDataWriter object that must actually put the data into
* the pipe. PipeDataWriter is an interface, not a class, so it must be implemented.
*
* The NotePadProvider class itself implements the "ContentProvider.PipeDataWriter, which means
* that it supplies the interface's only method, writeDataToPipe(). In effect, a call to
* openTypedAssetFile() calls writeDataToPipe().
*
* The test of writeDataToPipe() is separate from other tests of openTypedAssetFile() for the
* sake of clarity.
*/
public void testWriteDataToPipe() throws FileNotFoundException {
// A string array to hold the incoming data
String[] inputData = {"","",""};
// A URI for a note ID.
Uri noteIdUri;
// A Cursor to contain the retrieved note.
Cursor noteIdCursor;
// An AssetFileDescriptor for the pipe.
AssetFileDescriptor noteIdAssetDescriptor;
// The ParcelFileDescriptor in the AssetFileDescriptor
ParcelFileDescriptor noteIdParcelDescriptor;
// Inserts test data into the provider.
insertData();
// Creates note ID URI for a note that should now be in the provider.
noteIdUri = ContentUris.withAppendedId(
NotePad.Notes.CONTENT_ID_URI_BASE, // The base pattern for a note ID URI
1 // Sets the URI to point to record ID 1 in the
// provider
);
// Gets a Cursor for the note.
noteIdCursor = mMockResolver.query(
noteIdUri, // the URI for the note ID we want to retrieve
null, // no projection, retrieve all the columns
null, // no WHERE clause
null, // no WHERE arguments
null // default sort order
);
// Checks that the call worked.
// a) Checks that the cursor is not null
// b) Checks that it contains a single record
assertNotNull(noteIdCursor);
assertEquals(1,noteIdCursor.getCount());
// Opens the pipe that will contain the data.
noteIdAssetDescriptor = mMockResolver.openTypedAssetFileDescriptor(
noteIdUri, // the URI of the note that will provide the data
MIME_TYPE_TEXT, // the "text/plain" MIME type
null // no other options
);
// Checks that the call worked.
// a) checks that the AssetFileDescriptor is not null
// b) gets its ParcelFileDescriptor
// c) checks that the ParcelFileDescriptor is not null
assertNotNull(noteIdAssetDescriptor);
noteIdParcelDescriptor = noteIdAssetDescriptor.getParcelFileDescriptor();
assertNotNull(noteIdParcelDescriptor);
// Gets a File Reader that can read the pipe.
FileReader fIn = new FileReader(noteIdParcelDescriptor.getFileDescriptor());
// Gets a buffered reader wrapper for the File Reader. This allows reading line by line.
BufferedReader bIn = new BufferedReader(fIn);
/*
* The pipe should contain three lines: The note's title, an empty line, and the note's
* contents. The following code reads and stores these three lines.
*/
for (int index = 0; index < inputData.length; index++) {
try {
inputData[index] = bIn.readLine();
} catch (IOException e) {
e.printStackTrace();
fail();
}
}
// Asserts that the first record in the provider (written from TEST_NOTES[0]) has the same
// note title as the first line retrieved from the pipe.
assertEquals(TEST_NOTES[0].title, inputData[0]);
// Asserts that the first record in the provider (written from TEST_NOTES[0]) has the same
// note contents as the third line retrieved from the pipe.
assertEquals(TEST_NOTES[0].note, inputData[2]);
}
/*
* Tests the provider's public API for querying data in the table, using the URI for
* a dataset of records.
@@ -534,6 +787,7 @@ public class NotePadProviderTest extends ProviderTestCase2<NotePadProvider> {
// Asserts that only one row was updated. The selection criteria evaluated to
// "title = Note1", and the test data should only contain one row that matches that.
assertEquals(1, rowsUpdated);
}
// A utility for converting note data to a ContentValues map.