+ */
+public class TextToSpeechActivity extends Activity implements TextToSpeech.OnInitListener {
+
+ private static final String TAG = "TextToSpeechDemo";
+
+ private TextToSpeech mTts;
+ private Button mAgainButton;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.text_to_speech);
+
+ // Initialize text-to-speech. This is an asynchronous operation.
+ // The OnInitListener (second argument) is called after initialization completes.
+ mTts = new TextToSpeech(this,
+ this // TextToSpeech.OnInitListener
+ );
+
+ // The button is disabled in the layout.
+ // It will be enabled upon initialization of the TTS engine.
+ mAgainButton = (Button) findViewById(R.id.again_button);
+
+ mAgainButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ sayHello();
+ }
+ });
+ }
+
+ @Override
+ public void onDestroy() {
+ // Don't forget to shutdown!
+ if (mTts != null) {
+ mTts.stop();
+ mTts.shutdown();
+ }
+
+ super.onDestroy();
+ }
+
+ // Implements TextToSpeech.OnInitListener.
+ public void onInit(int status) {
+ // status can be either TextToSpeech.SUCCESS or TextToSpeech.ERROR.
+ if (status == TextToSpeech.SUCCESS) {
+ // Set preferred language to US english.
+ // Note that a language may not be available, and the result will indicate this.
+ int result = mTts.setLanguage(Locale.US);
+ // Try this someday for some interesting results.
+ // int result mTts.setLanguage(Locale.FRANCE);
+ if (result == TextToSpeech.LANG_MISSING_DATA ||
+ result == TextToSpeech.LANG_NOT_SUPPORTED) {
+ // Lanuage data is missing or the language is not supported.
+ Log.e(TAG, "Language is not available.");
+ } else {
+ // Check the documentation for other possible result codes.
+ // For example, the language may be available for the locale,
+ // but not for the specified country and variant.
+
+ // The TTS engine has been successfully initialized.
+ // Allow the user to press the button for the app to speak again.
+ mAgainButton.setEnabled(true);
+ // Greet the user.
+ sayHello();
+ }
+ } else {
+ // Initialization failed.
+ Log.e(TAG, "Could not initialize TextToSpeech.");
+ }
+ }
+
+ private static final Random RANDOM = new Random();
+ private static final String[] HELLOS = {
+ "Hello",
+ "Salutations",
+ "Greetings",
+ "Howdy",
+ "What's crack-a-lackin?",
+ "That explains the stench!"
+ };
+
+ private void sayHello() {
+ // Select a random hello.
+ int helloLength = HELLOS.length;
+ String hello = HELLOS[RANDOM.nextInt(helloLength)];
+ mTts.speak(hello,
+ TextToSpeech.QUEUE_FLUSH, // Drop all pending entries in the playback queue.
+ null);
+ }
+
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/graphics/GLES20Activity.java b/samples/ApiDemos/src/com/example/android/apis/graphics/GLES20Activity.java
new file mode 100644
index 000000000..f5573cf48
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/graphics/GLES20Activity.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2009 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.apis.graphics;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.ConfigurationInfo;
+import android.opengl.GLSurfaceView;
+import android.os.Bundle;
+
+/**
+ * This sample shows how to check for OpenGL ES 2.0 support at runtime, and then
+ * use either OpenGL ES 1.0 or OpenGL ES 2.0, as appropriate.
+ */
+public class GLES20Activity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mGLSurfaceView = new GLSurfaceView(this);
+ if (detectOpenGLES20()) {
+ // Tell the surface view we want to create an OpenGL ES 2.0-compatible
+ // context, and set an OpenGL ES 2.0-compatible renderer.
+ mGLSurfaceView.setEGLContextClientVersion(2);
+ mGLSurfaceView.setRenderer(new GLES20TriangleRenderer(this));
+ } else {
+ // Set an OpenGL ES 1.x-compatible renderer. In a real application
+ // this renderer might approximate the same output as the 2.0 renderer.
+ mGLSurfaceView.setRenderer(new TriangleRenderer(this));
+ }
+ setContentView(mGLSurfaceView);
+ }
+
+ private boolean detectOpenGLES20() {
+ ActivityManager am =
+ (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
+ ConfigurationInfo info = am.getDeviceConfigurationInfo();
+ return (info.reqGlEsVersion >= 0x20000);
+ }
+
+ @Override
+ protected void onResume() {
+ // Ideally a game should implement onResume() and onPause()
+ // to take appropriate action when the activity looses focus
+ super.onResume();
+ mGLSurfaceView.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ // Ideally a game should implement onResume() and onPause()
+ // to take appropriate action when the activity looses focus
+ super.onPause();
+ mGLSurfaceView.onPause();
+ }
+
+ private GLSurfaceView mGLSurfaceView;
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/graphics/GLES20TriangleRenderer.java b/samples/ApiDemos/src/com/example/android/apis/graphics/GLES20TriangleRenderer.java
new file mode 100644
index 000000000..956eb6862
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/graphics/GLES20TriangleRenderer.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2009 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.apis.graphics;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.opengl.GLES20;
+import android.opengl.GLSurfaceView;
+import android.opengl.GLUtils;
+import android.opengl.Matrix;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.example.android.apis.R;
+
+class GLES20TriangleRenderer implements GLSurfaceView.Renderer {
+
+ public GLES20TriangleRenderer(Context context) {
+ mContext = context;
+ mTriangleVertices = ByteBuffer.allocateDirect(mTriangleVerticesData.length
+ * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
+ mTriangleVertices.put(mTriangleVerticesData).position(0);
+ }
+
+ public void onDrawFrame(GL10 glUnused) {
+ // Ignore the passed-in GL10 interface, and use the GLES20
+ // class's static methods instead.
+ GLES20.glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
+ GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
+ GLES20.glUseProgram(mProgram);
+ checkGlError("glUseProgram");
+
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureID);
+
+ mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
+ GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false,
+ TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
+ checkGlError("glVertexAttribPointer maPosition");
+ mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
+ GLES20.glEnableVertexAttribArray(maPositionHandle);
+ checkGlError("glEnableVertexAttribArray maPositionHandle");
+ GLES20.glVertexAttribPointer(maTextureHandle, 2, GLES20.GL_FLOAT, false,
+ TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
+ checkGlError("glVertexAttribPointer maTextureHandle");
+ GLES20.glEnableVertexAttribArray(maTextureHandle);
+ checkGlError("glEnableVertexAttribArray maTextureHandle");
+
+ long time = SystemClock.uptimeMillis() % 4000L;
+ float angle = 0.090f * ((int) time);
+ Matrix.setRotateM(mMMatrix, 0, angle, 0, 0, 1.0f);
+ Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, mMMatrix, 0);
+ Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0);
+
+ GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
+ checkGlError("glDrawArrays");
+ }
+
+ public void onSurfaceChanged(GL10 glUnused, int width, int height) {
+ // Ignore the passed-in GL10 interface, and use the GLES20
+ // class's static methods instead.
+ GLES20.glViewport(0, 0, width, height);
+ float ratio = (float) width / height;
+ Matrix.frustumM(mProjMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
+ }
+
+ public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
+ // Ignore the passed-in GL10 interface, and use the GLES20
+ // class's static methods instead.
+ mProgram = createProgram(mVertexShader, mFragmentShader);
+ if (mProgram == 0) {
+ return;
+ }
+ maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
+ checkGlError("glGetAttribLocation aPosition");
+ if (maPositionHandle == -1) {
+ throw new RuntimeException("Could not get attrib location for aPosition");
+ }
+ maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
+ checkGlError("glGetAttribLocation aTextureCoord");
+ if (maTextureHandle == -1) {
+ throw new RuntimeException("Could not get attrib location for aTextureCoord");
+ }
+
+ muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
+ checkGlError("glGetUniformLocation uMVPMatrix");
+ if (muMVPMatrixHandle == -1) {
+ throw new RuntimeException("Could not get attrib location for uMVPMatrix");
+ }
+
+ /*
+ * Create our texture. This has to be done each time the
+ * surface is created.
+ */
+
+ int[] textures = new int[1];
+ GLES20.glGenTextures(1, textures, 0);
+
+ mTextureID = textures[0];
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureID);
+
+ GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
+ GLES20.GL_NEAREST);
+ GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
+ GLES20.GL_TEXTURE_MAG_FILTER,
+ GLES20.GL_LINEAR);
+
+ GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
+ GLES20.GL_REPEAT);
+ GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
+ GLES20.GL_REPEAT);
+
+ InputStream is = mContext.getResources()
+ .openRawResource(R.raw.robot);
+ Bitmap bitmap;
+ try {
+ bitmap = BitmapFactory.decodeStream(is);
+ } finally {
+ try {
+ is.close();
+ } catch(IOException e) {
+ // Ignore.
+ }
+ }
+
+ GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
+ bitmap.recycle();
+
+ Matrix.setLookAtM(mVMatrix, 0, 0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
+ }
+
+ private int loadShader(int shaderType, String source) {
+ int shader = GLES20.glCreateShader(shaderType);
+ if (shader != 0) {
+ GLES20.glShaderSource(shader, source);
+ GLES20.glCompileShader(shader);
+ int[] compiled = new int[1];
+ GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
+ if (compiled[0] == 0) {
+ Log.e(TAG, "Could not compile shader " + shaderType + ":");
+ Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
+ GLES20.glDeleteShader(shader);
+ shader = 0;
+ }
+ }
+ return shader;
+ }
+
+ private int createProgram(String vertexSource, String fragmentSource) {
+ int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
+ if (vertexShader == 0) {
+ return 0;
+ }
+
+ int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
+ if (pixelShader == 0) {
+ return 0;
+ }
+
+ int program = GLES20.glCreateProgram();
+ if (program != 0) {
+ GLES20.glAttachShader(program, vertexShader);
+ checkGlError("glAttachShader");
+ GLES20.glAttachShader(program, pixelShader);
+ checkGlError("glAttachShader");
+ GLES20.glLinkProgram(program);
+ int[] linkStatus = new int[1];
+ GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
+ if (linkStatus[0] != GLES20.GL_TRUE) {
+ Log.e(TAG, "Could not link program: ");
+ Log.e(TAG, GLES20.glGetProgramInfoLog(program));
+ GLES20.glDeleteProgram(program);
+ program = 0;
+ }
+ }
+ return program;
+ }
+
+ private void checkGlError(String op) {
+ int error;
+ while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
+ Log.e(TAG, op + ": glError " + error);
+ throw new RuntimeException(op + ": glError " + error);
+ }
+ }
+
+ private static final int FLOAT_SIZE_BYTES = 4;
+ private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
+ private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
+ private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
+ private final float[] mTriangleVerticesData = {
+ // X, Y, Z, U, V
+ -1.0f, -0.5f, 0, -0.5f, 0.0f,
+ 1.0f, -0.5f, 0, 1.5f, -0.0f,
+ 0.0f, 1.11803399f, 0, 0.5f, 1.61803399f };
+
+ private FloatBuffer mTriangleVertices;
+
+ private final String mVertexShader =
+ "uniform mat4 uMVPMatrix;\n" +
+ "attribute vec4 aPosition;\n" +
+ "attribute vec2 aTextureCoord;\n" +
+ "varying vec2 vTextureCoord;\n" +
+ "void main() {\n" +
+ " gl_Position = uMVPMatrix * aPosition;\n" +
+ " vTextureCoord = aTextureCoord;\n" +
+ "}\n";
+
+ private final String mFragmentShader =
+ "precision mediump float;\n" +
+ "varying vec2 vTextureCoord;\n" +
+ "uniform sampler2D sTexture;\n" +
+ "void main() {\n" +
+ " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
+ "}\n";
+
+ private float[] mMVPMatrix = new float[16];
+ private float[] mProjMatrix = new float[16];
+ private float[] mMMatrix = new float[16];
+ private float[] mVMatrix = new float[16];
+
+ private int mProgram;
+ private int mTextureID;
+ private int muMVPMatrixHandle;
+ private int maPositionHandle;
+ private int maTextureHandle;
+
+ private Context mContext;
+ private static String TAG = "GLES20TriangleRenderer";
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/graphics/MatrixPaletteActivity.java b/samples/ApiDemos/src/com/example/android/apis/graphics/MatrixPaletteActivity.java
new file mode 100644
index 000000000..520e0e1c8
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/graphics/MatrixPaletteActivity.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2009 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.apis.graphics;
+
+import android.app.Activity;
+import android.opengl.GLSurfaceView;
+import android.os.Bundle;
+
+/**
+ * This sample shows how to implement a Matrix Palette
+ */
+public class MatrixPaletteActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mGLSurfaceView = new GLSurfaceView(this);
+ mGLSurfaceView.setRenderer(new MatrixPaletteRenderer(this));
+ setContentView(mGLSurfaceView);
+ }
+
+ @Override
+ protected void onResume() {
+ // Ideally a game should implement onResume() and onPause()
+ // to take appropriate action when the activity looses focus
+ super.onResume();
+ mGLSurfaceView.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ // Ideally a game should implement onResume() and onPause()
+ // to take appropriate action when the activity looses focus
+ super.onPause();
+ mGLSurfaceView.onPause();
+ }
+
+ private GLSurfaceView mGLSurfaceView;
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/graphics/MatrixPaletteRenderer.java b/samples/ApiDemos/src/com/example/android/apis/graphics/MatrixPaletteRenderer.java
new file mode 100644
index 000000000..e0e2db166
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/graphics/MatrixPaletteRenderer.java
@@ -0,0 +1,408 @@
+/*
+ * Copyright (C) 2009 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.apis.graphics;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.CharBuffer;
+import java.nio.FloatBuffer;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL;
+import javax.microedition.khronos.opengles.GL10;
+import javax.microedition.khronos.opengles.GL11;
+import javax.microedition.khronos.opengles.GL11Ext;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.opengl.GLSurfaceView;
+import android.opengl.GLU;
+import android.opengl.GLUtils;
+import android.os.SystemClock;
+
+import com.example.android.apis.R;
+
+public class MatrixPaletteRenderer implements GLSurfaceView.Renderer{
+ private Context mContext;
+ private Grid mGrid;
+ private int mTextureID;
+
+ /** A grid is a topologically rectangular array of vertices.
+ *
+ * This grid class is customized for the vertex data required for this
+ * example.
+ *
+ * The vertex and index data are held in VBO objects because on most
+ * GPUs VBO objects are the fastest way of rendering static vertex
+ * and index data.
+ *
+ */
+
+ private static class Grid {
+ // Size of vertex data elements in bytes:
+ final static int FLOAT_SIZE = 4;
+ final static int CHAR_SIZE = 2;
+
+ // Vertex structure:
+ // float x, y, z;
+ // float u, v;
+ // float weight0, weight1;
+ // byte palette0, palette1, pad0, pad1;
+
+ final static int VERTEX_SIZE = 8 * FLOAT_SIZE;
+ final static int VERTEX_TEXTURE_BUFFER_INDEX_OFFSET = 3;
+ final static int VERTEX_WEIGHT_BUFFER_INDEX_OFFSET = 5;
+ final static int VERTEX_PALETTE_INDEX_OFFSET = 7 * FLOAT_SIZE;
+
+ private int mVertexBufferObjectId;
+ private int mElementBufferObjectId;
+
+ // These buffers are used to hold the vertex and index data while
+ // constructing the grid. Once createBufferObjects() is called
+ // the buffers are nulled out to save memory.
+
+ private ByteBuffer mVertexByteBuffer;
+ private FloatBuffer mVertexBuffer;
+ private CharBuffer mIndexBuffer;
+
+ private int mW;
+ private int mH;
+ private int mIndexCount;
+
+ public Grid(int w, int h) {
+ if (w < 0 || w >= 65536) {
+ throw new IllegalArgumentException("w");
+ }
+ if (h < 0 || h >= 65536) {
+ throw new IllegalArgumentException("h");
+ }
+ if (w * h >= 65536) {
+ throw new IllegalArgumentException("w * h >= 65536");
+ }
+
+ mW = w;
+ mH = h;
+ int size = w * h;
+
+ mVertexByteBuffer = ByteBuffer.allocateDirect(VERTEX_SIZE * size)
+ .order(ByteOrder.nativeOrder());
+ mVertexBuffer = mVertexByteBuffer.asFloatBuffer();
+
+ int quadW = mW - 1;
+ int quadH = mH - 1;
+ int quadCount = quadW * quadH;
+ int indexCount = quadCount * 6;
+ mIndexCount = indexCount;
+ mIndexBuffer = ByteBuffer.allocateDirect(CHAR_SIZE * indexCount)
+ .order(ByteOrder.nativeOrder()).asCharBuffer();
+
+ /*
+ * Initialize triangle list mesh.
+ *
+ * [0]-----[ 1] ...
+ * | / |
+ * | / |
+ * | / |
+ * [w]-----[w+1] ...
+ * | |
+ *
+ */
+
+ {
+ int i = 0;
+ for (int y = 0; y < quadH; y++) {
+ for (int x = 0; x < quadW; x++) {
+ char a = (char) (y * mW + x);
+ char b = (char) (y * mW + x + 1);
+ char c = (char) ((y + 1) * mW + x);
+ char d = (char) ((y + 1) * mW + x + 1);
+
+ mIndexBuffer.put(i++, a);
+ mIndexBuffer.put(i++, c);
+ mIndexBuffer.put(i++, b);
+
+ mIndexBuffer.put(i++, b);
+ mIndexBuffer.put(i++, c);
+ mIndexBuffer.put(i++, d);
+ }
+ }
+ }
+
+ }
+
+ public void set(int i, int j, float x, float y, float z,
+ float u, float v,
+ float w0, float w1,
+ int p0, int p1) {
+ if (i < 0 || i >= mW) {
+ throw new IllegalArgumentException("i");
+ }
+ if (j < 0 || j >= mH) {
+ throw new IllegalArgumentException("j");
+ }
+
+ if (w0 + w1 != 1.0f) {
+ throw new IllegalArgumentException("Weights must add up to 1.0f");
+ }
+
+ int index = mW * j + i;
+
+ mVertexBuffer.position(index * VERTEX_SIZE / FLOAT_SIZE);
+ mVertexBuffer.put(x);
+ mVertexBuffer.put(y);
+ mVertexBuffer.put(z);
+ mVertexBuffer.put(u);
+ mVertexBuffer.put(v);
+ mVertexBuffer.put(w0);
+ mVertexBuffer.put(w1);
+
+ mVertexByteBuffer.position(index * VERTEX_SIZE + VERTEX_PALETTE_INDEX_OFFSET);
+ mVertexByteBuffer.put((byte) p0);
+ mVertexByteBuffer.put((byte) p1);
+ }
+
+ public void createBufferObjects(GL gl) {
+ // Generate a the vertex and element buffer IDs
+ int[] vboIds = new int[2];
+ GL11 gl11 = (GL11) gl;
+ gl11.glGenBuffers(2, vboIds, 0);
+ mVertexBufferObjectId = vboIds[0];
+ mElementBufferObjectId = vboIds[1];
+
+ // Upload the vertex data
+ gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, mVertexBufferObjectId);
+ mVertexByteBuffer.position(0);
+ gl11.glBufferData(GL11.GL_ARRAY_BUFFER, mVertexByteBuffer.capacity(), mVertexByteBuffer, GL11.GL_STATIC_DRAW);
+
+ gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, mElementBufferObjectId);
+ mIndexBuffer.position(0);
+ gl11.glBufferData(GL11.GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer.capacity() * CHAR_SIZE, mIndexBuffer, GL11.GL_STATIC_DRAW);
+
+ // We don't need the in-memory data any more
+ mVertexBuffer = null;
+ mVertexByteBuffer = null;
+ mIndexBuffer = null;
+ }
+
+ public void draw(GL10 gl) {
+ GL11 gl11 = (GL11) gl;
+ GL11Ext gl11Ext = (GL11Ext) gl;
+
+ gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
+
+ gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, mVertexBufferObjectId);
+ gl11.glVertexPointer(3, GL10.GL_FLOAT, VERTEX_SIZE, 0);
+ gl11.glTexCoordPointer(2, GL10.GL_FLOAT, VERTEX_SIZE, VERTEX_TEXTURE_BUFFER_INDEX_OFFSET * FLOAT_SIZE);
+
+ gl.glEnableClientState(GL11Ext.GL_MATRIX_INDEX_ARRAY_OES);
+ gl.glEnableClientState(GL11Ext.GL_WEIGHT_ARRAY_OES);
+
+ gl11Ext.glWeightPointerOES(2, GL10.GL_FLOAT, VERTEX_SIZE, VERTEX_WEIGHT_BUFFER_INDEX_OFFSET * FLOAT_SIZE);
+ gl11Ext.glMatrixIndexPointerOES(2, GL10.GL_UNSIGNED_BYTE, VERTEX_SIZE, VERTEX_PALETTE_INDEX_OFFSET );
+
+ gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, mElementBufferObjectId);
+ gl11.glDrawElements(GL10.GL_TRIANGLES, mIndexCount, GL10.GL_UNSIGNED_SHORT, 0);
+ gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
+ gl.glDisableClientState(GL11Ext.GL_MATRIX_INDEX_ARRAY_OES);
+ gl.glDisableClientState(GL11Ext.GL_WEIGHT_ARRAY_OES);
+ gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, 0);
+ gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, 0);
+ }
+ }
+
+ public MatrixPaletteRenderer(Context context) {
+ mContext = context;
+ }
+
+ public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+ /*
+ * By default, OpenGL enables features that improve quality
+ * but reduce performance. One might want to tweak that
+ * especially on software renderer.
+ */
+ gl.glDisable(GL10.GL_DITHER);
+
+ /*
+ * Some one-time OpenGL initialization can be made here
+ * probably based on features of this particular context
+ */
+ gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,
+ GL10.GL_FASTEST);
+
+ gl.glClearColor(.5f, .5f, .5f, 1);
+ gl.glShadeModel(GL10.GL_SMOOTH);
+ gl.glEnable(GL10.GL_DEPTH_TEST);
+ gl.glEnable(GL10.GL_TEXTURE_2D);
+
+ /*
+ * Create our texture. This has to be done each time the
+ * surface is created.
+ */
+
+ int[] textures = new int[1];
+ gl.glGenTextures(1, textures, 0);
+
+ mTextureID = textures[0];
+ gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureID);
+
+ gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,
+ GL10.GL_NEAREST);
+ gl.glTexParameterf(GL10.GL_TEXTURE_2D,
+ GL10.GL_TEXTURE_MAG_FILTER,
+ GL10.GL_LINEAR);
+
+ gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
+ GL10.GL_CLAMP_TO_EDGE);
+ gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
+ GL10.GL_CLAMP_TO_EDGE);
+
+ gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE,
+ GL10.GL_REPLACE);
+
+ InputStream is = mContext.getResources()
+ .openRawResource(R.raw.robot);
+ Bitmap bitmap;
+ try {
+ bitmap = BitmapFactory.decodeStream(is);
+ } finally {
+ try {
+ is.close();
+ } catch(IOException e) {
+ // Ignore.
+ }
+ }
+
+ GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
+ bitmap.recycle();
+
+ mGrid = generateWeightedGrid(gl);
+ }
+
+ public void onDrawFrame(GL10 gl) {
+ /*
+ * By default, OpenGL enables features that improve quality
+ * but reduce performance. One might want to tweak that
+ * especially on software renderer.
+ */
+ gl.glDisable(GL10.GL_DITHER);
+
+ gl.glTexEnvx(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE,
+ GL10.GL_MODULATE);
+
+ /*
+ * Usually, the first thing one might want to do is to clear
+ * the screen. The most efficient way of doing this is to use
+ * glClear().
+ */
+
+ gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
+
+ gl.glEnable(GL10.GL_DEPTH_TEST);
+
+ gl.glEnable(GL10.GL_CULL_FACE);
+
+ /*
+ * Now we're ready to draw some 3D objects
+ */
+
+ gl.glMatrixMode(GL10.GL_MODELVIEW);
+ gl.glLoadIdentity();
+
+ GLU.gluLookAt(gl, 0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
+
+ gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
+ gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
+
+ gl.glActiveTexture(GL10.GL_TEXTURE0);
+ gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureID);
+ gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
+ GL10.GL_REPEAT);
+ gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
+ GL10.GL_REPEAT);
+
+ long time = SystemClock.uptimeMillis() % 4000L;
+
+ // Rock back and forth
+ double animationUnit = ((double) time) / 4000;
+ float unitAngle = (float) Math.cos(animationUnit * 2 * Math.PI);
+ float angle = unitAngle * 135f;
+
+ gl.glEnable(GL11Ext.GL_MATRIX_PALETTE_OES);
+ gl.glMatrixMode(GL11Ext.GL_MATRIX_PALETTE_OES);
+
+ GL11Ext gl11Ext = (GL11Ext) gl;
+
+ // matrix 0: no transformation
+ gl11Ext.glCurrentPaletteMatrixOES(0);
+ gl11Ext.glLoadPaletteFromModelViewMatrixOES();
+
+
+ // matrix 1: rotate by "angle"
+ gl.glRotatef(angle, 0, 0, 1.0f);
+
+ gl11Ext.glCurrentPaletteMatrixOES(1);
+ gl11Ext.glLoadPaletteFromModelViewMatrixOES();
+
+ mGrid.draw(gl);
+
+ gl.glDisable(GL11Ext.GL_MATRIX_PALETTE_OES);
+ }
+
+ public void onSurfaceChanged(GL10 gl, int w, int h) {
+ gl.glViewport(0, 0, w, h);
+
+ /*
+ * Set our projection matrix. This doesn't have to be done
+ * each time we draw, but usually a new projection needs to
+ * be set when the viewport is resized.
+ */
+
+ float ratio = (float) w / h;
+ gl.glMatrixMode(GL10.GL_PROJECTION);
+ gl.glLoadIdentity();
+ gl.glFrustumf(-ratio, ratio, -1, 1, 3, 7);
+ }
+
+ private Grid generateWeightedGrid(GL gl) {
+ final int uSteps = 20;
+ final int vSteps = 20;
+
+ float radius = 0.25f;
+ float height = 2.0f;
+ Grid grid = new Grid(uSteps + 1, vSteps + 1);
+
+ for (int j = 0; j <= vSteps; j++) {
+ for (int i = 0; i <= uSteps; i++) {
+ double angle = Math.PI * 2 * i / uSteps;
+ float x = radius * (float) Math.cos(angle);
+ float y = height * ((float) j / vSteps - 0.5f);
+ float z = radius * (float) Math.sin(angle);
+ float u = -4.0f * (float) i / uSteps;
+ float v = -4.0f * (float) j / vSteps;
+ float w0 = (float) j / vSteps;
+ float w1 = 1.0f - w0;
+ grid.set(i, j, x, y, z, u, v, w0, w1, 0, 1);
+ }
+ }
+
+ grid.createBufferObjects(gl);
+ return grid;
+ }
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/view/List7.java b/samples/ApiDemos/src/com/example/android/apis/view/List7.java
index d44ed5672..e773db62a 100644
--- a/samples/ApiDemos/src/com/example/android/apis/view/List7.java
+++ b/samples/ApiDemos/src/com/example/android/apis/view/List7.java
@@ -16,15 +16,12 @@
package com.example.android.apis.view;
-// Need the following import to get access to the app resources, since this
-// class is in a sub-package.
import com.example.android.apis.R;
-
import android.app.ListActivity;
import android.database.Cursor;
-import android.provider.Contacts.People;
import android.os.Bundle;
+import android.provider.ContactsContract;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
@@ -36,46 +33,69 @@ import android.widget.TextView;
* A list view example where the data comes from a cursor.
*/
public class List7 extends ListActivity implements OnItemSelectedListener {
- private static String[] PROJECTION = new String[] {
- People._ID, People.NAME, People.NUMBER
+ private static final String[] PROJECTION = new String[] {
+ ContactsContract.Contacts._ID,
+ ContactsContract.Contacts.DISPLAY_NAME,
+ ContactsContract.Contacts.HAS_PHONE_NUMBER,
+ ContactsContract.Contacts.LOOKUP_KEY
};
+ private int mIdColumnIndex;
+ private int mHasPhoneColumnIndex;
+
+ private TextView mPhone;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+
setContentView(R.layout.list_7);
+
mPhone = (TextView) findViewById(R.id.phone);
getListView().setOnItemSelectedListener(this);
// Get a cursor with all people
- Cursor c = getContentResolver().query(People.CONTENT_URI, PROJECTION, null, null, null);
- startManagingCursor(c);
- mPhoneColumnIndex = c.getColumnIndex(People.NUMBER);
+ Cursor c = managedQuery(ContactsContract.Contacts.CONTENT_URI,
+ PROJECTION, null, null, null);
+ mIdColumnIndex = c.getColumnIndex(ContactsContract.Contacts._ID);
+ mHasPhoneColumnIndex = c.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER);
ListAdapter adapter = new SimpleCursorAdapter(this,
android.R.layout.simple_list_item_1, // Use a template
- // that displays a
- // text view
- c, // Give the cursor to the list adatper
- new String[] {People.NAME}, // Map the NAME column in the
- // people database to...
- new int[] {android.R.id.text1}); // The "text1" view defined in
- // the XML template
+ // that displays a
+ // text view
+ c, // Give the cursor to the list adapter
+ new String[] { ContactsContract.Contacts.DISPLAY_NAME }, // Map the NAME column in the
+ // people database to...
+ new int[] { android.R.id.text1 }); // The "text1" view defined in
+ // the XML template
setListAdapter(adapter);
}
public void onItemSelected(AdapterView parent, View v, int position, long id) {
if (position >= 0) {
- Cursor c = (Cursor) parent.getItemAtPosition(position);
- mPhone.setText(c.getString(mPhoneColumnIndex));
+ final Cursor c = (Cursor) parent.getItemAtPosition(position);
+ if (c.getInt(mHasPhoneColumnIndex) > 0) {
+ final long contactId = c.getLong(mIdColumnIndex);
+ final Cursor phones = getContentResolver().query(
+ ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
+ new String[] { ContactsContract.CommonDataKinds.Phone.NUMBER },
+ ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=" + contactId, null,
+ ContactsContract.CommonDataKinds.Phone.IS_SUPER_PRIMARY + " DESC");
+
+ try {
+ phones.moveToFirst();
+ mPhone.setText(phones.getString(0));
+ } finally {
+ phones.close();
+ }
+ } else {
+ mPhone.setText(R.string.list_7_nothing);
+ }
}
}
public void onNothingSelected(AdapterView parent) {
mPhone.setText(R.string.list_7_nothing);
-
}
-
- private int mPhoneColumnIndex;
- private TextView mPhone;
}
diff --git a/samples/BluetoothChat/src/com/example/android/BluetoothChat/BluetoothChat.java b/samples/BluetoothChat/src/com/example/android/BluetoothChat/BluetoothChat.java
index e4b9d5289..d05bbd611 100644
--- a/samples/BluetoothChat/src/com/example/android/BluetoothChat/BluetoothChat.java
+++ b/samples/BluetoothChat/src/com/example/android/BluetoothChat/BluetoothChat.java
@@ -50,14 +50,13 @@ public class BluetoothChat extends Activity {
// Message types sent from the BluetoothChatService Handler
public static final int MESSAGE_STATE_CHANGE = 1;
public static final int MESSAGE_READ = 2;
- public static final int MESSAGE_OUTGOING_STRING = 3;
+ public static final int MESSAGE_WRITE = 3;
public static final int MESSAGE_DEVICE_NAME = 4;
public static final int MESSAGE_TOAST = 5;
// Key names received from the BluetoothChatService Handler
public static final String DEVICE_NAME = "device_name";
public static final String TOAST = "toast";
- public static final String MESSAGE_WRITE = "message_write";
// Intent request codes
private static final int REQUEST_CONNECT_DEVICE = 1;
@@ -192,9 +191,10 @@ public class BluetoothChat extends Activity {
private void ensureDiscoverable() {
if(D) Log.d(TAG, "ensure discoverable");
- if (mBluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
+ if (mBluetoothAdapter.getScanMode() !=
+ BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
- discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300); // set max duration
+ discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);
}
}
@@ -223,7 +223,8 @@ public class BluetoothChat extends Activity {
}
// The action listener for the EditText widget, to listen for the return key
- private TextView.OnEditorActionListener mWriteListener = new TextView.OnEditorActionListener() {
+ private TextView.OnEditorActionListener mWriteListener =
+ new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView view, int actionId, KeyEvent event) {
// If the action is a key-up event on the return key, send the message
if (actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_UP) {
@@ -257,20 +258,27 @@ public class BluetoothChat extends Activity {
break;
}
break;
- case MESSAGE_OUTGOING_STRING:
- mConversationArrayAdapter.add("Me: " + new String(msg.getData().getByteArray(MESSAGE_WRITE)).trim());
- mOutEditText.setText(mOutStringBuffer);
+ case MESSAGE_WRITE:
+ byte[] writeBuf = (byte[]) msg.obj;
+ // construct a string from the buffer
+ String writeMessage = new String(writeBuf);
+ mConversationArrayAdapter.add("Me: " + writeMessage);
break;
case MESSAGE_READ:
- byte[] buf = (byte[]) msg.obj;
- mConversationArrayAdapter.add(mConnectedDeviceName+": " + new String(buf).trim());
+ byte[] readBuf = (byte[]) msg.obj;
+ // construct a string from the valid bytes in the buffer
+ String readMessage = new String(readBuf, 0, msg.arg1);
+ mConversationArrayAdapter.add(mConnectedDeviceName+": " + readMessage);
break;
case MESSAGE_DEVICE_NAME:
- mConnectedDeviceName = msg.getData().getString(DEVICE_NAME); // save the connected device's name
- Toast.makeText(getApplicationContext(), "Connected to " + mConnectedDeviceName, Toast.LENGTH_SHORT).show();
+ // save the connected device's name
+ mConnectedDeviceName = msg.getData().getString(DEVICE_NAME);
+ Toast.makeText(getApplicationContext(), "Connected to "
+ + mConnectedDeviceName, Toast.LENGTH_SHORT).show();
break;
case MESSAGE_TOAST:
- Toast.makeText(getApplicationContext(), msg.getData().getString(TOAST), Toast.LENGTH_SHORT).show();
+ Toast.makeText(getApplicationContext(), msg.getData().getString(TOAST),
+ Toast.LENGTH_SHORT).show();
break;
}
}
@@ -283,7 +291,8 @@ public class BluetoothChat extends Activity {
// When DeviceListActivity returns with a device to connect
if (resultCode == Activity.RESULT_OK) {
// Get the device MAC address
- String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
+ String address = data.getExtras()
+ .getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
// Get the BLuetoothDevice object
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
// Attempt to connect to the device
diff --git a/samples/BluetoothChat/src/com/example/android/BluetoothChat/BluetoothChatService.java b/samples/BluetoothChat/src/com/example/android/BluetoothChat/BluetoothChatService.java
index 8d29c6a99..d0c1d43cd 100644
--- a/samples/BluetoothChat/src/com/example/android/BluetoothChat/BluetoothChatService.java
+++ b/samples/BluetoothChat/src/com/example/android/BluetoothChat/BluetoothChatService.java
@@ -57,7 +57,7 @@ public class BluetoothChatService {
private int mState;
// Constants that indicate the current connection state
- public static final int STATE_NONE = 0; // we're doing nothing. only valid during setup/shutdown
+ public static final int STATE_NONE = 0; // we're doing nothing
public static final int STATE_LISTEN = 1; // now listening for incoming connections
public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection
public static final int STATE_CONNECTED = 3; // now connected to a remote device
@@ -397,10 +397,8 @@ public class BluetoothChatService {
bytes = mmInStream.read(buffer);
// Send the obtained bytes to the UI Activity
- mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes, -1, buffer).sendToTarget();
-
- // Reload the buffer to clear extra bytes from the previous read
- buffer = new byte[1024];
+ mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes, -1, buffer)
+ .sendToTarget();
} catch (IOException e) {
Log.e(TAG, "disconnected", e);
connectionLost();
@@ -411,18 +409,15 @@ public class BluetoothChatService {
/**
* Write to the connected OutStream.
- * @param b The bytes to write
+ * @param buffer The bytes to write
*/
- public void write(byte[] b) {
+ public void write(byte[] buffer) {
try {
- mmOutStream.write(b);
+ mmOutStream.write(buffer);
// Share the sent message back to the UI Activity
- Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_OUTGOING_STRING);
- Bundle bundle = new Bundle();
- bundle.putByteArray(BluetoothChat.MESSAGE_WRITE, b);
- msg.setData(bundle);
- mHandler.sendMessage(msg);
+ mHandler.obtainMessage(BluetoothChat.MESSAGE_WRITE, -1, -1, buffer)
+ .sendToTarget();
} catch (IOException e) {
Log.e(TAG, "Exception during write", e);
}
diff --git a/samples/BrowserPlugin/jni/background/BackgroundPlugin.cpp b/samples/BrowserPlugin/jni/background/BackgroundPlugin.cpp
index f79d9ac14..699fa4da3 100644
--- a/samples/BrowserPlugin/jni/background/BackgroundPlugin.cpp
+++ b/samples/BrowserPlugin/jni/background/BackgroundPlugin.cpp
@@ -72,6 +72,13 @@ BackgroundPlugin::BackgroundPlugin(NPP inst) : SurfaceSubPlugin(inst) {
test_domAccess();
test_javascript();
test_loadJavaClass();
+
+ //register for touch events
+ ANPEventFlags flags = kTouch_ANPEventFlag;
+ NPError err = browser->setvalue(inst, kAcceptEvents_ANPSetValue, &flags);
+ if (err != NPERR_NO_ERROR) {
+ gLogI.log(kError_ANPLogType, "Error selecting input events.");
+ }
}
BackgroundPlugin::~BackgroundPlugin() {
@@ -170,7 +177,12 @@ int16 BackgroundPlugin::handleEvent(const ANPEvent* evt) {
}
break;
case kTouch_ANPEventType:
- gLogI.log(kError_ANPLogType, " ------ %p the plugin did not request touch events", inst());
+ if (kDown_ANPTouchAction == evt->data.touch.action)
+ return kHandleLongPress_ANPTouchResult | kHandleDoubleTap_ANPTouchResult;
+ else if (kLongPress_ANPTouchAction == evt->data.touch.action)
+ browser->geturl(inst(), "javascript:alert('Detected long press event.')", 0);
+ else if (kDoubleTap_ANPTouchAction == evt->data.touch.action)
+ browser->geturl(inst(), "javascript:alert('Detected double tap event.')", 0);
break;
case kKey_ANPEventType:
gLogI.log(kError_ANPLogType, " ------ %p the plugin did not request key events", inst());
diff --git a/samples/ContactManager/Android.mk b/samples/ContactManager/Android.mk
new file mode 100644
index 000000000..c58571685
--- /dev/null
+++ b/samples/ContactManager/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := samples
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := ContactManager
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
+
+# Use the following include to make our test apk.
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/samples/ContactManager/AndroidManifest.xml b/samples/ContactManager/AndroidManifest.xml
new file mode 100644
index 000000000..c0a01cd31
--- /dev/null
+++ b/samples/ContactManager/AndroidManifest.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ContactManager/_index.html b/samples/ContactManager/_index.html
new file mode 100644
index 000000000..1fd51f06f
--- /dev/null
+++ b/samples/ContactManager/_index.html
@@ -0,0 +1,10 @@
+
A sample application that demonstrates how to manually query the system contacts provider using
+the new ContactsContract API, as
+well as manually insert contacts into a specific account.
+
+
In most cases, you will want to ask the existing Contacts activity to handle these tasks for you,
+resulting in a more consistent user experience.
+
+
+
\ No newline at end of file
diff --git a/samples/ContactManager/res/drawable-hdpi/icon.png b/samples/ContactManager/res/drawable-hdpi/icon.png
new file mode 100644
index 000000000..8074c4c57
Binary files /dev/null and b/samples/ContactManager/res/drawable-hdpi/icon.png differ
diff --git a/samples/ContactManager/res/drawable-ldpi/icon.png b/samples/ContactManager/res/drawable-ldpi/icon.png
new file mode 100644
index 000000000..1095584ec
Binary files /dev/null and b/samples/ContactManager/res/drawable-ldpi/icon.png differ
diff --git a/samples/ContactManager/res/drawable-mdpi/icon.png b/samples/ContactManager/res/drawable-mdpi/icon.png
new file mode 100644
index 000000000..a07c69fa5
Binary files /dev/null and b/samples/ContactManager/res/drawable-mdpi/icon.png differ
diff --git a/samples/ContactManager/res/layout/account_entry.xml b/samples/ContactManager/res/layout/account_entry.xml
new file mode 100644
index 000000000..435e73766
--- /dev/null
+++ b/samples/ContactManager/res/layout/account_entry.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
diff --git a/samples/ContactManager/res/layout/contact_adder.xml b/samples/ContactManager/res/layout/contact_adder.xml
new file mode 100644
index 000000000..92d06f305
--- /dev/null
+++ b/samples/ContactManager/res/layout/contact_adder.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ContactManager/res/layout/contact_entry.xml b/samples/ContactManager/res/layout/contact_entry.xml
new file mode 100644
index 000000000..9909025a7
--- /dev/null
+++ b/samples/ContactManager/res/layout/contact_entry.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
diff --git a/samples/ContactManager/res/layout/contact_manager.xml b/samples/ContactManager/res/layout/contact_manager.xml
new file mode 100644
index 000000000..73f6f8151
--- /dev/null
+++ b/samples/ContactManager/res/layout/contact_manager.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
diff --git a/samples/ContactManager/res/values/strings.xml b/samples/ContactManager/res/values/strings.xml
new file mode 100644
index 000000000..c7a65b4a0
--- /dev/null
+++ b/samples/ContactManager/res/values/strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ Account
+ Add Contact
+ Add Contact
+ All Accounts
+ Contact Manager
+ Contact creation failed, check logs.
+ Contact Email
+ Contact Name
+ Contact Phone
+ Save
+ Select
+ Select label
+ Show Invisible Contacts (Only)
+ Target Account
+ (Undefined)
+
diff --git a/samples/ContactManager/src/com/google/example/android/contactmanager/ContactAdder.java b/samples/ContactManager/src/com/google/example/android/contactmanager/ContactAdder.java
new file mode 100644
index 000000000..92ad5a237
--- /dev/null
+++ b/samples/ContactManager/src/com/google/example/android/contactmanager/ContactAdder.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2009 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.example.android.contactmanager;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorDescription;
+import android.accounts.OnAccountsUpdateListener;
+import android.app.Activity;
+import android.content.ContentProviderOperation;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.widget.AdapterView.OnItemSelectedListener;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+public final class ContactAdder extends Activity implements OnAccountsUpdateListener
+{
+ public static final String TAG = "ContactsAdder";
+ public static final String ACCOUNT_NAME =
+ "com.google.example.android.contactmanager.ContactsAdder.ACCOUNT_NAME";
+ public static final String ACCOUNT_TYPE =
+ "com.google.example.android.contactmanager.ContactsAdder.ACCOUNT_TYPE";
+
+ private ArrayList mAccounts;
+ private AccountAdapter mAccountAdapter;
+ private Spinner mAccountSpinner;
+ private EditText mContactEmailEditText;
+ private ArrayList mContactEmailTypes;
+ private Spinner mContactEmailTypeSpinner;
+ private EditText mContactNameEditText;
+ private EditText mContactPhoneEditText;
+ private ArrayList mContactPhoneTypes;
+ private Spinner mContactPhoneTypeSpinner;
+ private Button mContactSaveButton;
+ private AccountData mSelectedAccount;
+
+ /**
+ * Called when the activity is first created. Responsible for initializing the UI.
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ Log.v(TAG, "Activity State: onCreate()");
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.contact_adder);
+
+ // Obtain handles to UI objects
+ mAccountSpinner = (Spinner) findViewById(R.id.accountSpinner);
+ mContactNameEditText = (EditText) findViewById(R.id.contactNameEditText);
+ mContactPhoneEditText = (EditText) findViewById(R.id.contactPhoneEditText);
+ mContactEmailEditText = (EditText) findViewById(R.id.contactEmailEditText);
+ mContactPhoneTypeSpinner = (Spinner) findViewById(R.id.contactPhoneTypeSpinner);
+ mContactEmailTypeSpinner = (Spinner) findViewById(R.id.contactEmailTypeSpinner);
+ mContactSaveButton = (Button) findViewById(R.id.contactSaveButton);
+
+ // Prepare list of supported account types
+ // Note: Other types are available in ContactsContract.CommonDataKinds
+ // Also, be aware that type IDs differ between Phone and Email, and MUST be computed
+ // separately.
+ mContactPhoneTypes = new ArrayList();
+ mContactPhoneTypes.add(ContactsContract.CommonDataKinds.Phone.TYPE_HOME);
+ mContactPhoneTypes.add(ContactsContract.CommonDataKinds.Phone.TYPE_WORK);
+ mContactPhoneTypes.add(ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);
+ mContactPhoneTypes.add(ContactsContract.CommonDataKinds.Phone.TYPE_OTHER);
+ mContactEmailTypes = new ArrayList();
+ mContactEmailTypes.add(ContactsContract.CommonDataKinds.Email.TYPE_HOME);
+ mContactEmailTypes.add(ContactsContract.CommonDataKinds.Email.TYPE_WORK);
+ mContactEmailTypes.add(ContactsContract.CommonDataKinds.Email.TYPE_MOBILE);
+ mContactEmailTypes.add(ContactsContract.CommonDataKinds.Email.TYPE_OTHER);
+
+ // Prepare model for account spinner
+ mAccounts = new ArrayList();
+ mAccountAdapter = new AccountAdapter(this, mAccounts);
+ mAccountSpinner.setAdapter(mAccountAdapter);
+
+ // Populate list of account types for phone
+ ArrayAdapter adapter;
+ adapter = new ArrayAdapter(this, android.R.layout.simple_spinner_item);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ Iterator iter;
+ iter = mContactPhoneTypes.iterator();
+ while (iter.hasNext()) {
+ adapter.add(ContactsContract.CommonDataKinds.Phone.getTypeLabel(
+ this.getResources(),
+ iter.next(),
+ getString(R.string.undefinedTypeLabel)).toString());
+ }
+ mContactPhoneTypeSpinner.setAdapter(adapter);
+ mContactPhoneTypeSpinner.setPrompt(getString(R.string.selectLabel));
+
+ // Populate list of account types for email
+ adapter = new ArrayAdapter(this, android.R.layout.simple_spinner_item);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ iter = mContactEmailTypes.iterator();
+ while (iter.hasNext()) {
+ adapter.add(ContactsContract.CommonDataKinds.Email.getTypeLabel(
+ this.getResources(),
+ iter.next(),
+ getString(R.string.undefinedTypeLabel)).toString());
+ }
+ mContactEmailTypeSpinner.setAdapter(adapter);
+ mContactEmailTypeSpinner.setPrompt(getString(R.string.selectLabel));
+
+ // Prepare the system account manager. On registering the listener below, we also ask for
+ // an initial callback to pre-populate the account list.
+ AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true);
+
+ // Register handlers for UI elements
+ mAccountSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
+ public void onItemSelected(AdapterView> parent, View view, int position, long i) {
+ updateAccountSelection();
+ }
+
+ public void onNothingSelected(AdapterView> parent) {
+ // We don't need to worry about nothing being selected, since Spinners don't allow
+ // this.
+ }
+ });
+ mContactSaveButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ onSaveButtonClicked();
+ }
+ });
+ }
+
+ /**
+ * Actions for when the Save button is clicked. Creates a contact entry and terminates the
+ * activity.
+ */
+ private void onSaveButtonClicked() {
+ Log.v(TAG, "Save button clicked");
+ createContactEntry();
+ finish();
+ }
+
+ /**
+ * Creates a contact entry from the current UI values in the account named by mSelectedAccount.
+ */
+ protected void createContactEntry() {
+ // Get values from UI
+ String name = mContactNameEditText.getText().toString();
+ String phone = mContactPhoneEditText.getText().toString();
+ String email = mContactEmailEditText.getText().toString();
+ int phoneType = mContactPhoneTypes.get(
+ mContactPhoneTypeSpinner.getSelectedItemPosition());
+ int emailType = mContactEmailTypes.get(
+ mContactEmailTypeSpinner.getSelectedItemPosition());;
+
+ // Prepare contact creation request
+ //
+ // Note: We use RawContacts because this data must be associated with a particular account.
+ // The system will aggregate this with any other data for this contact and create a
+ // coresponding entry in the ContactsContract.Contacts provider for us.
+ ArrayList ops = new ArrayList();
+ ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
+ .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType())
+ .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName())
+ .build());
+ ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+ .withValue(ContactsContract.Data.MIMETYPE,
+ ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
+ .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name)
+ .build());
+ ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+ .withValue(ContactsContract.Data.MIMETYPE,
+ ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
+ .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
+ .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType)
+ .build());
+ ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+ .withValue(ContactsContract.Data.MIMETYPE,
+ ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
+ .withValue(ContactsContract.CommonDataKinds.Email.DATA, email)
+ .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType)
+ .build());
+
+ // Ask the Contact provider to create a new contact
+ Log.i(TAG,"Selected account: " + mSelectedAccount.getName() + " (" +
+ mSelectedAccount.getType() + ")");
+ Log.i(TAG,"Creating contact: " + name);
+ try {
+ getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
+ } catch (Exception e) {
+ // Display warning
+ Context ctx = getApplicationContext();
+ CharSequence txt = getString(R.string.contactCreationFailure);
+ int duration = Toast.LENGTH_SHORT;
+ Toast toast = Toast.makeText(ctx, txt, duration);
+ toast.show();
+
+ // Log exception
+ Log.e(TAG, "Exceptoin encoutered while inserting contact: " + e);
+ }
+ }
+
+ /**
+ * Called when this activity is about to be destroyed by the system.
+ */
+ @Override
+ public void onDestroy() {
+ // Remove AccountManager callback
+ AccountManager.get(this).removeOnAccountsUpdatedListener(this);
+ super.onDestroy();
+ }
+
+ /**
+ * Updates account list spinner when the list of Accounts on the system changes. Satisfies
+ * OnAccountsUpdateListener implementation.
+ */
+ public void onAccountsUpdated(Account[] a) {
+ Log.i(TAG, "Account list update detected");
+ // Clear out any old data to prevent duplicates
+ mAccounts.clear();
+
+ // Get account data from system
+ AuthenticatorDescription[] accountTypes = AccountManager.get(this).getAuthenticatorTypes();
+
+ // Populate tables
+ for (int i = 0; i < a.length; i++) {
+ // The user may have multiple accounts with the same name, so we need to construct a
+ // meaningful display name for each.
+ String systemAccountType = a[i].type;
+ AuthenticatorDescription ad = getAuthenticatorDescription(systemAccountType,
+ accountTypes);
+ AccountData data = new AccountData(a[i].name, ad);
+ mAccounts.add(data);
+ }
+
+ // Update the account spinner
+ mAccountAdapter.notifyDataSetChanged();
+ }
+
+ /**
+ * Obtain the AuthenticatorDescription for a given account type.
+ * @param type The account type to locate.
+ * @param dictionary An array of AuthenticatorDescriptions, as returned by AccountManager.
+ * @return The description for the specified account type.
+ */
+ private static AuthenticatorDescription getAuthenticatorDescription(String type,
+ AuthenticatorDescription[] dictionary) {
+ for (int i = 0; i < dictionary.length; i++) {
+ if (dictionary[i].type.equals(type)) {
+ return dictionary[i];
+ }
+ }
+ // No match found
+ throw new RuntimeException("Unable to find matching authenticator");
+ }
+
+ /**
+ * Update account selection. If NO_ACCOUNT is selected, then we prohibit inserting new contacts.
+ */
+ private void updateAccountSelection() {
+ // Read current account selection
+ mSelectedAccount = (AccountData) mAccountSpinner.getSelectedItem();
+ }
+
+ /**
+ * A container class used to repreresent all known information about an account.
+ */
+ private class AccountData {
+ private String mName;
+ private String mType;
+ private CharSequence mTypeLabel;
+ private Drawable mIcon;
+
+ /**
+ * @param name The name of the account. This is usually the user's email address or
+ * username.
+ * @param description The description for this account. This will be dictated by the
+ * type of account returned, and can be obtained from the system AccountManager.
+ */
+ public AccountData(String name, AuthenticatorDescription description) {
+ mName = name;
+ if (description != null) {
+ mType = description.type;
+
+ // The type string is stored in a resource, so we need to convert it into something
+ // human readable.
+ String packageName = description.packageName;
+ PackageManager pm = getPackageManager();
+
+ if (description.labelId != 0) {
+ mTypeLabel = pm.getText(packageName, description.labelId, null);
+ if (mTypeLabel == null) {
+ throw new IllegalArgumentException("LabelID provided, but label not found");
+ }
+ } else {
+ mTypeLabel = "";
+ }
+
+ if (description.iconId != 0) {
+ mIcon = pm.getDrawable(packageName, description.iconId, null);
+ if (mIcon == null) {
+ throw new IllegalArgumentException("IconID provided, but drawable not " +
+ "found");
+ }
+ } else {
+ mIcon = getResources().getDrawable(android.R.drawable.sym_def_app_icon);
+ }
+ }
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public String getType() {
+ return mType;
+ }
+
+ public CharSequence getTypeLabel() {
+ return mTypeLabel;
+ }
+
+ public Drawable getIcon() {
+ return mIcon;
+ }
+
+ public String toString() {
+ return mName;
+ }
+ }
+
+ /**
+ * Custom adapter used to display account icons and descriptions in the account spinner.
+ */
+ private class AccountAdapter extends ArrayAdapter {
+ public AccountAdapter(Context context, ArrayList accountData) {
+ super(context, android.R.layout.simple_spinner_item, accountData);
+ setDropDownViewResource(R.layout.account_entry);
+ }
+
+ public View getDropDownView(int position, View convertView, ViewGroup parent) {
+ // Inflate a view template
+ if (convertView == null) {
+ LayoutInflater layoutInflater = getLayoutInflater();
+ convertView = layoutInflater.inflate(R.layout.account_entry, parent, false);
+ }
+ TextView firstAccountLine = (TextView) convertView.findViewById(R.id.firstAccountLine);
+ TextView secondAccountLine = (TextView) convertView.findViewById(R.id.secondAccountLine);
+ ImageView accountIcon = (ImageView) convertView.findViewById(R.id.accountIcon);
+
+ // Populate template
+ AccountData data = getItem(position);
+ firstAccountLine.setText(data.getName());
+ secondAccountLine.setText(data.getTypeLabel());
+ Drawable icon = data.getIcon();
+ if (icon == null) {
+ icon = getResources().getDrawable(android.R.drawable.ic_menu_search);
+ }
+ accountIcon.setImageDrawable(icon);
+ return convertView;
+ }
+ }
+}
diff --git a/samples/ContactManager/src/com/google/example/android/contactmanager/ContactManager.java b/samples/ContactManager/src/com/google/example/android/contactmanager/ContactManager.java
new file mode 100644
index 000000000..7b416b084
--- /dev/null
+++ b/samples/ContactManager/src/com/google/example/android/contactmanager/ContactManager.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2009 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.example.android.contactmanager;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
+
+public final class ContactManager extends Activity
+{
+
+ public static final String TAG = "ContactManager";
+
+ private Button mAddAccountButton;
+ private ListView mContactList;
+ private boolean mShowInvisible;
+ private CheckBox mShowInvisibleControl;
+
+ /**
+ * Called when the activity is first created. Responsible for initializing the UI.
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ Log.v(TAG, "Activity State: onCreate()");
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.contact_manager);
+
+ // Obtain handles to UI objects
+ mAddAccountButton = (Button) findViewById(R.id.addContactButton);
+ mContactList = (ListView) findViewById(R.id.contactList);
+ mShowInvisibleControl = (CheckBox) findViewById(R.id.showInvisible);
+
+ // Initialize class properties
+ mShowInvisible = false;
+ mShowInvisibleControl.setChecked(mShowInvisible);
+
+ // Register handler for UI elements
+ mAddAccountButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ Log.d(TAG, "mAddAccountButton clicked");
+ launchContactAdder();
+ }
+ });
+ mShowInvisibleControl.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ Log.d(TAG, "mShowInvisibleControl changed: " + isChecked);
+ mShowInvisible = isChecked;
+ populateContactList();
+ }
+ });
+
+ // Populate the contact list
+ populateContactList();
+ }
+
+ /**
+ * Populate the contact list based on account currently selected in the account spinner.
+ */
+ private void populateContactList() {
+ // Build adapter with contact entries
+ Cursor cursor = getContacts();
+ String[] fields = new String[] {
+ ContactsContract.Data.DISPLAY_NAME
+ };
+ SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, R.layout.contact_entry, cursor,
+ fields, new int[] {R.id.contactEntryText});
+ mContactList.setAdapter(adapter);
+ }
+
+ /**
+ * Obtains the contact list for the currently selected account.
+ *
+ * @return A cursor for for accessing the contact list.
+ */
+ private Cursor getContacts()
+ {
+ // Run query
+ Uri uri = ContactsContract.Contacts.CONTENT_URI;
+ String[] projection = new String[] {
+ ContactsContract.Contacts._ID,
+ ContactsContract.Contacts.DISPLAY_NAME
+ };
+ String selection = ContactsContract.Contacts.IN_VISIBLE_GROUP + " = '" +
+ (mShowInvisible ? "0" : "1") + "'";
+ String[] selectionArgs = null;
+ String sortOrder = ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC";
+
+ return managedQuery(uri, projection, selection, selectionArgs, sortOrder);
+ }
+
+ /**
+ * Launches the ContactAdder activity to add a new contact to the selected accont.
+ */
+ protected void launchContactAdder() {
+ Intent i = new Intent(this, ContactAdder.class);
+ startActivity(i);
+ }
+}
diff --git a/samples/MultiResolution/_index.html b/samples/MultiResolution/_index.html
index 73089ae64..825ea4da4 100644
--- a/samples/MultiResolution/_index.html
+++ b/samples/MultiResolution/_index.html
@@ -17,4 +17,4 @@ It demonstrates using:
-
+
diff --git a/samples/Wiktionary/Android.mk b/samples/Wiktionary/Android.mk
new file mode 100644
index 000000000..d6ce1f16c
--- /dev/null
+++ b/samples/Wiktionary/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := samples
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := Wiktionary
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
+
+# Use the following include to make our test apk.
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/samples/Wiktionary/AndroidManifest.xml b/samples/Wiktionary/AndroidManifest.xml
new file mode 100644
index 000000000..1641a8b0a
--- /dev/null
+++ b/samples/Wiktionary/AndroidManifest.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/Wiktionary/_index.html b/samples/Wiktionary/_index.html
new file mode 100644
index 000000000..507dda331
--- /dev/null
+++ b/samples/Wiktionary/_index.html
@@ -0,0 +1,10 @@
+
A sample application that demonstrates how to create an interactive Android
+home screen widget that launches an application activity.
+
+
When installed, this adds a "Wiktionary" option to the widget installation
+menu. The word of the day is downloaded from Wiktionary and displayed in a
+frame. Touching the widget will launch a custom application activity showing
+more details.
+
+
+
diff --git a/samples/Wiktionary/res/anim/slide_in.xml b/samples/Wiktionary/res/anim/slide_in.xml
new file mode 100644
index 000000000..3da074e09
--- /dev/null
+++ b/samples/Wiktionary/res/anim/slide_in.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
diff --git a/samples/Wiktionary/res/anim/slide_out.xml b/samples/Wiktionary/res/anim/slide_out.xml
new file mode 100644
index 000000000..ec21f521e
--- /dev/null
+++ b/samples/Wiktionary/res/anim/slide_out.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
diff --git a/samples/Wiktionary/res/drawable/app_icon.png b/samples/Wiktionary/res/drawable/app_icon.png
new file mode 100644
index 000000000..2b1417aad
Binary files /dev/null and b/samples/Wiktionary/res/drawable/app_icon.png differ
diff --git a/samples/Wiktionary/res/drawable/ic_menu_shuffle.png b/samples/Wiktionary/res/drawable/ic_menu_shuffle.png
new file mode 100755
index 000000000..cb7009dea
Binary files /dev/null and b/samples/Wiktionary/res/drawable/ic_menu_shuffle.png differ
diff --git a/samples/Wiktionary/res/drawable/logo_overlay.9.png b/samples/Wiktionary/res/drawable/logo_overlay.9.png
new file mode 100644
index 000000000..851ceb15b
Binary files /dev/null and b/samples/Wiktionary/res/drawable/logo_overlay.9.png differ
diff --git a/samples/Wiktionary/res/drawable/lookup_bg.xml b/samples/Wiktionary/res/drawable/lookup_bg.xml
new file mode 100644
index 000000000..46d76eb9b
--- /dev/null
+++ b/samples/Wiktionary/res/drawable/lookup_bg.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
diff --git a/samples/Wiktionary/res/drawable/progress_spin.xml b/samples/Wiktionary/res/drawable/progress_spin.xml
new file mode 100644
index 000000000..4594a181b
--- /dev/null
+++ b/samples/Wiktionary/res/drawable/progress_spin.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/Wiktionary/res/drawable/star_logo.png b/samples/Wiktionary/res/drawable/star_logo.png
new file mode 100644
index 000000000..b32d1756c
Binary files /dev/null and b/samples/Wiktionary/res/drawable/star_logo.png differ
diff --git a/samples/Wiktionary/res/drawable/widget_bg.xml b/samples/Wiktionary/res/drawable/widget_bg.xml
new file mode 100644
index 000000000..c2b846277
--- /dev/null
+++ b/samples/Wiktionary/res/drawable/widget_bg.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
diff --git a/samples/Wiktionary/res/drawable/widget_bg_normal.9.png b/samples/Wiktionary/res/drawable/widget_bg_normal.9.png
new file mode 100644
index 000000000..314eb8ef9
Binary files /dev/null and b/samples/Wiktionary/res/drawable/widget_bg_normal.9.png differ
diff --git a/samples/Wiktionary/res/drawable/widget_bg_pressed.9.png b/samples/Wiktionary/res/drawable/widget_bg_pressed.9.png
new file mode 100644
index 000000000..cc23e787b
Binary files /dev/null and b/samples/Wiktionary/res/drawable/widget_bg_pressed.9.png differ
diff --git a/samples/Wiktionary/res/drawable/widget_bg_selected.9.png b/samples/Wiktionary/res/drawable/widget_bg_selected.9.png
new file mode 100644
index 000000000..ef0cdc066
Binary files /dev/null and b/samples/Wiktionary/res/drawable/widget_bg_selected.9.png differ
diff --git a/samples/Wiktionary/res/layout/about.xml b/samples/Wiktionary/res/layout/about.xml
new file mode 100644
index 000000000..3b25b3274
--- /dev/null
+++ b/samples/Wiktionary/res/layout/about.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/Wiktionary/res/layout/lookup.xml b/samples/Wiktionary/res/layout/lookup.xml
new file mode 100644
index 000000000..43cffaa1a
--- /dev/null
+++ b/samples/Wiktionary/res/layout/lookup.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/Wiktionary/res/layout/widget_message.xml b/samples/Wiktionary/res/layout/widget_message.xml
new file mode 100644
index 000000000..ba9471447
--- /dev/null
+++ b/samples/Wiktionary/res/layout/widget_message.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
diff --git a/samples/Wiktionary/res/layout/widget_word.xml b/samples/Wiktionary/res/layout/widget_word.xml
new file mode 100644
index 000000000..0e76f0b80
--- /dev/null
+++ b/samples/Wiktionary/res/layout/widget_word.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/Wiktionary/res/menu/lookup.xml b/samples/Wiktionary/res/menu/lookup.xml
new file mode 100644
index 000000000..741ca9ab2
--- /dev/null
+++ b/samples/Wiktionary/res/menu/lookup.xml
@@ -0,0 +1,34 @@
+
+
+
+
diff --git a/samples/Wiktionary/res/values/strings.xml b/samples/Wiktionary/res/values/strings.xml
new file mode 100644
index 000000000..38d993768
--- /dev/null
+++ b/samples/Wiktionary/res/values/strings.xml
@@ -0,0 +1,56 @@
+
+
+
+
+ Wiktionary example
+ Example of a fast Wiktionary browser and Word-of-day widget
+ "All dictionary content provided by Wiktionary under a GFDL license. http://en.wiktionary.org\n\nIcon derived from Tango Desktop Project under a public domain license. http://tango.freedesktop.org"
+
+ "%s/%s (Linux; Android)"
+ "Wiktionary:Word of the day/%s %s"
+ "http://en.wiktionary.org/wiki/%s"
+
+ Wiktionary
+
+ "Loading word\nof day\u2026"
+ No word of day found
+
+
+ January
+ February
+ March
+ April
+ May
+ June
+ July
+ August
+ September
+ October
+ November
+ December
+
+
+
+ Wiktionary search
+ Define word
+
+ Search
+ Random
+ About
+
+ No entry found for this word, or problem reading data.
+
+
diff --git a/samples/Wiktionary/res/values/styles.xml b/samples/Wiktionary/res/values/styles.xml
new file mode 100644
index 000000000..45fc8f5a4
--- /dev/null
+++ b/samples/Wiktionary/res/values/styles.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/Wiktionary/res/values/themes.xml b/samples/Wiktionary/res/values/themes.xml
new file mode 100644
index 000000000..c4d7630f6
--- /dev/null
+++ b/samples/Wiktionary/res/values/themes.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
diff --git a/samples/Wiktionary/res/xml/searchable.xml b/samples/Wiktionary/res/xml/searchable.xml
new file mode 100644
index 000000000..02ee31fc5
--- /dev/null
+++ b/samples/Wiktionary/res/xml/searchable.xml
@@ -0,0 +1,19 @@
+
+
+
+
diff --git a/samples/Wiktionary/res/xml/widget_word.xml b/samples/Wiktionary/res/xml/widget_word.xml
new file mode 100644
index 000000000..46d31c321
--- /dev/null
+++ b/samples/Wiktionary/res/xml/widget_word.xml
@@ -0,0 +1,21 @@
+
+
+
+
diff --git a/samples/Wiktionary/src/com/example/android/wiktionary/ExtendedWikiHelper.java b/samples/Wiktionary/src/com/example/android/wiktionary/ExtendedWikiHelper.java
new file mode 100644
index 000000000..3a3917248
--- /dev/null
+++ b/samples/Wiktionary/src/com/example/android/wiktionary/ExtendedWikiHelper.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2009 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.wiktionary;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.net.Uri;
+import android.text.TextUtils;
+import android.webkit.WebView;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Extended version of {@link SimpleWikiHelper}. This version adds methods to
+ * pick a random word, and to format generic wiki-style text into HTML.
+ */
+public class ExtendedWikiHelper extends SimpleWikiHelper {
+ /**
+ * HTML style sheet to include with any {@link #formatWikiText(String)} HTML
+ * results. It formats nicely for a mobile screen, and hides some content
+ * boxes to keep things tidy.
+ */
+ private static final String STYLE_SHEET = "";
+
+ /**
+ * Pattern of section titles we're interested in showing. This trims out
+ * extra sections that can clutter things up on a mobile screen.
+ */
+ private static final Pattern sValidSections =
+ Pattern.compile("(verb|noun|adjective|pronoun|interjection)", Pattern.CASE_INSENSITIVE);
+
+ /**
+ * Pattern that can be used to split a returned wiki page into its various
+ * sections. Doesn't treat children sections differently.
+ */
+ private static final Pattern sSectionSplit =
+ Pattern.compile("^=+(.+?)=+.+?(?=^=)", Pattern.MULTILINE | Pattern.DOTALL);
+
+ /**
+ * When picking random words in {@link #getRandomWord()}, we sometimes
+ * encounter special articles or templates. This pattern ignores any words
+ * like those, usually because they have ":" or other punctuation.
+ */
+ private static final Pattern sInvalidWord = Pattern.compile("[^A-Za-z0-9 ]");
+
+ /**
+ * {@link Uri} authority to use when creating internal links.
+ */
+ public static final String WIKI_AUTHORITY = "wiktionary";
+
+ /**
+ * {@link Uri} host to use when creating internal links.
+ */
+ public static final String WIKI_LOOKUP_HOST = "lookup";
+
+ /**
+ * Mime-type to use when showing parsed results in a {@link WebView}.
+ */
+ public static final String MIME_TYPE = "text/html";
+
+ /**
+ * Encoding to use when showing parsed results in a {@link WebView}.
+ */
+ public static final String ENCODING = "utf-8";
+
+ /**
+ * {@link Uri} to use when requesting a random page.
+ */
+ private static final String WIKTIONARY_RANDOM =
+ "http://en.wiktionary.org/w/api.php?action=query&list=random&format=json";
+
+ /**
+ * Fake section to insert at the bottom of a wiki response before parsing.
+ * This ensures that {@link #sSectionSplit} will always catch the last
+ * section, as it uses section headers in its searching.
+ */
+ private static final String STUB_SECTION = "\n=Stub section=";
+
+ /**
+ * Number of times to try finding a random word in {@link #getRandomWord()}.
+ * These failures are usually when the found word fails the
+ * {@link #sInvalidWord} test, or when a network error happens.
+ */
+ private static final int RANDOM_TRIES = 3;
+
+ /**
+ * Internal class to hold a wiki formatting rule. It's mostly a wrapper to
+ * simplify {@link Matcher#replaceAll(String)}.
+ */
+ private static class FormatRule {
+ private Pattern mPattern;
+ private String mReplaceWith;
+
+ /**
+ * Create a wiki formatting rule.
+ *
+ * @param pattern Search string to be compiled into a {@link Pattern}.
+ * @param replaceWith String to replace any found occurances with. This
+ * string can also include back-references into the given
+ * pattern.
+ * @param flags Any flags to compile the {@link Pattern} with.
+ */
+ public FormatRule(String pattern, String replaceWith, int flags) {
+ mPattern = Pattern.compile(pattern, flags);
+ mReplaceWith = replaceWith;
+ }
+
+ /**
+ * Create a wiki formatting rule.
+ *
+ * @param pattern Search string to be compiled into a {@link Pattern}.
+ * @param replaceWith String to replace any found occurances with. This
+ * string can also include back-references into the given
+ * pattern.
+ */
+ public FormatRule(String pattern, String replaceWith) {
+ this(pattern, replaceWith, 0);
+ }
+
+ /**
+ * Apply this formatting rule to the given input string, and return the
+ * resulting new string.
+ */
+ public String apply(String input) {
+ Matcher m = mPattern.matcher(input);
+ return m.replaceAll(mReplaceWith);
+ }
+
+ }
+
+ /**
+ * List of internal formatting rules to apply when parsing wiki text. These
+ * include indenting various bullets, apply italic and bold styles, and
+ * adding internal linking.
+ */
+ private static final List sFormatRules = new ArrayList();
+
+ static {
+ // Format header blocks and wrap outside content in ordered list
+ sFormatRules.add(new FormatRule("^=+(.+?)=+", "
",
+ Pattern.MULTILINE));
+
+ // Add internal links
+ sFormatRules.add(new FormatRule("\\[\\[([^:\\|\\]]+)\\]\\]",
+ String.format("$1", WIKI_AUTHORITY, WIKI_LOOKUP_HOST)));
+ sFormatRules.add(new FormatRule("\\[\\[([^:\\|\\]]+)\\|([^\\]]+)\\]\\]",
+ String.format("$2", WIKI_AUTHORITY, WIKI_LOOKUP_HOST)));
+
+ // Add bold and italic formatting
+ sFormatRules.add(new FormatRule("'''(.+?)'''", "$1"));
+ sFormatRules.add(new FormatRule("([^'])''([^'].*?[^'])''([^'])", "$1$2$3"));
+
+ // Remove odd category links and convert remaining links into flat text
+ sFormatRules.add(new FormatRule("(\\{+.+?\\}+|\\[\\[[^:]+:[^\\\\|\\]]+\\]\\]|" +
+ "\\[http.+?\\]|\\[\\[Category:.+?\\]\\])", "", Pattern.MULTILINE | Pattern.DOTALL));
+ sFormatRules.add(new FormatRule("\\[\\[([^\\|\\]]+\\|)?(.+?)\\]\\]", "$2",
+ Pattern.MULTILINE));
+
+ }
+
+ /**
+ * Query the Wiktionary API to pick a random dictionary word. Will try
+ * multiple times to find a valid word before giving up.
+ *
+ * @return Random dictionary word, or null if no valid word was found.
+ * @throws ApiException If any connection or server error occurs.
+ * @throws ParseException If there are problems parsing the response.
+ */
+ public static String getRandomWord() throws ApiException, ParseException {
+ // Keep trying a few times until we find a valid word
+ int tries = 0;
+ while (tries++ < RANDOM_TRIES) {
+ // Query the API for a random word
+ String content = getUrlContent(WIKTIONARY_RANDOM);
+ try {
+ // Drill into the JSON response to find the returned word
+ JSONObject response = new JSONObject(content);
+ JSONObject query = response.getJSONObject("query");
+ JSONArray random = query.getJSONArray("random");
+ JSONObject word = random.getJSONObject(0);
+ String foundWord = word.getString("title");
+
+ // If we found an actual word, and it wasn't rejected by our invalid
+ // filter, then accept and return it.
+ if (foundWord != null &&
+ !sInvalidWord.matcher(foundWord).find()) {
+ return foundWord;
+ }
+ } catch (JSONException e) {
+ throw new ParseException("Problem parsing API response", e);
+ }
+ }
+
+ // No valid word found in number of tries, so return null
+ return null;
+ }
+
+ /**
+ * Format the given wiki-style text into formatted HTML content. This will
+ * create headers, lists, internal links, and style formatting for any wiki
+ * markup found.
+ *
+ * @param wikiText The raw text to format, with wiki-markup included.
+ * @return HTML formatted content, ready for display in {@link WebView}.
+ */
+ public static String formatWikiText(String wikiText) {
+ if (wikiText == null) {
+ return null;
+ }
+
+ // Insert a fake last section into the document so our section splitter
+ // can correctly catch the last section.
+ wikiText = wikiText.concat(STUB_SECTION);
+
+ // Read through all sections, keeping only those matching our filter,
+ // and only including the first entry for each title.
+ HashSet foundSections = new HashSet();
+ StringBuilder builder = new StringBuilder();
+
+ Matcher sectionMatcher = sSectionSplit.matcher(wikiText);
+ while (sectionMatcher.find()) {
+ String title = sectionMatcher.group(1);
+ if (!foundSections.contains(title) &&
+ sValidSections.matcher(title).matches()) {
+ String sectionContent = sectionMatcher.group();
+ foundSections.add(title);
+ builder.append(sectionContent);
+ }
+ }
+
+ // Our new wiki text is the selected sections only
+ wikiText = builder.toString();
+
+ // Apply all formatting rules, in order, to the wiki text
+ for (FormatRule rule : sFormatRules) {
+ wikiText = rule.apply(wikiText);
+ }
+
+ // Return the resulting HTML with style sheet, if we have content left
+ if (!TextUtils.isEmpty(wikiText)) {
+ return STYLE_SHEET + wikiText;
+ } else {
+ return null;
+ }
+ }
+
+}
diff --git a/samples/Wiktionary/src/com/example/android/wiktionary/LookupActivity.java b/samples/Wiktionary/src/com/example/android/wiktionary/LookupActivity.java
new file mode 100644
index 000000000..6cc231b8c
--- /dev/null
+++ b/samples/Wiktionary/src/com/example/android/wiktionary/LookupActivity.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2009 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.wiktionary;
+
+import com.example.android.wiktionary.SimpleWikiHelper.ApiException;
+import com.example.android.wiktionary.SimpleWikiHelper.ParseException;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.SearchManager;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Animation.AnimationListener;
+import android.webkit.WebView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import java.util.Stack;
+
+/**
+ * Activity that lets users browse through Wiktionary content. This is just the
+ * user interface, and all API communication and parsing is handled in
+ * {@link ExtendedWikiHelper}.
+ */
+public class LookupActivity extends Activity implements AnimationListener {
+ private static final String TAG = "LookupActivity";
+
+ private View mTitleBar;
+ private TextView mTitle;
+ private ProgressBar mProgress;
+ private WebView mWebView;
+
+ private Animation mSlideIn;
+ private Animation mSlideOut;
+
+ /**
+ * History stack of previous words browsed in this session. This is
+ * referenced when the user taps the "back" key, to possibly intercept and
+ * show the last-visited entry, instead of closing the activity.
+ */
+ private Stack mHistory = new Stack();
+
+ private String mEntryTitle;
+
+ /**
+ * Keep track of last time user tapped "back" hard key. When pressed more
+ * than once within {@link #BACK_THRESHOLD}, we treat let the back key fall
+ * through and close the app.
+ */
+ private long mLastPress = -1;
+
+ private static final long BACK_THRESHOLD = DateUtils.SECOND_IN_MILLIS / 2;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.lookup);
+
+ // Load animations used to show/hide progress bar
+ mSlideIn = AnimationUtils.loadAnimation(this, R.anim.slide_in);
+ mSlideOut = AnimationUtils.loadAnimation(this, R.anim.slide_out);
+
+ // Listen for the "in" animation so we make the progress bar visible
+ // only after the sliding has finished.
+ mSlideIn.setAnimationListener(this);
+
+ mTitleBar = findViewById(R.id.title_bar);
+ mTitle = (TextView) findViewById(R.id.title);
+ mProgress = (ProgressBar) findViewById(R.id.progress);
+ mWebView = (WebView) findViewById(R.id.webview);
+
+ // Make the view transparent to show background
+ mWebView.setBackgroundColor(0);
+
+ // Prepare User-Agent string for wiki actions
+ ExtendedWikiHelper.prepareUserAgent(this);
+
+ // Handle incoming intents as possible searches or links
+ onNewIntent(getIntent());
+ }
+
+ /**
+ * Intercept the back-key to try walking backwards along our word history
+ * stack. If we don't have any remaining history, the key behaves normally
+ * and closes this activity.
+ */
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ // Handle back key as long we have a history stack
+ if (keyCode == KeyEvent.KEYCODE_BACK && !mHistory.empty()) {
+
+ // Compare against last pressed time, and if user hit multiple times
+ // in quick succession, we should consider bailing out early.
+ long currentPress = SystemClock.uptimeMillis();
+ if (currentPress - mLastPress < BACK_THRESHOLD) {
+ return super.onKeyDown(keyCode, event);
+ }
+ mLastPress = currentPress;
+
+ // Pop last entry off stack and start loading
+ String lastEntry = mHistory.pop();
+ startNavigating(lastEntry, false);
+
+ return true;
+ }
+
+ // Otherwise fall through to parent
+ return super.onKeyDown(keyCode, event);
+ }
+
+ /**
+ * Start navigating to the given word, pushing any current word onto the
+ * history stack if requested. The navigation happens on a background thread
+ * and updates the GUI when finished.
+ *
+ * @param word The dictionary word to navigate to.
+ * @param pushHistory If true, push the current word onto history stack.
+ */
+ private void startNavigating(String word, boolean pushHistory) {
+ // Push any current word onto the history stack
+ if (!TextUtils.isEmpty(mEntryTitle) && pushHistory) {
+ mHistory.add(mEntryTitle);
+ }
+
+ // Start lookup for new word in background
+ new LookupTask().execute(word);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.lookup, menu);
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.lookup_search: {
+ onSearchRequested();
+ return true;
+ }
+ case R.id.lookup_random: {
+ startNavigating(null, true);
+ return true;
+ }
+ case R.id.lookup_about: {
+ showAbout();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Show an about dialog that cites data sources.
+ */
+ protected void showAbout() {
+ // Inflate the about message contents
+ View messageView = getLayoutInflater().inflate(R.layout.about, null, false);
+
+ // When linking text, force to always use default color. This works
+ // around a pressed color state bug.
+ TextView textView = (TextView) messageView.findViewById(R.id.about_credits);
+ int defaultColor = textView.getTextColors().getDefaultColor();
+ textView.setTextColor(defaultColor);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setIcon(R.drawable.app_icon);
+ builder.setTitle(R.string.app_name);
+ builder.setView(messageView);
+ builder.create();
+ builder.show();
+ }
+
+ /**
+ * Because we're singleTop, we handle our own new intents. These usually
+ * come from the {@link SearchManager} when a search is requested, or from
+ * internal links the user clicks on.
+ */
+ @Override
+ public void onNewIntent(Intent intent) {
+ final String action = intent.getAction();
+ if (Intent.ACTION_SEARCH.equals(action)) {
+ // Start query for incoming search request
+ String query = intent.getStringExtra(SearchManager.QUERY);
+ startNavigating(query, true);
+
+ } else if (Intent.ACTION_VIEW.equals(action)) {
+ // Treat as internal link only if valid Uri and host matches
+ Uri data = intent.getData();
+ if (data != null && ExtendedWikiHelper.WIKI_LOOKUP_HOST
+ .equals(data.getHost())) {
+ String query = data.getPathSegments().get(0);
+ startNavigating(query, true);
+ }
+
+ } else {
+ // If not recognized, then start showing random word
+ startNavigating(null, true);
+ }
+ }
+
+ /**
+ * Set the title for the current entry.
+ */
+ protected void setEntryTitle(String entryText) {
+ mEntryTitle = entryText;
+ mTitle.setText(mEntryTitle);
+ }
+
+ /**
+ * Set the content for the current entry. This will update our
+ * {@link WebView} to show the requested content.
+ */
+ protected void setEntryContent(String entryContent) {
+ mWebView.loadDataWithBaseURL(ExtendedWikiHelper.WIKI_AUTHORITY, entryContent,
+ ExtendedWikiHelper.MIME_TYPE, ExtendedWikiHelper.ENCODING, null);
+ }
+
+ /**
+ * Background task to handle Wiktionary lookups. This correctly shows and
+ * hides the loading animation from the GUI thread before starting a
+ * background query to the Wiktionary API. When finished, it transitions
+ * back to the GUI thread where it updates with the newly-found entry.
+ */
+ private class LookupTask extends AsyncTask {
+ /**
+ * Before jumping into background thread, start sliding in the
+ * {@link ProgressBar}. We'll only show it once the animation finishes.
+ */
+ @Override
+ protected void onPreExecute() {
+ mTitleBar.startAnimation(mSlideIn);
+ }
+
+ /**
+ * Perform the background query using {@link ExtendedWikiHelper}, which
+ * may return an error message as the result.
+ */
+ @Override
+ protected String doInBackground(String... args) {
+ String query = args[0];
+ String parsedText = null;
+
+ try {
+ // If query word is null, assume request for random word
+ if (query == null) {
+ query = ExtendedWikiHelper.getRandomWord();
+ }
+
+ if (query != null) {
+ // Push our requested word to the title bar
+ publishProgress(query);
+ String wikiText = ExtendedWikiHelper.getPageContent(query, true);
+ parsedText = ExtendedWikiHelper.formatWikiText(wikiText);
+ }
+ } catch (ApiException e) {
+ Log.e(TAG, "Problem making wiktionary request", e);
+ } catch (ParseException e) {
+ Log.e(TAG, "Problem making wiktionary request", e);
+ }
+
+ if (parsedText == null) {
+ parsedText = getString(R.string.empty_result);
+ }
+
+ return parsedText;
+ }
+
+ /**
+ * Our progress update pushes a title bar update.
+ */
+ @Override
+ protected void onProgressUpdate(String... args) {
+ String searchWord = args[0];
+ setEntryTitle(searchWord);
+ }
+
+ /**
+ * When finished, push the newly-found entry content into our
+ * {@link WebView} and hide the {@link ProgressBar}.
+ */
+ @Override
+ protected void onPostExecute(String parsedText) {
+ mTitleBar.startAnimation(mSlideOut);
+ mProgress.setVisibility(View.INVISIBLE);
+
+ setEntryContent(parsedText);
+ }
+ }
+
+ /**
+ * Make the {@link ProgressBar} visible when our in-animation finishes.
+ */
+ public void onAnimationEnd(Animation animation) {
+ mProgress.setVisibility(View.VISIBLE);
+ }
+
+ public void onAnimationRepeat(Animation animation) {
+ // Not interested if the animation repeats
+ }
+
+ public void onAnimationStart(Animation animation) {
+ // Not interested when the animation starts
+ }
+}
diff --git a/samples/Wiktionary/src/com/example/android/wiktionary/SimpleWikiHelper.java b/samples/Wiktionary/src/com/example/android/wiktionary/SimpleWikiHelper.java
new file mode 100644
index 000000000..1c71d7e8b
--- /dev/null
+++ b/samples/Wiktionary/src/com/example/android/wiktionary/SimpleWikiHelper.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2009 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.wiktionary;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.StatusLine;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.Uri;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Helper methods to simplify talking with and parsing responses from a
+ * lightweight Wiktionary API. Before making any requests, you should call
+ * {@link #prepareUserAgent(Context)} to generate a User-Agent string based on
+ * your application package name and version.
+ */
+public class SimpleWikiHelper {
+ private static final String TAG = "SimpleWikiHelper";
+
+ /**
+ * Partial URL to use when requesting the detailed entry for a specific
+ * Wiktionary page. Use {@link String#format(String, Object...)} to insert
+ * the desired page title after escaping it as needed.
+ */
+ private static final String WIKTIONARY_PAGE =
+ "http://en.wiktionary.org/w/api.php?action=query&prop=revisions&titles=%s&" +
+ "rvprop=content&format=json%s";
+
+ /**
+ * Partial URL to append to {@link #WIKTIONARY_PAGE} when you want to expand
+ * any templates found on the requested page. This is useful when browsing
+ * full entries, but may use more network bandwidth.
+ */
+ private static final String WIKTIONARY_EXPAND_TEMPLATES =
+ "&rvexpandtemplates=true";
+
+ /**
+ * {@link StatusLine} HTTP status code when no server error has occurred.
+ */
+ private static final int HTTP_STATUS_OK = 200;
+
+ /**
+ * Shared buffer used by {@link #getUrlContent(String)} when reading results
+ * from an API request.
+ */
+ private static byte[] sBuffer = new byte[512];
+
+ /**
+ * User-agent string to use when making requests. Should be filled using
+ * {@link #prepareUserAgent(Context)} before making any other calls.
+ */
+ private static String sUserAgent = null;
+
+ /**
+ * Thrown when there were problems contacting the remote API server, either
+ * because of a network error, or the server returned a bad status code.
+ */
+ public static class ApiException extends Exception {
+ public ApiException(String detailMessage, Throwable throwable) {
+ super(detailMessage, throwable);
+ }
+
+ public ApiException(String detailMessage) {
+ super(detailMessage);
+ }
+ }
+
+ /**
+ * Thrown when there were problems parsing the response to an API call,
+ * either because the response was empty, or it was malformed.
+ */
+ public static class ParseException extends Exception {
+ public ParseException(String detailMessage, Throwable throwable) {
+ super(detailMessage, throwable);
+ }
+ }
+
+ /**
+ * Prepare the internal User-Agent string for use. This requires a
+ * {@link Context} to pull the package name and version number for this
+ * application.
+ */
+ public static void prepareUserAgent(Context context) {
+ try {
+ // Read package name and version number from manifest
+ PackageManager manager = context.getPackageManager();
+ PackageInfo info = manager.getPackageInfo(context.getPackageName(), 0);
+ sUserAgent = String.format(context.getString(R.string.template_user_agent),
+ info.packageName, info.versionName);
+
+ } catch(NameNotFoundException e) {
+ Log.e(TAG, "Couldn't find package information in PackageManager", e);
+ }
+ }
+
+ /**
+ * Read and return the content for a specific Wiktionary page. This makes a
+ * lightweight API call, and trims out just the page content returned.
+ * Because this call blocks until results are available, it should not be
+ * run from a UI thread.
+ *
+ * @param title The exact title of the Wiktionary page requested.
+ * @param expandTemplates If true, expand any wiki templates found.
+ * @return Exact content of page.
+ * @throws ApiException If any connection or server error occurs.
+ * @throws ParseException If there are problems parsing the response.
+ */
+ public static String getPageContent(String title, boolean expandTemplates)
+ throws ApiException, ParseException {
+ // Encode page title and expand templates if requested
+ String encodedTitle = Uri.encode(title);
+ String expandClause = expandTemplates ? WIKTIONARY_EXPAND_TEMPLATES : "";
+
+ // Query the API for content
+ String content = getUrlContent(String.format(WIKTIONARY_PAGE,
+ encodedTitle, expandClause));
+ try {
+ // Drill into the JSON response to find the content body
+ JSONObject response = new JSONObject(content);
+ JSONObject query = response.getJSONObject("query");
+ JSONObject pages = query.getJSONObject("pages");
+ JSONObject page = pages.getJSONObject((String) pages.keys().next());
+ JSONArray revisions = page.getJSONArray("revisions");
+ JSONObject revision = revisions.getJSONObject(0);
+ return revision.getString("*");
+ } catch (JSONException e) {
+ throw new ParseException("Problem parsing API response", e);
+ }
+ }
+
+ /**
+ * Pull the raw text content of the given URL. This call blocks until the
+ * operation has completed, and is synchronized because it uses a shared
+ * buffer {@link #sBuffer}.
+ *
+ * @param url The exact URL to request.
+ * @return The raw content returned by the server.
+ * @throws ApiException If any connection or server error occurs.
+ */
+ protected static synchronized String getUrlContent(String url) throws ApiException {
+ if (sUserAgent == null) {
+ throw new ApiException("User-Agent string must be prepared");
+ }
+
+ // Create client and set our specific user-agent string
+ HttpClient client = new DefaultHttpClient();
+ HttpGet request = new HttpGet(url);
+ request.setHeader("User-Agent", sUserAgent);
+
+ try {
+ HttpResponse response = client.execute(request);
+
+ // Check if server response is valid
+ StatusLine status = response.getStatusLine();
+ if (status.getStatusCode() != HTTP_STATUS_OK) {
+ throw new ApiException("Invalid response from server: " +
+ status.toString());
+ }
+
+ // Pull content stream from response
+ HttpEntity entity = response.getEntity();
+ InputStream inputStream = entity.getContent();
+
+ ByteArrayOutputStream content = new ByteArrayOutputStream();
+
+ // Read response into a buffered stream
+ int readBytes = 0;
+ while ((readBytes = inputStream.read(sBuffer)) != -1) {
+ content.write(sBuffer, 0, readBytes);
+ }
+
+ // Return result from buffered stream
+ return new String(content.toByteArray());
+ } catch (IOException e) {
+ throw new ApiException("Problem communicating with API", e);
+ }
+ }
+
+}
diff --git a/samples/Wiktionary/src/com/example/android/wiktionary/WordWidget.java b/samples/Wiktionary/src/com/example/android/wiktionary/WordWidget.java
new file mode 100644
index 000000000..e80eaf92a
--- /dev/null
+++ b/samples/Wiktionary/src/com/example/android/wiktionary/WordWidget.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2009 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.wiktionary;
+
+import com.example.android.wiktionary.SimpleWikiHelper.ApiException;
+import com.example.android.wiktionary.SimpleWikiHelper.ParseException;
+
+import android.app.PendingIntent;
+import android.app.Service;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.IBinder;
+import android.text.format.Time;
+import android.util.Log;
+import android.widget.RemoteViews;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Define a simple widget that shows the Wiktionary "Word of the day." To build
+ * an update we spawn a background {@link Service} to perform the API queries.
+ */
+public class WordWidget extends AppWidgetProvider {
+ @Override
+ public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+ // To prevent any ANR timeouts, we perform the update in a service
+ context.startService(new Intent(context, UpdateService.class));
+ }
+
+ public static class UpdateService extends Service {
+ @Override
+ public void onStart(Intent intent, int startId) {
+ // Build the widget update for today
+ RemoteViews updateViews = buildUpdate(this);
+
+ // Push update for this widget to the home screen
+ ComponentName thisWidget = new ComponentName(this, WordWidget.class);
+ AppWidgetManager manager = AppWidgetManager.getInstance(this);
+ manager.updateAppWidget(thisWidget, updateViews);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ /**
+ * Regular expression that splits "Word of the day" entry into word
+ * name, word type, and the first description bullet point.
+ */
+ private static final String WOTD_PATTERN =
+ "(?s)\\{\\{wotd\\|(.+?)\\|(.+?)\\|([^#\\|]+).*?\\}\\}";
+
+ /**
+ * Build a widget update to show the current Wiktionary
+ * "Word of the day." Will block until the online API returns.
+ */
+ public RemoteViews buildUpdate(Context context) {
+ // Pick out month names from resources
+ Resources res = context.getResources();
+ String[] monthNames = res.getStringArray(R.array.month_names);
+
+ // Find current month and day
+ Time today = new Time();
+ today.setToNow();
+
+ // Build the page title for today, such as "March 21"
+ String pageName = res.getString(R.string.template_wotd_title,
+ monthNames[today.month], today.monthDay);
+ String pageContent = null;
+
+ try {
+ // Try querying the Wiktionary API for today's word
+ SimpleWikiHelper.prepareUserAgent(context);
+ pageContent = SimpleWikiHelper.getPageContent(pageName, false);
+ } catch (ApiException e) {
+ Log.e("WordWidget", "Couldn't contact API", e);
+ } catch (ParseException e) {
+ Log.e("WordWidget", "Couldn't parse API response", e);
+ }
+
+ RemoteViews views = null;
+ Matcher matcher = Pattern.compile(WOTD_PATTERN).matcher(pageContent);
+ if (matcher.find()) {
+ // Build an update that holds the updated widget contents
+ views = new RemoteViews(context.getPackageName(), R.layout.widget_word);
+
+ String wordTitle = matcher.group(1);
+ views.setTextViewText(R.id.word_title, wordTitle);
+ views.setTextViewText(R.id.word_type, matcher.group(2));
+ views.setTextViewText(R.id.definition, matcher.group(3).trim());
+
+ // When user clicks on widget, launch to Wiktionary definition page
+ String definePage = String.format("%s://%s/%s", ExtendedWikiHelper.WIKI_AUTHORITY,
+ ExtendedWikiHelper.WIKI_LOOKUP_HOST, wordTitle);
+ Intent defineIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(definePage));
+ PendingIntent pendingIntent = PendingIntent.getActivity(context,
+ 0 /* no requestCode */, defineIntent, 0 /* no flags */);
+ views.setOnClickPendingIntent(R.id.widget, pendingIntent);
+
+ } else {
+ // Didn't find word of day, so show error message
+ views = new RemoteViews(context.getPackageName(), R.layout.widget_message);
+ views.setTextViewText(R.id.message, context.getString(R.string.widget_error));
+ }
+ return views;
+ }
+ }
+}
diff --git a/samples/WiktionarySimple/Android.mk b/samples/WiktionarySimple/Android.mk
new file mode 100644
index 000000000..a5a1423e0
--- /dev/null
+++ b/samples/WiktionarySimple/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := samples
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := WiktionarySimple
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
+
+# Use the following include to make our test apk.
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/samples/WiktionarySimple/AndroidManifest.xml b/samples/WiktionarySimple/AndroidManifest.xml
new file mode 100644
index 000000000..c6b872486
--- /dev/null
+++ b/samples/WiktionarySimple/AndroidManifest.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/WiktionarySimple/_index.html b/samples/WiktionarySimple/_index.html
new file mode 100644
index 000000000..3980e3326
--- /dev/null
+++ b/samples/WiktionarySimple/_index.html
@@ -0,0 +1,10 @@
+
A sample application that demonstrates how to create an interactive widget for display on the Android home screen.
+
+
When installed, this adds a "Wiktionary simple" option to the widget
+installation menu. The word of the day is downloaded from Wiktionary and
+displayed in a frame. Touching the widget will open a new browser session and
+load the word's Wiktionary entry.
+
+
A more advanced version of this sample is available in the Wiktionary directory.
+
+
diff --git a/samples/WiktionarySimple/res/drawable/app_icon.png b/samples/WiktionarySimple/res/drawable/app_icon.png
new file mode 100644
index 000000000..2b1417aad
Binary files /dev/null and b/samples/WiktionarySimple/res/drawable/app_icon.png differ
diff --git a/samples/WiktionarySimple/res/drawable/star_logo.png b/samples/WiktionarySimple/res/drawable/star_logo.png
new file mode 100644
index 000000000..b32d1756c
Binary files /dev/null and b/samples/WiktionarySimple/res/drawable/star_logo.png differ
diff --git a/samples/WiktionarySimple/res/drawable/widget_bg.xml b/samples/WiktionarySimple/res/drawable/widget_bg.xml
new file mode 100644
index 000000000..692a13d84
--- /dev/null
+++ b/samples/WiktionarySimple/res/drawable/widget_bg.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/WiktionarySimple/res/drawable/widget_bg_normal.9.png b/samples/WiktionarySimple/res/drawable/widget_bg_normal.9.png
new file mode 100644
index 000000000..314eb8ef9
Binary files /dev/null and b/samples/WiktionarySimple/res/drawable/widget_bg_normal.9.png differ
diff --git a/samples/WiktionarySimple/res/drawable/widget_bg_pressed.9.png b/samples/WiktionarySimple/res/drawable/widget_bg_pressed.9.png
new file mode 100644
index 000000000..cc23e787b
Binary files /dev/null and b/samples/WiktionarySimple/res/drawable/widget_bg_pressed.9.png differ
diff --git a/samples/WiktionarySimple/res/drawable/widget_bg_selected.9.png b/samples/WiktionarySimple/res/drawable/widget_bg_selected.9.png
new file mode 100644
index 000000000..ef0cdc066
Binary files /dev/null and b/samples/WiktionarySimple/res/drawable/widget_bg_selected.9.png differ
diff --git a/samples/WiktionarySimple/res/layout/widget_message.xml b/samples/WiktionarySimple/res/layout/widget_message.xml
new file mode 100644
index 000000000..ba9471447
--- /dev/null
+++ b/samples/WiktionarySimple/res/layout/widget_message.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
diff --git a/samples/WiktionarySimple/res/layout/widget_word.xml b/samples/WiktionarySimple/res/layout/widget_word.xml
new file mode 100644
index 000000000..0e76f0b80
--- /dev/null
+++ b/samples/WiktionarySimple/res/layout/widget_word.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/WiktionarySimple/res/values/strings.xml b/samples/WiktionarySimple/res/values/strings.xml
new file mode 100644
index 000000000..65e44cb86
--- /dev/null
+++ b/samples/WiktionarySimple/res/values/strings.xml
@@ -0,0 +1,44 @@
+
+
+
+
+ Wiktionary simple example
+
+ "%s/%s (Linux; Android)"
+ "Wiktionary:Word of the day/%s %s"
+ "http://en.wiktionary.org/wiki/%s"
+
+ Wiktionary simple
+
+ Loading word\nof day\u2026
+ No word of\nday found
+
+
+ January
+ February
+ March
+ April
+ May
+ June
+ July
+ August
+ September
+ October
+ November
+ December
+
+
+
diff --git a/samples/WiktionarySimple/res/values/styles.xml b/samples/WiktionarySimple/res/values/styles.xml
new file mode 100644
index 000000000..42d679c56
--- /dev/null
+++ b/samples/WiktionarySimple/res/values/styles.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/WiktionarySimple/res/xml/widget_word.xml b/samples/WiktionarySimple/res/xml/widget_word.xml
new file mode 100644
index 000000000..46d31c321
--- /dev/null
+++ b/samples/WiktionarySimple/res/xml/widget_word.xml
@@ -0,0 +1,21 @@
+
+
+
+
diff --git a/samples/WiktionarySimple/src/com/example/android/simplewiktionary/SimpleWikiHelper.java b/samples/WiktionarySimple/src/com/example/android/simplewiktionary/SimpleWikiHelper.java
new file mode 100644
index 000000000..bb39d7bd7
--- /dev/null
+++ b/samples/WiktionarySimple/src/com/example/android/simplewiktionary/SimpleWikiHelper.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2009 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.simplewiktionary;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.StatusLine;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.Uri;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Helper methods to simplify talking with and parsing responses from a
+ * lightweight Wiktionary API. Before making any requests, you should call
+ * {@link #prepareUserAgent(Context)} to generate a User-Agent string based on
+ * your application package name and version.
+ */
+public class SimpleWikiHelper {
+ private static final String TAG = "SimpleWikiHelper";
+
+ /**
+ * Regular expression that splits "Word of the day" entry into word
+ * name, word type, and the first description bullet point.
+ */
+ public static final String WORD_OF_DAY_REGEX =
+ "(?s)\\{\\{wotd\\|(.+?)\\|(.+?)\\|([^#\\|]+).*?\\}\\}";
+
+ /**
+ * Partial URL to use when requesting the detailed entry for a specific
+ * Wiktionary page. Use {@link String#format(String, Object...)} to insert
+ * the desired page title after escaping it as needed.
+ */
+ private static final String WIKTIONARY_PAGE =
+ "http://en.wiktionary.org/w/api.php?action=query&prop=revisions&titles=%s&" +
+ "rvprop=content&format=json%s";
+
+ /**
+ * Partial URL to append to {@link #WIKTIONARY_PAGE} when you want to expand
+ * any templates found on the requested page. This is useful when browsing
+ * full entries, but may use more network bandwidth.
+ */
+ private static final String WIKTIONARY_EXPAND_TEMPLATES =
+ "&rvexpandtemplates=true";
+
+ /**
+ * {@link StatusLine} HTTP status code when no server error has occurred.
+ */
+ private static final int HTTP_STATUS_OK = 200;
+
+ /**
+ * Shared buffer used by {@link #getUrlContent(String)} when reading results
+ * from an API request.
+ */
+ private static byte[] sBuffer = new byte[512];
+
+ /**
+ * User-agent string to use when making requests. Should be filled using
+ * {@link #prepareUserAgent(Context)} before making any other calls.
+ */
+ private static String sUserAgent = null;
+
+ /**
+ * Thrown when there were problems contacting the remote API server, either
+ * because of a network error, or the server returned a bad status code.
+ */
+ public static class ApiException extends Exception {
+ public ApiException(String detailMessage, Throwable throwable) {
+ super(detailMessage, throwable);
+ }
+
+ public ApiException(String detailMessage) {
+ super(detailMessage);
+ }
+ }
+
+ /**
+ * Thrown when there were problems parsing the response to an API call,
+ * either because the response was empty, or it was malformed.
+ */
+ public static class ParseException extends Exception {
+ public ParseException(String detailMessage, Throwable throwable) {
+ super(detailMessage, throwable);
+ }
+ }
+
+ /**
+ * Prepare the internal User-Agent string for use. This requires a
+ * {@link Context} to pull the package name and version number for this
+ * application.
+ */
+ public static void prepareUserAgent(Context context) {
+ try {
+ // Read package name and version number from manifest
+ PackageManager manager = context.getPackageManager();
+ PackageInfo info = manager.getPackageInfo(context.getPackageName(), 0);
+ sUserAgent = String.format(context.getString(R.string.template_user_agent),
+ info.packageName, info.versionName);
+
+ } catch(NameNotFoundException e) {
+ Log.e(TAG, "Couldn't find package information in PackageManager", e);
+ }
+ }
+
+ /**
+ * Read and return the content for a specific Wiktionary page. This makes a
+ * lightweight API call, and trims out just the page content returned.
+ * Because this call blocks until results are available, it should not be
+ * run from a UI thread.
+ *
+ * @param title The exact title of the Wiktionary page requested.
+ * @param expandTemplates If true, expand any wiki templates found.
+ * @return Exact content of page.
+ * @throws ApiException If any connection or server error occurs.
+ * @throws ParseException If there are problems parsing the response.
+ */
+ public static String getPageContent(String title, boolean expandTemplates)
+ throws ApiException, ParseException {
+ // Encode page title and expand templates if requested
+ String encodedTitle = Uri.encode(title);
+ String expandClause = expandTemplates ? WIKTIONARY_EXPAND_TEMPLATES : "";
+
+ // Query the API for content
+ String content = getUrlContent(String.format(WIKTIONARY_PAGE,
+ encodedTitle, expandClause));
+ try {
+ // Drill into the JSON response to find the content body
+ JSONObject response = new JSONObject(content);
+ JSONObject query = response.getJSONObject("query");
+ JSONObject pages = query.getJSONObject("pages");
+ JSONObject page = pages.getJSONObject((String) pages.keys().next());
+ JSONArray revisions = page.getJSONArray("revisions");
+ JSONObject revision = revisions.getJSONObject(0);
+ return revision.getString("*");
+ } catch (JSONException e) {
+ throw new ParseException("Problem parsing API response", e);
+ }
+ }
+
+ /**
+ * Pull the raw text content of the given URL. This call blocks until the
+ * operation has completed, and is synchronized because it uses a shared
+ * buffer {@link #sBuffer}.
+ *
+ * @param url The exact URL to request.
+ * @return The raw content returned by the server.
+ * @throws ApiException If any connection or server error occurs.
+ */
+ protected static synchronized String getUrlContent(String url) throws ApiException {
+ if (sUserAgent == null) {
+ throw new ApiException("User-Agent string must be prepared");
+ }
+
+ // Create client and set our specific user-agent string
+ HttpClient client = new DefaultHttpClient();
+ HttpGet request = new HttpGet(url);
+ request.setHeader("User-Agent", sUserAgent);
+
+ try {
+ HttpResponse response = client.execute(request);
+
+ // Check if server response is valid
+ StatusLine status = response.getStatusLine();
+ if (status.getStatusCode() != HTTP_STATUS_OK) {
+ throw new ApiException("Invalid response from server: " +
+ status.toString());
+ }
+
+ // Pull content stream from response
+ HttpEntity entity = response.getEntity();
+ InputStream inputStream = entity.getContent();
+
+ ByteArrayOutputStream content = new ByteArrayOutputStream();
+
+ // Read response into a buffered stream
+ int readBytes = 0;
+ while ((readBytes = inputStream.read(sBuffer)) != -1) {
+ content.write(sBuffer, 0, readBytes);
+ }
+
+ // Return result from buffered stream
+ return new String(content.toByteArray());
+ } catch (IOException e) {
+ throw new ApiException("Problem communicating with API", e);
+ }
+ }
+}
diff --git a/samples/WiktionarySimple/src/com/example/android/simplewiktionary/WordWidget.java b/samples/WiktionarySimple/src/com/example/android/simplewiktionary/WordWidget.java
new file mode 100644
index 000000000..d005faa4d
--- /dev/null
+++ b/samples/WiktionarySimple/src/com/example/android/simplewiktionary/WordWidget.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2009 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.simplewiktionary;
+
+import com.example.android.simplewiktionary.SimpleWikiHelper.ApiException;
+import com.example.android.simplewiktionary.SimpleWikiHelper.ParseException;
+
+import android.app.PendingIntent;
+import android.app.Service;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.IBinder;
+import android.text.format.Time;
+import android.util.Log;
+import android.widget.RemoteViews;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Define a simple widget that shows the Wiktionary "Word of the day." To build
+ * an update we spawn a background {@link Service} to perform the API queries.
+ */
+public class WordWidget extends AppWidgetProvider {
+ @Override
+ public void onUpdate(Context context, AppWidgetManager appWidgetManager,
+ int[] appWidgetIds) {
+ // To prevent any ANR timeouts, we perform the update in a service
+ context.startService(new Intent(context, UpdateService.class));
+ }
+
+ public static class UpdateService extends Service {
+ @Override
+ public void onStart(Intent intent, int startId) {
+ // Build the widget update for today
+ RemoteViews updateViews = buildUpdate(this);
+
+ // Push update for this widget to the home screen
+ ComponentName thisWidget = new ComponentName(this, WordWidget.class);
+ AppWidgetManager manager = AppWidgetManager.getInstance(this);
+ manager.updateAppWidget(thisWidget, updateViews);
+ }
+
+ /**
+ * Build a widget update to show the current Wiktionary
+ * "Word of the day." Will block until the online API returns.
+ */
+ public RemoteViews buildUpdate(Context context) {
+ // Pick out month names from resources
+ Resources res = context.getResources();
+ String[] monthNames = res.getStringArray(R.array.month_names);
+
+ // Find current month and day
+ Time today = new Time();
+ today.setToNow();
+
+ // Build today's page title, like "Wiktionary:Word of the day/March 21"
+ String pageName = res.getString(R.string.template_wotd_title,
+ monthNames[today.month], today.monthDay);
+ RemoteViews updateViews = null;
+ String pageContent = "";
+
+ try {
+ // Try querying the Wiktionary API for today's word
+ SimpleWikiHelper.prepareUserAgent(context);
+ pageContent = SimpleWikiHelper.getPageContent(pageName, false);
+ } catch (ApiException e) {
+ Log.e("WordWidget", "Couldn't contact API", e);
+ } catch (ParseException e) {
+ Log.e("WordWidget", "Couldn't parse API response", e);
+ }
+
+ // Use a regular expression to parse out the word and its definition
+ Pattern pattern = Pattern.compile(SimpleWikiHelper.WORD_OF_DAY_REGEX);
+ Matcher matcher = pattern.matcher(pageContent);
+ if (matcher.find()) {
+ // Build an update that holds the updated widget contents
+ updateViews = new RemoteViews(context.getPackageName(), R.layout.widget_word);
+
+ String wordTitle = matcher.group(1);
+ updateViews.setTextViewText(R.id.word_title, wordTitle);
+ updateViews.setTextViewText(R.id.word_type, matcher.group(2));
+ updateViews.setTextViewText(R.id.definition, matcher.group(3).trim());
+
+ // When user clicks on widget, launch to Wiktionary definition page
+ String definePage = res.getString(R.string.template_define_url,
+ Uri.encode(wordTitle));
+ Intent defineIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(definePage));
+ PendingIntent pendingIntent = PendingIntent.getActivity(context,
+ 0 /* no requestCode */, defineIntent, 0 /* no flags */);
+ updateViews.setOnClickPendingIntent(R.id.widget, pendingIntent);
+
+ } else {
+ // Didn't find word of day, so show error message
+ updateViews = new RemoteViews(context.getPackageName(), R.layout.widget_message);
+ CharSequence errorMessage = context.getText(R.string.widget_error);
+ updateViews.setTextViewText(R.id.message, errorMessage);
+ }
+ return updateViews;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ // We don't need to bind to this service
+ return null;
+ }
+ }
+}
diff --git a/tutorials/NotepadCodeLab/Notepadv1/AndroidManifest.xml b/tutorials/NotepadCodeLab/Notepadv1/AndroidManifest.xml
new file mode 100755
index 000000000..33f30da3c
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv1/AndroidManifest.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tutorials/NotepadCodeLab/Notepadv1/res/drawable/icon.png b/tutorials/NotepadCodeLab/Notepadv1/res/drawable/icon.png
new file mode 100755
index 000000000..64e3601c2
Binary files /dev/null and b/tutorials/NotepadCodeLab/Notepadv1/res/drawable/icon.png differ
diff --git a/tutorials/NotepadCodeLab/Notepadv1/res/layout/notepad_list.xml b/tutorials/NotepadCodeLab/Notepadv1/res/layout/notepad_list.xml
new file mode 100755
index 000000000..154888dd3
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv1/res/layout/notepad_list.xml
@@ -0,0 +1,6 @@
+
+
+
+
\ No newline at end of file
diff --git a/tutorials/NotepadCodeLab/Notepadv1/res/values/strings.xml b/tutorials/NotepadCodeLab/Notepadv1/res/values/strings.xml
new file mode 100755
index 000000000..5698961a6
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv1/res/values/strings.xml
@@ -0,0 +1,5 @@
+
+
+ Notepad v1
+ No Notes Yet
+
diff --git a/tutorials/NotepadCodeLab/Notepadv1/src/com/android/demo/notepad1/Notepadv1.java b/tutorials/NotepadCodeLab/Notepadv1/src/com/android/demo/notepad1/Notepadv1.java
new file mode 100755
index 000000000..43e7a7733
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv1/src/com/android/demo/notepad1/Notepadv1.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.demo.notepad1;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+
+public class Notepadv1 extends Activity {
+ private int mNoteNumber = 1;
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // TODO Auto-generated method stub
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // TODO Auto-generated method stub
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/tutorials/NotepadCodeLab/Notepadv1/src/com/android/demo/notepad1/NotesDbAdapter.java b/tutorials/NotepadCodeLab/Notepadv1/src/com/android/demo/notepad1/NotesDbAdapter.java
new file mode 100755
index 000000000..6f85bbd20
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv1/src/com/android/demo/notepad1/NotesDbAdapter.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.demo.notepad1;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
+
+/**
+ * Simple notes database access helper class. Defines the basic CRUD operations
+ * for the notepad example, and gives the ability to list all notes as well as
+ * retrieve or modify a specific note.
+ *
+ * This has been improved from the first version of this tutorial through the
+ * addition of better error handling and also using returning a Cursor instead
+ * of using a collection of inner classes (which is less scalable and not
+ * recommended).
+ */
+public class NotesDbAdapter {
+
+ public static final String KEY_TITLE = "title";
+ public static final String KEY_BODY = "body";
+ public static final String KEY_ROWID = "_id";
+
+ private static final String TAG = "NotesDbAdapter";
+ private DatabaseHelper mDbHelper;
+ private SQLiteDatabase mDb;
+
+ /**
+ * Database creation sql statement
+ */
+ private static final String DATABASE_CREATE =
+ "create table notes (_id integer primary key autoincrement, "
+ + "title text not null, body text not null);";
+
+ private static final String DATABASE_NAME = "data";
+ private static final String DATABASE_TABLE = "notes";
+ private static final int DATABASE_VERSION = 2;
+
+ private final Context mCtx;
+
+ private static class DatabaseHelper extends SQLiteOpenHelper {
+
+ DatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+
+ db.execSQL(DATABASE_CREATE);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+ + newVersion + ", which will destroy all old data");
+ db.execSQL("DROP TABLE IF EXISTS notes");
+ onCreate(db);
+ }
+ }
+
+ /**
+ * Constructor - takes the context to allow the database to be
+ * opened/created
+ *
+ * @param ctx the Context within which to work
+ */
+ public NotesDbAdapter(Context ctx) {
+ this.mCtx = ctx;
+ }
+
+ /**
+ * Open the notes database. If it cannot be opened, try to create a new
+ * instance of the database. If it cannot be created, throw an exception to
+ * signal the failure
+ *
+ * @return this (self reference, allowing this to be chained in an
+ * initialization call)
+ * @throws SQLException if the database could be neither opened or created
+ */
+ public NotesDbAdapter open() throws SQLException {
+ mDbHelper = new DatabaseHelper(mCtx);
+ mDb = mDbHelper.getWritableDatabase();
+ return this;
+ }
+
+ public void close() {
+ mDbHelper.close();
+ }
+
+
+ /**
+ * Create a new note using the title and body provided. If the note is
+ * successfully created return the new rowId for that note, otherwise return
+ * a -1 to indicate failure.
+ *
+ * @param title the title of the note
+ * @param body the body of the note
+ * @return rowId or -1 if failed
+ */
+ public long createNote(String title, String body) {
+ ContentValues initialValues = new ContentValues();
+ initialValues.put(KEY_TITLE, title);
+ initialValues.put(KEY_BODY, body);
+
+ return mDb.insert(DATABASE_TABLE, null, initialValues);
+ }
+
+ /**
+ * Delete the note with the given rowId
+ *
+ * @param rowId id of note to delete
+ * @return true if deleted, false otherwise
+ */
+ public boolean deleteNote(long rowId) {
+
+ return mDb.delete(DATABASE_TABLE, KEY_ROWID + "=" + rowId, null) > 0;
+ }
+
+ /**
+ * Return a Cursor over the list of all notes in the database
+ *
+ * @return Cursor over all notes
+ */
+ public Cursor fetchAllNotes() {
+
+ return mDb.query(DATABASE_TABLE, new String[] {KEY_ROWID, KEY_TITLE,
+ KEY_BODY}, null, null, null, null, null);
+ }
+
+ /**
+ * Return a Cursor positioned at the note that matches the given rowId
+ *
+ * @param rowId id of note to retrieve
+ * @return Cursor positioned to matching note, if found
+ * @throws SQLException if note could not be found/retrieved
+ */
+ public Cursor fetchNote(long rowId) throws SQLException {
+
+ Cursor mCursor =
+
+ mDb.query(true, DATABASE_TABLE, new String[] {KEY_ROWID,
+ KEY_TITLE, KEY_BODY}, KEY_ROWID + "=" + rowId, null,
+ null, null, null, null);
+ if (mCursor != null) {
+ mCursor.moveToFirst();
+ }
+ return mCursor;
+
+ }
+
+ /**
+ * Update the note using the details provided. The note to be updated is
+ * specified using the rowId, and it is altered to use the title and body
+ * values passed in
+ *
+ * @param rowId id of note to update
+ * @param title value to set note title to
+ * @param body value to set note body to
+ * @return true if the note was successfully updated, false otherwise
+ */
+ public boolean updateNote(long rowId, String title, String body) {
+ ContentValues args = new ContentValues();
+ args.put(KEY_TITLE, title);
+ args.put(KEY_BODY, body);
+
+ return mDb.update(DATABASE_TABLE, args, KEY_ROWID + "=" + rowId, null) > 0;
+ }
+}
diff --git a/tutorials/NotepadCodeLab/Notepadv1Solution/Android.mk b/tutorials/NotepadCodeLab/Notepadv1Solution/Android.mk
new file mode 100644
index 000000000..537503105
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv1Solution/Android.mk
@@ -0,0 +1,30 @@
+#
+# Copyright (C) 2009 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.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := Notepadv1Solution
+
+# Make the app build against the current SDK
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/tutorials/NotepadCodeLab/Notepadv1Solution/AndroidManifest.xml b/tutorials/NotepadCodeLab/Notepadv1Solution/AndroidManifest.xml
new file mode 100755
index 000000000..99023fe78
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv1Solution/AndroidManifest.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tutorials/NotepadCodeLab/Notepadv1Solution/res/drawable/icon.png b/tutorials/NotepadCodeLab/Notepadv1Solution/res/drawable/icon.png
new file mode 100755
index 000000000..64e3601c2
Binary files /dev/null and b/tutorials/NotepadCodeLab/Notepadv1Solution/res/drawable/icon.png differ
diff --git a/tutorials/NotepadCodeLab/Notepadv1Solution/res/layout/notepad_list.xml b/tutorials/NotepadCodeLab/Notepadv1Solution/res/layout/notepad_list.xml
new file mode 100755
index 000000000..0c8dbabee
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv1Solution/res/layout/notepad_list.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tutorials/NotepadCodeLab/Notepadv1Solution/res/layout/notes_row.xml b/tutorials/NotepadCodeLab/Notepadv1Solution/res/layout/notes_row.xml
new file mode 100755
index 000000000..6b1a65b05
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv1Solution/res/layout/notes_row.xml
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/tutorials/NotepadCodeLab/Notepadv1Solution/res/values/strings.xml b/tutorials/NotepadCodeLab/Notepadv1Solution/res/values/strings.xml
new file mode 100755
index 000000000..265bc62e3
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv1Solution/res/values/strings.xml
@@ -0,0 +1,6 @@
+
+
+ Notepad v1
+ No Notes Yet
+ Add Item
+
diff --git a/tutorials/NotepadCodeLab/Notepadv1Solution/src/com/android/demo/notepad1/Notepadv1.java b/tutorials/NotepadCodeLab/Notepadv1Solution/src/com/android/demo/notepad1/Notepadv1.java
new file mode 100755
index 000000000..a1819ed0f
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv1Solution/src/com/android/demo/notepad1/Notepadv1.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.demo.notepad1;
+
+import android.app.ListActivity;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.SimpleCursorAdapter;
+
+public class Notepadv1 extends ListActivity {
+ public static final int INSERT_ID = Menu.FIRST;
+
+ private int mNoteNumber = 1;
+ private NotesDbAdapter mDbHelper;
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.notepad_list);
+ mDbHelper = new NotesDbAdapter(this);
+ mDbHelper.open();
+ fillData();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ boolean result = super.onCreateOptionsMenu(menu);
+ menu.add(0, INSERT_ID, 0, R.string.menu_insert);
+ return result;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case INSERT_ID:
+ createNote();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void createNote() {
+ String noteName = "Note " + mNoteNumber++;
+ mDbHelper.createNote(noteName, "");
+ fillData();
+ }
+
+ private void fillData() {
+ // Get all of the notes from the database and create the item list
+ Cursor c = mDbHelper.fetchAllNotes();
+ startManagingCursor(c);
+
+ String[] from = new String[] { NotesDbAdapter.KEY_TITLE };
+ int[] to = new int[] { R.id.text1 };
+
+ // Now create an array adapter and set it to display using our row
+ SimpleCursorAdapter notes =
+ new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to);
+ setListAdapter(notes);
+ }
+}
diff --git a/tutorials/NotepadCodeLab/Notepadv1Solution/src/com/android/demo/notepad1/NotesDbAdapter.java b/tutorials/NotepadCodeLab/Notepadv1Solution/src/com/android/demo/notepad1/NotesDbAdapter.java
new file mode 100755
index 000000000..6f85bbd20
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv1Solution/src/com/android/demo/notepad1/NotesDbAdapter.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.demo.notepad1;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
+
+/**
+ * Simple notes database access helper class. Defines the basic CRUD operations
+ * for the notepad example, and gives the ability to list all notes as well as
+ * retrieve or modify a specific note.
+ *
+ * This has been improved from the first version of this tutorial through the
+ * addition of better error handling and also using returning a Cursor instead
+ * of using a collection of inner classes (which is less scalable and not
+ * recommended).
+ */
+public class NotesDbAdapter {
+
+ public static final String KEY_TITLE = "title";
+ public static final String KEY_BODY = "body";
+ public static final String KEY_ROWID = "_id";
+
+ private static final String TAG = "NotesDbAdapter";
+ private DatabaseHelper mDbHelper;
+ private SQLiteDatabase mDb;
+
+ /**
+ * Database creation sql statement
+ */
+ private static final String DATABASE_CREATE =
+ "create table notes (_id integer primary key autoincrement, "
+ + "title text not null, body text not null);";
+
+ private static final String DATABASE_NAME = "data";
+ private static final String DATABASE_TABLE = "notes";
+ private static final int DATABASE_VERSION = 2;
+
+ private final Context mCtx;
+
+ private static class DatabaseHelper extends SQLiteOpenHelper {
+
+ DatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+
+ db.execSQL(DATABASE_CREATE);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+ + newVersion + ", which will destroy all old data");
+ db.execSQL("DROP TABLE IF EXISTS notes");
+ onCreate(db);
+ }
+ }
+
+ /**
+ * Constructor - takes the context to allow the database to be
+ * opened/created
+ *
+ * @param ctx the Context within which to work
+ */
+ public NotesDbAdapter(Context ctx) {
+ this.mCtx = ctx;
+ }
+
+ /**
+ * Open the notes database. If it cannot be opened, try to create a new
+ * instance of the database. If it cannot be created, throw an exception to
+ * signal the failure
+ *
+ * @return this (self reference, allowing this to be chained in an
+ * initialization call)
+ * @throws SQLException if the database could be neither opened or created
+ */
+ public NotesDbAdapter open() throws SQLException {
+ mDbHelper = new DatabaseHelper(mCtx);
+ mDb = mDbHelper.getWritableDatabase();
+ return this;
+ }
+
+ public void close() {
+ mDbHelper.close();
+ }
+
+
+ /**
+ * Create a new note using the title and body provided. If the note is
+ * successfully created return the new rowId for that note, otherwise return
+ * a -1 to indicate failure.
+ *
+ * @param title the title of the note
+ * @param body the body of the note
+ * @return rowId or -1 if failed
+ */
+ public long createNote(String title, String body) {
+ ContentValues initialValues = new ContentValues();
+ initialValues.put(KEY_TITLE, title);
+ initialValues.put(KEY_BODY, body);
+
+ return mDb.insert(DATABASE_TABLE, null, initialValues);
+ }
+
+ /**
+ * Delete the note with the given rowId
+ *
+ * @param rowId id of note to delete
+ * @return true if deleted, false otherwise
+ */
+ public boolean deleteNote(long rowId) {
+
+ return mDb.delete(DATABASE_TABLE, KEY_ROWID + "=" + rowId, null) > 0;
+ }
+
+ /**
+ * Return a Cursor over the list of all notes in the database
+ *
+ * @return Cursor over all notes
+ */
+ public Cursor fetchAllNotes() {
+
+ return mDb.query(DATABASE_TABLE, new String[] {KEY_ROWID, KEY_TITLE,
+ KEY_BODY}, null, null, null, null, null);
+ }
+
+ /**
+ * Return a Cursor positioned at the note that matches the given rowId
+ *
+ * @param rowId id of note to retrieve
+ * @return Cursor positioned to matching note, if found
+ * @throws SQLException if note could not be found/retrieved
+ */
+ public Cursor fetchNote(long rowId) throws SQLException {
+
+ Cursor mCursor =
+
+ mDb.query(true, DATABASE_TABLE, new String[] {KEY_ROWID,
+ KEY_TITLE, KEY_BODY}, KEY_ROWID + "=" + rowId, null,
+ null, null, null, null);
+ if (mCursor != null) {
+ mCursor.moveToFirst();
+ }
+ return mCursor;
+
+ }
+
+ /**
+ * Update the note using the details provided. The note to be updated is
+ * specified using the rowId, and it is altered to use the title and body
+ * values passed in
+ *
+ * @param rowId id of note to update
+ * @param title value to set note title to
+ * @param body value to set note body to
+ * @return true if the note was successfully updated, false otherwise
+ */
+ public boolean updateNote(long rowId, String title, String body) {
+ ContentValues args = new ContentValues();
+ args.put(KEY_TITLE, title);
+ args.put(KEY_BODY, body);
+
+ return mDb.update(DATABASE_TABLE, args, KEY_ROWID + "=" + rowId, null) > 0;
+ }
+}
diff --git a/tutorials/NotepadCodeLab/Notepadv2/AndroidManifest.xml b/tutorials/NotepadCodeLab/Notepadv2/AndroidManifest.xml
new file mode 100755
index 000000000..738fb2afc
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv2/AndroidManifest.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tutorials/NotepadCodeLab/Notepadv2/res/drawable/icon.png b/tutorials/NotepadCodeLab/Notepadv2/res/drawable/icon.png
new file mode 100755
index 000000000..64e3601c2
Binary files /dev/null and b/tutorials/NotepadCodeLab/Notepadv2/res/drawable/icon.png differ
diff --git a/tutorials/NotepadCodeLab/Notepadv2/res/layout/note_edit.xml b/tutorials/NotepadCodeLab/Notepadv2/res/layout/note_edit.xml
new file mode 100755
index 000000000..b254552c3
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv2/res/layout/note_edit.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tutorials/NotepadCodeLab/Notepadv2/res/layout/notes_list.xml b/tutorials/NotepadCodeLab/Notepadv2/res/layout/notes_list.xml
new file mode 100755
index 000000000..6ae04729e
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv2/res/layout/notes_list.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/tutorials/NotepadCodeLab/Notepadv2/res/layout/notes_row.xml b/tutorials/NotepadCodeLab/Notepadv2/res/layout/notes_row.xml
new file mode 100755
index 000000000..f28a41bea
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv2/res/layout/notes_row.xml
@@ -0,0 +1,4 @@
+
+
diff --git a/tutorials/NotepadCodeLab/Notepadv2/res/values/strings.xml b/tutorials/NotepadCodeLab/Notepadv2/res/values/strings.xml
new file mode 100755
index 000000000..b70c3f843
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv2/res/values/strings.xml
@@ -0,0 +1,11 @@
+
+
+ Notepad v2
+ No Notes Yet
+ Add Note
+ Delete Note
+ Title
+ Body
+ Confirm
+ Edit Note
+
diff --git a/tutorials/NotepadCodeLab/Notepadv2/src/com/android/demo/notepad2/Notepadv2.java b/tutorials/NotepadCodeLab/Notepadv2/src/com/android/demo/notepad2/Notepadv2.java
new file mode 100755
index 000000000..abfc53864
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv2/src/com/android/demo/notepad2/Notepadv2.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.demo.notepad2;
+
+import android.app.ListActivity;
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
+
+public class Notepadv2 extends ListActivity {
+ private static final int ACTIVITY_CREATE=0;
+ private static final int ACTIVITY_EDIT=1;
+
+ private static final int INSERT_ID = Menu.FIRST;
+ private static final int DELETE_ID = Menu.FIRST + 1;
+
+ private NotesDbAdapter mDbHelper;
+ private Cursor mNotesCursor;
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.notes_list);
+ mDbHelper = new NotesDbAdapter(this);
+ mDbHelper.open();
+ fillData();
+ }
+
+ private void fillData() {
+ // Get all of the rows from the database and create the item list
+ mNotesCursor = mDbHelper.fetchAllNotes();
+ startManagingCursor(mNotesCursor);
+
+ // Create an array to specify the fields we want to display in the list (only TITLE)
+ String[] from = new String[]{NotesDbAdapter.KEY_TITLE};
+
+ // and an array of the fields we want to bind those fields to (in this case just text1)
+ int[] to = new int[]{R.id.text1};
+
+ // Now create a simple cursor adapter and set it to display
+ SimpleCursorAdapter notes =
+ new SimpleCursorAdapter(this, R.layout.notes_row, mNotesCursor, from, to);
+ setListAdapter(notes);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ menu.add(0, INSERT_ID,0, R.string.menu_insert);
+ return true;
+ }
+
+ @Override
+ public boolean onMenuItemSelected(int featureId, MenuItem item) {
+ switch(item.getItemId()) {
+ case INSERT_ID:
+ createNote();
+ return true;
+ }
+
+ return super.onMenuItemSelected(featureId, item);
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v,
+ ContextMenuInfo menuInfo) {
+ super.onCreateContextMenu(menu, v, menuInfo);
+
+ // TODO: fill in rest of method
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ return super.onContextItemSelected(item);
+
+ // TODO: fill in rest of method
+ }
+
+ private void createNote() {
+ // TODO: fill in implementation
+
+ }
+
+ @Override
+ protected void onListItemClick(ListView l, View v, int position, long id) {
+ super.onListItemClick(l, v, position, id);
+
+ // TODO: fill in rest of method
+
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ super.onActivityResult(requestCode, resultCode, intent);
+
+ // TODO: fill in rest of method
+
+ }
+
+}
diff --git a/tutorials/NotepadCodeLab/Notepadv2/src/com/android/demo/notepad2/NotesDbAdapter.java b/tutorials/NotepadCodeLab/Notepadv2/src/com/android/demo/notepad2/NotesDbAdapter.java
new file mode 100755
index 000000000..5bf51b1e5
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv2/src/com/android/demo/notepad2/NotesDbAdapter.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.demo.notepad2;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
+
+/**
+ * Simple notes database access helper class. Defines the basic CRUD operations
+ * for the notepad example, and gives the ability to list all notes as well as
+ * retrieve or modify a specific note.
+ *
+ * This has been improved from the first version of this tutorial through the
+ * addition of better error handling and also using returning a Cursor instead
+ * of using a collection of inner classes (which is less scalable and not
+ * recommended).
+ */
+public class NotesDbAdapter {
+
+ public static final String KEY_TITLE = "title";
+ public static final String KEY_BODY = "body";
+ public static final String KEY_ROWID = "_id";
+
+ private static final String TAG = "NotesDbAdapter";
+ private DatabaseHelper mDbHelper;
+ private SQLiteDatabase mDb;
+
+ /**
+ * Database creation sql statement
+ */
+ private static final String DATABASE_CREATE =
+ "create table notes (_id integer primary key autoincrement, "
+ + "title text not null, body text not null);";
+
+ private static final String DATABASE_NAME = "data";
+ private static final String DATABASE_TABLE = "notes";
+ private static final int DATABASE_VERSION = 2;
+
+ private final Context mCtx;
+
+ private static class DatabaseHelper extends SQLiteOpenHelper {
+
+ DatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+
+ db.execSQL(DATABASE_CREATE);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+ + newVersion + ", which will destroy all old data");
+ db.execSQL("DROP TABLE IF EXISTS notes");
+ onCreate(db);
+ }
+ }
+
+ /**
+ * Constructor - takes the context to allow the database to be
+ * opened/created
+ *
+ * @param ctx the Context within which to work
+ */
+ public NotesDbAdapter(Context ctx) {
+ this.mCtx = ctx;
+ }
+
+ /**
+ * Open the notes database. If it cannot be opened, try to create a new
+ * instance of the database. If it cannot be created, throw an exception to
+ * signal the failure
+ *
+ * @return this (self reference, allowing this to be chained in an
+ * initialization call)
+ * @throws SQLException if the database could be neither opened or created
+ */
+ public NotesDbAdapter open() throws SQLException {
+ mDbHelper = new DatabaseHelper(mCtx);
+ mDb = mDbHelper.getWritableDatabase();
+ return this;
+ }
+
+ public void close() {
+ mDbHelper.close();
+ }
+
+
+ /**
+ * Create a new note using the title and body provided. If the note is
+ * successfully created return the new rowId for that note, otherwise return
+ * a -1 to indicate failure.
+ *
+ * @param title the title of the note
+ * @param body the body of the note
+ * @return rowId or -1 if failed
+ */
+ public long createNote(String title, String body) {
+ ContentValues initialValues = new ContentValues();
+ initialValues.put(KEY_TITLE, title);
+ initialValues.put(KEY_BODY, body);
+
+ return mDb.insert(DATABASE_TABLE, null, initialValues);
+ }
+
+ /**
+ * Delete the note with the given rowId
+ *
+ * @param rowId id of note to delete
+ * @return true if deleted, false otherwise
+ */
+ public boolean deleteNote(long rowId) {
+
+ return mDb.delete(DATABASE_TABLE, KEY_ROWID + "=" + rowId, null) > 0;
+ }
+
+ /**
+ * Return a Cursor over the list of all notes in the database
+ *
+ * @return Cursor over all notes
+ */
+ public Cursor fetchAllNotes() {
+
+ return mDb.query(DATABASE_TABLE, new String[] {KEY_ROWID, KEY_TITLE,
+ KEY_BODY}, null, null, null, null, null);
+ }
+
+ /**
+ * Return a Cursor positioned at the note that matches the given rowId
+ *
+ * @param rowId id of note to retrieve
+ * @return Cursor positioned to matching note, if found
+ * @throws SQLException if note could not be found/retrieved
+ */
+ public Cursor fetchNote(long rowId) throws SQLException {
+
+ Cursor mCursor =
+
+ mDb.query(true, DATABASE_TABLE, new String[] {KEY_ROWID,
+ KEY_TITLE, KEY_BODY}, KEY_ROWID + "=" + rowId, null,
+ null, null, null, null);
+ if (mCursor != null) {
+ mCursor.moveToFirst();
+ }
+ return mCursor;
+
+ }
+
+ /**
+ * Update the note using the details provided. The note to be updated is
+ * specified using the rowId, and it is altered to use the title and body
+ * values passed in
+ *
+ * @param rowId id of note to update
+ * @param title value to set note title to
+ * @param body value to set note body to
+ * @return true if the note was successfully updated, false otherwise
+ */
+ public boolean updateNote(long rowId, String title, String body) {
+ ContentValues args = new ContentValues();
+ args.put(KEY_TITLE, title);
+ args.put(KEY_BODY, body);
+
+ return mDb.update(DATABASE_TABLE, args, KEY_ROWID + "=" + rowId, null) > 0;
+ }
+}
diff --git a/tutorials/NotepadCodeLab/Notepadv2Solution/Android.mk b/tutorials/NotepadCodeLab/Notepadv2Solution/Android.mk
new file mode 100644
index 000000000..8e483519b
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv2Solution/Android.mk
@@ -0,0 +1,30 @@
+#
+# Copyright (C) 2009 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.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := Notepadv2Solution
+
+# Make the app build against the current SDK
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/tutorials/NotepadCodeLab/Notepadv2Solution/AndroidManifest.xml b/tutorials/NotepadCodeLab/Notepadv2Solution/AndroidManifest.xml
new file mode 100755
index 000000000..dbfc9d0f7
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv2Solution/AndroidManifest.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tutorials/NotepadCodeLab/Notepadv2Solution/res/drawable/icon.png b/tutorials/NotepadCodeLab/Notepadv2Solution/res/drawable/icon.png
new file mode 100755
index 000000000..64e3601c2
Binary files /dev/null and b/tutorials/NotepadCodeLab/Notepadv2Solution/res/drawable/icon.png differ
diff --git a/tutorials/NotepadCodeLab/Notepadv2Solution/res/layout/note_edit.xml b/tutorials/NotepadCodeLab/Notepadv2Solution/res/layout/note_edit.xml
new file mode 100755
index 000000000..b254552c3
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv2Solution/res/layout/note_edit.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tutorials/NotepadCodeLab/Notepadv2Solution/res/layout/notes_list.xml b/tutorials/NotepadCodeLab/Notepadv2Solution/res/layout/notes_list.xml
new file mode 100755
index 000000000..6ae04729e
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv2Solution/res/layout/notes_list.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/tutorials/NotepadCodeLab/Notepadv2Solution/res/layout/notes_row.xml b/tutorials/NotepadCodeLab/Notepadv2Solution/res/layout/notes_row.xml
new file mode 100755
index 000000000..f28a41bea
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv2Solution/res/layout/notes_row.xml
@@ -0,0 +1,4 @@
+
+
diff --git a/tutorials/NotepadCodeLab/Notepadv2Solution/res/values/strings.xml b/tutorials/NotepadCodeLab/Notepadv2Solution/res/values/strings.xml
new file mode 100755
index 000000000..b70c3f843
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv2Solution/res/values/strings.xml
@@ -0,0 +1,11 @@
+
+
+ Notepad v2
+ No Notes Yet
+ Add Note
+ Delete Note
+ Title
+ Body
+ Confirm
+ Edit Note
+
diff --git a/tutorials/NotepadCodeLab/Notepadv2Solution/src/com/android/demo/notepad2/NoteEdit.java b/tutorials/NotepadCodeLab/Notepadv2Solution/src/com/android/demo/notepad2/NoteEdit.java
new file mode 100755
index 000000000..6c5e664b5
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv2Solution/src/com/android/demo/notepad2/NoteEdit.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.savedInstanceState
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.demo.notepad2;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+
+public class NoteEdit extends Activity {
+
+ private EditText mTitleText;
+ private EditText mBodyText;
+ private Long mRowId;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.note_edit);
+
+ mTitleText = (EditText) findViewById(R.id.title);
+ mBodyText = (EditText) findViewById(R.id.body);
+
+ Button confirmButton = (Button) findViewById(R.id.confirm);
+
+ mRowId = null;
+ Bundle extras = getIntent().getExtras();
+ if (extras != null) {
+ String title = extras.getString(NotesDbAdapter.KEY_TITLE);
+ String body = extras.getString(NotesDbAdapter.KEY_BODY);
+ mRowId = extras.getLong(NotesDbAdapter.KEY_ROWID);
+
+ if (title != null) {
+ mTitleText.setText(title);
+ }
+ if (body != null) {
+ mBodyText.setText(body);
+ }
+ }
+
+ confirmButton.setOnClickListener(new View.OnClickListener() {
+
+ public void onClick(View view) {
+ Bundle bundle = new Bundle();
+
+ bundle.putString(NotesDbAdapter.KEY_TITLE, mTitleText.getText().toString());
+ bundle.putString(NotesDbAdapter.KEY_BODY, mBodyText.getText().toString());
+ if (mRowId != null) {
+ bundle.putLong(NotesDbAdapter.KEY_ROWID, mRowId);
+ }
+
+ Intent mIntent = new Intent();
+ mIntent.putExtras(bundle);
+ setResult(RESULT_OK, mIntent);
+ finish();
+ }
+
+ });
+ }
+}
diff --git a/tutorials/NotepadCodeLab/Notepadv2Solution/src/com/android/demo/notepad2/Notepadv2.java b/tutorials/NotepadCodeLab/Notepadv2Solution/src/com/android/demo/notepad2/Notepadv2.java
new file mode 100755
index 000000000..1b3398291
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv2Solution/src/com/android/demo/notepad2/Notepadv2.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.demo.notepad2;
+
+import android.app.ListActivity;
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.view.ContextMenu;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+
+public class Notepadv2 extends ListActivity {
+ private static final int ACTIVITY_CREATE=0;
+ private static final int ACTIVITY_EDIT=1;
+
+ private static final int INSERT_ID = Menu.FIRST;
+ private static final int DELETE_ID = Menu.FIRST + 1;
+
+ private NotesDbAdapter mDbHelper;
+ private Cursor mNotesCursor;
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.notes_list);
+ mDbHelper = new NotesDbAdapter(this);
+ mDbHelper.open();
+ fillData();
+ registerForContextMenu(getListView());
+ }
+
+ private void fillData() {
+ // Get all of the rows from the database and create the item list
+ mNotesCursor = mDbHelper.fetchAllNotes();
+ startManagingCursor(mNotesCursor);
+
+ // Create an array to specify the fields we want to display in the list (only TITLE)
+ String[] from = new String[]{NotesDbAdapter.KEY_TITLE};
+
+ // and an array of the fields we want to bind those fields to (in this case just text1)
+ int[] to = new int[]{R.id.text1};
+
+ // Now create a simple cursor adapter and set it to display
+ SimpleCursorAdapter notes =
+ new SimpleCursorAdapter(this, R.layout.notes_row, mNotesCursor, from, to);
+ setListAdapter(notes);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ menu.add(0, INSERT_ID,0, R.string.menu_insert);
+ return true;
+ }
+
+ @Override
+ public boolean onMenuItemSelected(int featureId, MenuItem item) {
+ switch(item.getItemId()) {
+ case INSERT_ID:
+ createNote();
+ return true;
+ }
+ return super.onMenuItemSelected(featureId, item);
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v,
+ ContextMenuInfo menuInfo) {
+ super.onCreateContextMenu(menu, v, menuInfo);
+ menu.add(0, DELETE_ID, 0, R.string.menu_delete);
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ switch(item.getItemId()) {
+ case DELETE_ID:
+ AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
+ mDbHelper.deleteNote(info.id);
+ fillData();
+ return true;
+ }
+ return super.onContextItemSelected(item);
+ }
+
+ private void createNote() {
+ Intent i = new Intent(this, NoteEdit.class);
+ startActivityForResult(i, ACTIVITY_CREATE);
+ }
+
+ @Override
+ protected void onListItemClick(ListView l, View v, int position, long id) {
+ super.onListItemClick(l, v, position, id);
+ Cursor c = mNotesCursor;
+ c.moveToPosition(position);
+ Intent i = new Intent(this, NoteEdit.class);
+ i.putExtra(NotesDbAdapter.KEY_ROWID, id);
+ i.putExtra(NotesDbAdapter.KEY_TITLE, c.getString(
+ c.getColumnIndexOrThrow(NotesDbAdapter.KEY_TITLE)));
+ i.putExtra(NotesDbAdapter.KEY_BODY, c.getString(
+ c.getColumnIndexOrThrow(NotesDbAdapter.KEY_BODY)));
+ startActivityForResult(i, ACTIVITY_EDIT);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ super.onActivityResult(requestCode, resultCode, intent);
+ Bundle extras = intent.getExtras();
+ switch(requestCode) {
+ case ACTIVITY_CREATE:
+ String title = extras.getString(NotesDbAdapter.KEY_TITLE);
+ String body = extras.getString(NotesDbAdapter.KEY_BODY);
+ mDbHelper.createNote(title, body);
+ fillData();
+ break;
+ case ACTIVITY_EDIT:
+ Long rowId = extras.getLong(NotesDbAdapter.KEY_ROWID);
+ if (rowId != null) {
+ String editTitle = extras.getString(NotesDbAdapter.KEY_TITLE);
+ String editBody = extras.getString(NotesDbAdapter.KEY_BODY);
+ mDbHelper.updateNote(rowId, editTitle, editBody);
+ }
+ fillData();
+ break;
+ }
+ }
+}
diff --git a/tutorials/NotepadCodeLab/Notepadv2Solution/src/com/android/demo/notepad2/NotesDbAdapter.java b/tutorials/NotepadCodeLab/Notepadv2Solution/src/com/android/demo/notepad2/NotesDbAdapter.java
new file mode 100755
index 000000000..5bf51b1e5
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv2Solution/src/com/android/demo/notepad2/NotesDbAdapter.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.demo.notepad2;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
+
+/**
+ * Simple notes database access helper class. Defines the basic CRUD operations
+ * for the notepad example, and gives the ability to list all notes as well as
+ * retrieve or modify a specific note.
+ *
+ * This has been improved from the first version of this tutorial through the
+ * addition of better error handling and also using returning a Cursor instead
+ * of using a collection of inner classes (which is less scalable and not
+ * recommended).
+ */
+public class NotesDbAdapter {
+
+ public static final String KEY_TITLE = "title";
+ public static final String KEY_BODY = "body";
+ public static final String KEY_ROWID = "_id";
+
+ private static final String TAG = "NotesDbAdapter";
+ private DatabaseHelper mDbHelper;
+ private SQLiteDatabase mDb;
+
+ /**
+ * Database creation sql statement
+ */
+ private static final String DATABASE_CREATE =
+ "create table notes (_id integer primary key autoincrement, "
+ + "title text not null, body text not null);";
+
+ private static final String DATABASE_NAME = "data";
+ private static final String DATABASE_TABLE = "notes";
+ private static final int DATABASE_VERSION = 2;
+
+ private final Context mCtx;
+
+ private static class DatabaseHelper extends SQLiteOpenHelper {
+
+ DatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+
+ db.execSQL(DATABASE_CREATE);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+ + newVersion + ", which will destroy all old data");
+ db.execSQL("DROP TABLE IF EXISTS notes");
+ onCreate(db);
+ }
+ }
+
+ /**
+ * Constructor - takes the context to allow the database to be
+ * opened/created
+ *
+ * @param ctx the Context within which to work
+ */
+ public NotesDbAdapter(Context ctx) {
+ this.mCtx = ctx;
+ }
+
+ /**
+ * Open the notes database. If it cannot be opened, try to create a new
+ * instance of the database. If it cannot be created, throw an exception to
+ * signal the failure
+ *
+ * @return this (self reference, allowing this to be chained in an
+ * initialization call)
+ * @throws SQLException if the database could be neither opened or created
+ */
+ public NotesDbAdapter open() throws SQLException {
+ mDbHelper = new DatabaseHelper(mCtx);
+ mDb = mDbHelper.getWritableDatabase();
+ return this;
+ }
+
+ public void close() {
+ mDbHelper.close();
+ }
+
+
+ /**
+ * Create a new note using the title and body provided. If the note is
+ * successfully created return the new rowId for that note, otherwise return
+ * a -1 to indicate failure.
+ *
+ * @param title the title of the note
+ * @param body the body of the note
+ * @return rowId or -1 if failed
+ */
+ public long createNote(String title, String body) {
+ ContentValues initialValues = new ContentValues();
+ initialValues.put(KEY_TITLE, title);
+ initialValues.put(KEY_BODY, body);
+
+ return mDb.insert(DATABASE_TABLE, null, initialValues);
+ }
+
+ /**
+ * Delete the note with the given rowId
+ *
+ * @param rowId id of note to delete
+ * @return true if deleted, false otherwise
+ */
+ public boolean deleteNote(long rowId) {
+
+ return mDb.delete(DATABASE_TABLE, KEY_ROWID + "=" + rowId, null) > 0;
+ }
+
+ /**
+ * Return a Cursor over the list of all notes in the database
+ *
+ * @return Cursor over all notes
+ */
+ public Cursor fetchAllNotes() {
+
+ return mDb.query(DATABASE_TABLE, new String[] {KEY_ROWID, KEY_TITLE,
+ KEY_BODY}, null, null, null, null, null);
+ }
+
+ /**
+ * Return a Cursor positioned at the note that matches the given rowId
+ *
+ * @param rowId id of note to retrieve
+ * @return Cursor positioned to matching note, if found
+ * @throws SQLException if note could not be found/retrieved
+ */
+ public Cursor fetchNote(long rowId) throws SQLException {
+
+ Cursor mCursor =
+
+ mDb.query(true, DATABASE_TABLE, new String[] {KEY_ROWID,
+ KEY_TITLE, KEY_BODY}, KEY_ROWID + "=" + rowId, null,
+ null, null, null, null);
+ if (mCursor != null) {
+ mCursor.moveToFirst();
+ }
+ return mCursor;
+
+ }
+
+ /**
+ * Update the note using the details provided. The note to be updated is
+ * specified using the rowId, and it is altered to use the title and body
+ * values passed in
+ *
+ * @param rowId id of note to update
+ * @param title value to set note title to
+ * @param body value to set note body to
+ * @return true if the note was successfully updated, false otherwise
+ */
+ public boolean updateNote(long rowId, String title, String body) {
+ ContentValues args = new ContentValues();
+ args.put(KEY_TITLE, title);
+ args.put(KEY_BODY, body);
+
+ return mDb.update(DATABASE_TABLE, args, KEY_ROWID + "=" + rowId, null) > 0;
+ }
+}
diff --git a/tutorials/NotepadCodeLab/Notepadv3/AndroidManifest.xml b/tutorials/NotepadCodeLab/Notepadv3/AndroidManifest.xml
new file mode 100755
index 000000000..adb07ffdb
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv3/AndroidManifest.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tutorials/NotepadCodeLab/Notepadv3/res/drawable/icon.png b/tutorials/NotepadCodeLab/Notepadv3/res/drawable/icon.png
new file mode 100755
index 000000000..64e3601c2
Binary files /dev/null and b/tutorials/NotepadCodeLab/Notepadv3/res/drawable/icon.png differ
diff --git a/tutorials/NotepadCodeLab/Notepadv3/res/layout/note_edit.xml b/tutorials/NotepadCodeLab/Notepadv3/res/layout/note_edit.xml
new file mode 100755
index 000000000..b254552c3
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv3/res/layout/note_edit.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tutorials/NotepadCodeLab/Notepadv3/res/layout/notes_list.xml b/tutorials/NotepadCodeLab/Notepadv3/res/layout/notes_list.xml
new file mode 100755
index 000000000..6ae04729e
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv3/res/layout/notes_list.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/tutorials/NotepadCodeLab/Notepadv3/res/layout/notes_row.xml b/tutorials/NotepadCodeLab/Notepadv3/res/layout/notes_row.xml
new file mode 100755
index 000000000..f28a41bea
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv3/res/layout/notes_row.xml
@@ -0,0 +1,4 @@
+
+
diff --git a/tutorials/NotepadCodeLab/Notepadv3/res/values/strings.xml b/tutorials/NotepadCodeLab/Notepadv3/res/values/strings.xml
new file mode 100755
index 000000000..ae63b837d
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv3/res/values/strings.xml
@@ -0,0 +1,11 @@
+
+
+ Notepad v3
+ No Notes Yet
+ Add Note
+ Delete Note
+ Title
+ Body
+ Confirm
+ Edit Note
+
diff --git a/tutorials/NotepadCodeLab/Notepadv3/src/com/android/demo/notepad3/NoteEdit.java b/tutorials/NotepadCodeLab/Notepadv3/src/com/android/demo/notepad3/NoteEdit.java
new file mode 100755
index 000000000..af3a2340d
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv3/src/com/android/demo/notepad3/NoteEdit.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.demo.notepad3;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+
+public class NoteEdit extends Activity {
+
+ private EditText mTitleText;
+ private EditText mBodyText;
+ private Long mRowId;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.note_edit);
+
+ mTitleText = (EditText) findViewById(R.id.title);
+ mBodyText = (EditText) findViewById(R.id.body);
+
+ Button confirmButton = (Button) findViewById(R.id.confirm);
+
+ mRowId = null;
+ Bundle extras = getIntent().getExtras();
+ if (extras != null) {
+ String title = extras.getString(NotesDbAdapter.KEY_TITLE);
+ String body = extras.getString(NotesDbAdapter.KEY_BODY);
+ mRowId = extras.getLong(NotesDbAdapter.KEY_ROWID);
+
+ if (title != null) {
+ mTitleText.setText(title);
+ }
+ if (body != null) {
+ mBodyText.setText(body);
+ }
+ }
+
+ confirmButton.setOnClickListener(new View.OnClickListener() {
+
+ public void onClick(View view) {
+ Bundle bundle = new Bundle();
+
+ bundle.putString(NotesDbAdapter.KEY_TITLE, mTitleText.getText().toString());
+ bundle.putString(NotesDbAdapter.KEY_BODY, mBodyText.getText().toString());
+ if (mRowId != null) {
+ bundle.putLong(NotesDbAdapter.KEY_ROWID, mRowId);
+ }
+
+ Intent mIntent = new Intent();
+ mIntent.putExtras(bundle);
+ setResult(RESULT_OK, mIntent);
+ finish();
+ }
+
+ });
+ }
+}
diff --git a/tutorials/NotepadCodeLab/Notepadv3/src/com/android/demo/notepad3/Notepadv3.java b/tutorials/NotepadCodeLab/Notepadv3/src/com/android/demo/notepad3/Notepadv3.java
new file mode 100755
index 000000000..f50f371d8
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv3/src/com/android/demo/notepad3/Notepadv3.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License")savedInstanceState;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.demo.notepad3;
+
+import android.app.ListActivity;
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.view.ContextMenu;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+
+public class Notepadv3 extends ListActivity {
+ private static final int ACTIVITY_CREATE=0;
+ private static final int ACTIVITY_EDIT=1;
+
+ private static final int INSERT_ID = Menu.FIRST;
+ private static final int DELETE_ID = Menu.FIRST + 1;
+
+ private NotesDbAdapter mDbHelper;
+ private Cursor mNotesCursor;
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.notes_list);
+ mDbHelper = new NotesDbAdapter(this);
+ mDbHelper.open();
+ fillData();
+ registerForContextMenu(getListView());
+ }
+
+ private void fillData() {
+ // Get all of the rows from the database and create the item list
+ mNotesCursor = mDbHelper.fetchAllNotes();
+ startManagingCursor(mNotesCursor);
+
+ // Create an array to specify the fields we want to display in the list (only TITLE)
+ String[] from = new String[]{NotesDbAdapter.KEY_TITLE};
+
+ // and an array of the fields we want to bind those fields to (in this case just text1)
+ int[] to = new int[]{R.id.text1};
+
+ // Now create a simple cursor adapter and set it to display
+ SimpleCursorAdapter notes =
+ new SimpleCursorAdapter(this, R.layout.notes_row, mNotesCursor, from, to);
+ setListAdapter(notes);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ menu.add(0, INSERT_ID, 0, R.string.menu_insert);
+ return true;
+ }
+
+ @Override
+ public boolean onMenuItemSelected(int featureId, MenuItem item) {
+ switch(item.getItemId()) {
+ case INSERT_ID:
+ createNote();
+ return true;
+ }
+
+ return super.onMenuItemSelected(featureId, item);
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v,
+ ContextMenuInfo menuInfo) {
+ super.onCreateContextMenu(menu, v, menuInfo);
+ menu.add(0, DELETE_ID, 0, R.string.menu_delete);
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ switch(item.getItemId()) {
+ case DELETE_ID:
+ AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
+ mDbHelper.deleteNote(info.id);
+ fillData();
+ return true;
+ }
+ return super.onContextItemSelected(item);
+ }
+
+ private void createNote() {
+ Intent i = new Intent(this, NoteEdit.class);
+ startActivityForResult(i, ACTIVITY_CREATE);
+ }
+
+ @Override
+ protected void onListItemClick(ListView l, View v, int position, long id) {
+ super.onListItemClick(l, v, position, id);
+ Cursor c = mNotesCursor;
+ c.moveToPosition(position);
+ Intent i = new Intent(this, NoteEdit.class);
+ i.putExtra(NotesDbAdapter.KEY_ROWID, id);
+ i.putExtra(NotesDbAdapter.KEY_TITLE, c.getString(
+ c.getColumnIndexOrThrow(NotesDbAdapter.KEY_TITLE)));
+ i.putExtra(NotesDbAdapter.KEY_BODY, c.getString(
+ c.getColumnIndexOrThrow(NotesDbAdapter.KEY_BODY)));
+ startActivityForResult(i, ACTIVITY_EDIT);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ super.onActivityResult(requestCode, resultCode, intent);
+ Bundle extras = intent.getExtras();
+ switch(requestCode) {
+ case ACTIVITY_CREATE:
+ String title = extras.getString(NotesDbAdapter.KEY_TITLE);
+ String body = extras.getString(NotesDbAdapter.KEY_BODY);
+ mDbHelper.createNote(title, body);
+ fillData();
+ break;
+ case ACTIVITY_EDIT:
+ Long rowId = extras.getLong(NotesDbAdapter.KEY_ROWID);
+ if (rowId != null) {
+ String editTitle = extras.getString(NotesDbAdapter.KEY_TITLE);
+ String editBody = extras.getString(NotesDbAdapter.KEY_BODY);
+ mDbHelper.updateNote(rowId, editTitle, editBody);
+ }
+ fillData();
+ break;
+ }
+ }
+}
diff --git a/tutorials/NotepadCodeLab/Notepadv3/src/com/android/demo/notepad3/NotesDbAdapter.java b/tutorials/NotepadCodeLab/Notepadv3/src/com/android/demo/notepad3/NotesDbAdapter.java
new file mode 100755
index 000000000..61ad04623
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv3/src/com/android/demo/notepad3/NotesDbAdapter.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.demo.notepad3;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
+
+/**
+ * Simple notes database access helper class. Defines the basic CRUD operations
+ * for the notepad example, and gives the ability to list all notes as well as
+ * retrieve or modify a specific note.
+ *
+ * This has been improved from the first version of this tutorial through the
+ * addition of better error handling and also using returning a Cursor instead
+ * of using a collection of inner classes (which is less scalable and not
+ * recommended).
+ */
+public class NotesDbAdapter {
+
+ public static final String KEY_TITLE = "title";
+ public static final String KEY_BODY = "body";
+ public static final String KEY_ROWID = "_id";
+
+ private static final String TAG = "NotesDbAdapter";
+ private DatabaseHelper mDbHelper;
+ private SQLiteDatabase mDb;
+
+ /**
+ * Database creation sql statement
+ */
+ private static final String DATABASE_CREATE =
+ "create table notes (_id integer primary key autoincrement, "
+ + "title text not null, body text not null);";
+
+ private static final String DATABASE_NAME = "data";
+ private static final String DATABASE_TABLE = "notes";
+ private static final int DATABASE_VERSION = 2;
+
+ private final Context mCtx;
+
+ private static class DatabaseHelper extends SQLiteOpenHelper {
+
+ DatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+
+ db.execSQL(DATABASE_CREATE);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+ + newVersion + ", which will destroy all old data");
+ db.execSQL("DROP TABLE IF EXISTS notes");
+ onCreate(db);
+ }
+ }
+
+ /**
+ * Constructor - takes the context to allow the database to be
+ * opened/created
+ *
+ * @param ctx the Context within which to work
+ */
+ public NotesDbAdapter(Context ctx) {
+ this.mCtx = ctx;
+ }
+
+ /**
+ * Open the notes database. If it cannot be opened, try to create a new
+ * instance of the database. If it cannot be created, throw an exception to
+ * signal the failure
+ *
+ * @return this (self reference, allowing this to be chained in an
+ * initialization call)
+ * @throws SQLException if the database could be neither opened or created
+ */
+ public NotesDbAdapter open() throws SQLException {
+ mDbHelper = new DatabaseHelper(mCtx);
+ mDb = mDbHelper.getWritableDatabase();
+ return this;
+ }
+
+ public void close() {
+ mDbHelper.close();
+ }
+
+
+ /**
+ * Create a new note using the title and body provided. If the note is
+ * successfully created return the new rowId for that note, otherwise return
+ * a -1 to indicate failure.
+ *
+ * @param title the title of the note
+ * @param body the body of the note
+ * @return rowId or -1 if failed
+ */
+ public long createNote(String title, String body) {
+ ContentValues initialValues = new ContentValues();
+ initialValues.put(KEY_TITLE, title);
+ initialValues.put(KEY_BODY, body);
+
+ return mDb.insert(DATABASE_TABLE, null, initialValues);
+ }
+
+ /**
+ * Delete the note with the given rowId
+ *
+ * @param rowId id of note to delete
+ * @return true if deleted, false otherwise
+ */
+ public boolean deleteNote(long rowId) {
+
+ return mDb.delete(DATABASE_TABLE, KEY_ROWID + "=" + rowId, null) > 0;
+ }
+
+ /**
+ * Return a Cursor over the list of all notes in the database
+ *
+ * @return Cursor over all notes
+ */
+ public Cursor fetchAllNotes() {
+
+ return mDb.query(DATABASE_TABLE, new String[] {KEY_ROWID, KEY_TITLE,
+ KEY_BODY}, null, null, null, null, null);
+ }
+
+ /**
+ * Return a Cursor positioned at the note that matches the given rowId
+ *
+ * @param rowId id of note to retrieve
+ * @return Cursor positioned to matching note, if found
+ * @throws SQLException if note could not be found/retrieved
+ */
+ public Cursor fetchNote(long rowId) throws SQLException {
+
+ Cursor mCursor =
+
+ mDb.query(true, DATABASE_TABLE, new String[] {KEY_ROWID,
+ KEY_TITLE, KEY_BODY}, KEY_ROWID + "=" + rowId, null,
+ null, null, null, null);
+ if (mCursor != null) {
+ mCursor.moveToFirst();
+ }
+ return mCursor;
+
+ }
+
+ /**
+ * Update the note using the details provided. The note to be updated is
+ * specified using the rowId, and it is altered to use the title and body
+ * values passed in
+ *
+ * @param rowId id of note to update
+ * @param title value to set note title to
+ * @param body value to set note body to
+ * @return true if the note was successfully updated, false otherwise
+ */
+ public boolean updateNote(long rowId, String title, String body) {
+ ContentValues args = new ContentValues();
+ args.put(KEY_TITLE, title);
+ args.put(KEY_BODY, body);
+
+ return mDb.update(DATABASE_TABLE, args, KEY_ROWID + "=" + rowId, null) > 0;
+ }
+}
diff --git a/tutorials/NotepadCodeLab/Notepadv3Solution/Android.mk b/tutorials/NotepadCodeLab/Notepadv3Solution/Android.mk
new file mode 100644
index 000000000..177164ed8
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv3Solution/Android.mk
@@ -0,0 +1,30 @@
+#
+# Copyright (C) 2009 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.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := Notepadv3Solution
+
+# Make the app build against the current SDK
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/tutorials/NotepadCodeLab/Notepadv3Solution/AndroidManifest.xml b/tutorials/NotepadCodeLab/Notepadv3Solution/AndroidManifest.xml
new file mode 100755
index 000000000..adb07ffdb
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv3Solution/AndroidManifest.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tutorials/NotepadCodeLab/Notepadv3Solution/res/drawable/icon.png b/tutorials/NotepadCodeLab/Notepadv3Solution/res/drawable/icon.png
new file mode 100755
index 000000000..64e3601c2
Binary files /dev/null and b/tutorials/NotepadCodeLab/Notepadv3Solution/res/drawable/icon.png differ
diff --git a/tutorials/NotepadCodeLab/Notepadv3Solution/res/layout/note_edit.xml b/tutorials/NotepadCodeLab/Notepadv3Solution/res/layout/note_edit.xml
new file mode 100755
index 000000000..b254552c3
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv3Solution/res/layout/note_edit.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tutorials/NotepadCodeLab/Notepadv3Solution/res/layout/notes_list.xml b/tutorials/NotepadCodeLab/Notepadv3Solution/res/layout/notes_list.xml
new file mode 100755
index 000000000..6ae04729e
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv3Solution/res/layout/notes_list.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/tutorials/NotepadCodeLab/Notepadv3Solution/res/layout/notes_row.xml b/tutorials/NotepadCodeLab/Notepadv3Solution/res/layout/notes_row.xml
new file mode 100755
index 000000000..f28a41bea
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv3Solution/res/layout/notes_row.xml
@@ -0,0 +1,4 @@
+
+
diff --git a/tutorials/NotepadCodeLab/Notepadv3Solution/res/values/strings.xml b/tutorials/NotepadCodeLab/Notepadv3Solution/res/values/strings.xml
new file mode 100755
index 000000000..ae63b837d
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv3Solution/res/values/strings.xml
@@ -0,0 +1,11 @@
+
+
+ Notepad v3
+ No Notes Yet
+ Add Note
+ Delete Note
+ Title
+ Body
+ Confirm
+ Edit Note
+
diff --git a/tutorials/NotepadCodeLab/Notepadv3Solution/src/com/android/demo/notepad3/NoteEdit.java b/tutorials/NotepadCodeLab/Notepadv3Solution/src/com/android/demo/notepad3/NoteEdit.java
new file mode 100755
index 000000000..f5eb6c433
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv3Solution/src/com/android/demo/notepad3/NoteEdit.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.demo.notepad3;
+
+import android.app.Activity;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+
+public class NoteEdit extends Activity {
+
+ private EditText mTitleText;
+ private EditText mBodyText;
+ private Long mRowId;
+ private NotesDbAdapter mDbHelper;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mDbHelper = new NotesDbAdapter(this);
+ mDbHelper.open();
+ setContentView(R.layout.note_edit);
+
+
+ mTitleText = (EditText) findViewById(R.id.title);
+ mBodyText = (EditText) findViewById(R.id.body);
+
+ Button confirmButton = (Button) findViewById(R.id.confirm);
+
+ mRowId = (savedInstanceState == null) ? null :
+ (Long) savedInstanceState.getSerializable(NotesDbAdapter.KEY_ROWID);
+ if (mRowId == null) {
+ Bundle extras = getIntent().getExtras();
+ mRowId = extras != null ? extras.getLong(NotesDbAdapter.KEY_ROWID)
+ : null;
+ }
+
+ populateFields();
+
+ confirmButton.setOnClickListener(new View.OnClickListener() {
+
+ public void onClick(View view) {
+ setResult(RESULT_OK);
+ finish();
+ }
+
+ });
+ }
+
+ private void populateFields() {
+ if (mRowId != null) {
+ Cursor note = mDbHelper.fetchNote(mRowId);
+ startManagingCursor(note);
+ mTitleText.setText(note.getString(
+ note.getColumnIndexOrThrow(NotesDbAdapter.KEY_TITLE)));
+ mBodyText.setText(note.getString(
+ note.getColumnIndexOrThrow(NotesDbAdapter.KEY_BODY)));
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ saveState();
+ outState.putSerializable(NotesDbAdapter.KEY_ROWID, mRowId);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ saveState();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ populateFields();
+ }
+
+ private void saveState() {
+ String title = mTitleText.getText().toString();
+ String body = mBodyText.getText().toString();
+
+ if (mRowId == null) {
+ long id = mDbHelper.createNote(title, body);
+ if (id > 0) {
+ mRowId = id;
+ }
+ } else {
+ mDbHelper.updateNote(mRowId, title, body);
+ }
+ }
+
+}
diff --git a/tutorials/NotepadCodeLab/Notepadv3Solution/src/com/android/demo/notepad3/Notepadv3.java b/tutorials/NotepadCodeLab/Notepadv3Solution/src/com/android/demo/notepad3/Notepadv3.java
new file mode 100755
index 000000000..7f9903750
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv3Solution/src/com/android/demo/notepad3/Notepadv3.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License")savedInstanceState;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.demo.notepad3;
+
+import android.app.ListActivity;
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.view.ContextMenu;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+
+public class Notepadv3 extends ListActivity {
+ private static final int ACTIVITY_CREATE=0;
+ private static final int ACTIVITY_EDIT=1;
+
+ private static final int INSERT_ID = Menu.FIRST;
+ private static final int DELETE_ID = Menu.FIRST + 1;
+
+ private NotesDbAdapter mDbHelper;
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.notes_list);
+ mDbHelper = new NotesDbAdapter(this);
+ mDbHelper.open();
+ fillData();
+ registerForContextMenu(getListView());
+ }
+
+ private void fillData() {
+ Cursor notesCursor = mDbHelper.fetchAllNotes();
+ startManagingCursor(notesCursor);
+
+ // Create an array to specify the fields we want to display in the list (only TITLE)
+ String[] from = new String[]{NotesDbAdapter.KEY_TITLE};
+
+ // and an array of the fields we want to bind those fields to (in this case just text1)
+ int[] to = new int[]{R.id.text1};
+
+ // Now create a simple cursor adapter and set it to display
+ SimpleCursorAdapter notes =
+ new SimpleCursorAdapter(this, R.layout.notes_row, notesCursor, from, to);
+ setListAdapter(notes);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ menu.add(0, INSERT_ID, 0, R.string.menu_insert);
+ return true;
+ }
+
+ @Override
+ public boolean onMenuItemSelected(int featureId, MenuItem item) {
+ switch(item.getItemId()) {
+ case INSERT_ID:
+ createNote();
+ return true;
+ }
+
+ return super.onMenuItemSelected(featureId, item);
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v,
+ ContextMenuInfo menuInfo) {
+ super.onCreateContextMenu(menu, v, menuInfo);
+ menu.add(0, DELETE_ID, 0, R.string.menu_delete);
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ switch(item.getItemId()) {
+ case DELETE_ID:
+ AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
+ mDbHelper.deleteNote(info.id);
+ fillData();
+ return true;
+ }
+ return super.onContextItemSelected(item);
+ }
+
+ private void createNote() {
+ Intent i = new Intent(this, NoteEdit.class);
+ startActivityForResult(i, ACTIVITY_CREATE);
+ }
+
+ @Override
+ protected void onListItemClick(ListView l, View v, int position, long id) {
+ super.onListItemClick(l, v, position, id);
+ Intent i = new Intent(this, NoteEdit.class);
+ i.putExtra(NotesDbAdapter.KEY_ROWID, id);
+ startActivityForResult(i, ACTIVITY_EDIT);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode,
+ Intent intent) {
+ super.onActivityResult(requestCode, resultCode, intent);
+ fillData();
+ }
+}
diff --git a/tutorials/NotepadCodeLab/Notepadv3Solution/src/com/android/demo/notepad3/NotesDbAdapter.java b/tutorials/NotepadCodeLab/Notepadv3Solution/src/com/android/demo/notepad3/NotesDbAdapter.java
new file mode 100755
index 000000000..61ad04623
--- /dev/null
+++ b/tutorials/NotepadCodeLab/Notepadv3Solution/src/com/android/demo/notepad3/NotesDbAdapter.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.demo.notepad3;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
+
+/**
+ * Simple notes database access helper class. Defines the basic CRUD operations
+ * for the notepad example, and gives the ability to list all notes as well as
+ * retrieve or modify a specific note.
+ *
+ * This has been improved from the first version of this tutorial through the
+ * addition of better error handling and also using returning a Cursor instead
+ * of using a collection of inner classes (which is less scalable and not
+ * recommended).
+ */
+public class NotesDbAdapter {
+
+ public static final String KEY_TITLE = "title";
+ public static final String KEY_BODY = "body";
+ public static final String KEY_ROWID = "_id";
+
+ private static final String TAG = "NotesDbAdapter";
+ private DatabaseHelper mDbHelper;
+ private SQLiteDatabase mDb;
+
+ /**
+ * Database creation sql statement
+ */
+ private static final String DATABASE_CREATE =
+ "create table notes (_id integer primary key autoincrement, "
+ + "title text not null, body text not null);";
+
+ private static final String DATABASE_NAME = "data";
+ private static final String DATABASE_TABLE = "notes";
+ private static final int DATABASE_VERSION = 2;
+
+ private final Context mCtx;
+
+ private static class DatabaseHelper extends SQLiteOpenHelper {
+
+ DatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+
+ db.execSQL(DATABASE_CREATE);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+ + newVersion + ", which will destroy all old data");
+ db.execSQL("DROP TABLE IF EXISTS notes");
+ onCreate(db);
+ }
+ }
+
+ /**
+ * Constructor - takes the context to allow the database to be
+ * opened/created
+ *
+ * @param ctx the Context within which to work
+ */
+ public NotesDbAdapter(Context ctx) {
+ this.mCtx = ctx;
+ }
+
+ /**
+ * Open the notes database. If it cannot be opened, try to create a new
+ * instance of the database. If it cannot be created, throw an exception to
+ * signal the failure
+ *
+ * @return this (self reference, allowing this to be chained in an
+ * initialization call)
+ * @throws SQLException if the database could be neither opened or created
+ */
+ public NotesDbAdapter open() throws SQLException {
+ mDbHelper = new DatabaseHelper(mCtx);
+ mDb = mDbHelper.getWritableDatabase();
+ return this;
+ }
+
+ public void close() {
+ mDbHelper.close();
+ }
+
+
+ /**
+ * Create a new note using the title and body provided. If the note is
+ * successfully created return the new rowId for that note, otherwise return
+ * a -1 to indicate failure.
+ *
+ * @param title the title of the note
+ * @param body the body of the note
+ * @return rowId or -1 if failed
+ */
+ public long createNote(String title, String body) {
+ ContentValues initialValues = new ContentValues();
+ initialValues.put(KEY_TITLE, title);
+ initialValues.put(KEY_BODY, body);
+
+ return mDb.insert(DATABASE_TABLE, null, initialValues);
+ }
+
+ /**
+ * Delete the note with the given rowId
+ *
+ * @param rowId id of note to delete
+ * @return true if deleted, false otherwise
+ */
+ public boolean deleteNote(long rowId) {
+
+ return mDb.delete(DATABASE_TABLE, KEY_ROWID + "=" + rowId, null) > 0;
+ }
+
+ /**
+ * Return a Cursor over the list of all notes in the database
+ *
+ * @return Cursor over all notes
+ */
+ public Cursor fetchAllNotes() {
+
+ return mDb.query(DATABASE_TABLE, new String[] {KEY_ROWID, KEY_TITLE,
+ KEY_BODY}, null, null, null, null, null);
+ }
+
+ /**
+ * Return a Cursor positioned at the note that matches the given rowId
+ *
+ * @param rowId id of note to retrieve
+ * @return Cursor positioned to matching note, if found
+ * @throws SQLException if note could not be found/retrieved
+ */
+ public Cursor fetchNote(long rowId) throws SQLException {
+
+ Cursor mCursor =
+
+ mDb.query(true, DATABASE_TABLE, new String[] {KEY_ROWID,
+ KEY_TITLE, KEY_BODY}, KEY_ROWID + "=" + rowId, null,
+ null, null, null, null);
+ if (mCursor != null) {
+ mCursor.moveToFirst();
+ }
+ return mCursor;
+
+ }
+
+ /**
+ * Update the note using the details provided. The note to be updated is
+ * specified using the rowId, and it is altered to use the title and body
+ * values passed in
+ *
+ * @param rowId id of note to update
+ * @param title value to set note title to
+ * @param body value to set note body to
+ * @return true if the note was successfully updated, false otherwise
+ */
+ public boolean updateNote(long rowId, String title, String body) {
+ ContentValues args = new ContentValues();
+ args.put(KEY_TITLE, title);
+ args.put(KEY_BODY, body);
+
+ return mDb.update(DATABASE_TABLE, args, KEY_ROWID + "=" + rowId, null) > 0;
+ }
+}