diff options
| author | Ying Wang <wangying@google.com> | 2009-12-14 16:23:32 -0800 |
|---|---|---|
| committer | Ying Wang <wangying@google.com> | 2009-12-14 16:23:32 -0800 |
| commit | dc388c565be0f539ded2bca165fef347dc7a89cc (patch) | |
| tree | a9799e5db72a0032756d1eb12bf86d999513c5dc | |
| parent | a35897da6fcddec45270324a67614deaaacbde94 (diff) | |
| parent | 5595acbed8ca1778a61881a00d7baaffecb2a64c (diff) | |
Merge commit 'goog/eclair-mr2' into play-with-monkey
Conflicts:
cmds/monkey/src/com/android/commands/monkey/Monkey.java
134 files changed, 6783 insertions, 317 deletions
diff --git a/apps/Development/AndroidManifest.xml b/apps/Development/AndroidManifest.xml index 31427a59a..07a4213b3 100644 --- a/apps/Development/AndroidManifest.xml +++ b/apps/Development/AndroidManifest.xml @@ -58,13 +58,6 @@ <category android:name="android.intent.category.TEST" /> </intent-filter> </activity> - <activity android:name="ExceptionBrowser" android:label="Exception Browser"> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.TEST" /> - </intent-filter> - </activity> - <activity android:name="StacktraceViewer" android:label="Stacktrace Viewer"/> <activity android:name="PackageSummary" android:label="Package Summary"> </activity> <activity android:name="ShowActivity" android:label="Activity"> @@ -128,44 +121,50 @@ </intent-filter> </activity> - <activity android:name="GLSTester" android:label="Google Login Service"> + <activity android:name="GLSTester" android:label="Google Login Service"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.TEST" /> </intent-filter> </activity> - <activity android:name="RunningProcesses" android:label="Running processes"> + <activity android:name="RunningProcesses" android:label="Running processes"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.TEST" /> </intent-filter> </activity> - <activity android:name="ProcessInfo" android:label="Process Information"> + <activity android:name="ProcessInfo" android:label="Process Information"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> - </activity> + </activity> <!-- - <activity android:name="AppHwConfigList" android:label="Applications Hw Configuration"> + <activity android:name="AppHwConfigList" android:label="Applications Hw Configuration"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.TEST" /> </intent-filter> - </activity> + </activity> --> - <activity android:name="AppHwPref" android:label="Applications Hardware Preferences"> + <activity android:name="AppHwPref" android:label="Applications Hardware Preferences"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> - </activity> - <activity android:name="PermissionDetails" android:label="Permission Info"> + </activity> + <activity android:name="PermissionDetails" android:label="Permission Info"> <intent-filter> <action android:name="com.android.development.VIEW_PERMISSION" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> - </activity> + </activity> + <activity android:name="BadBehaviorActivity" android:label="Bad Behavior"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.TEST" /> + </intent-filter> + </activity> </application> </manifest> diff --git a/apps/Development/res/layout/bad_behavior.xml b/apps/Development/res/layout/bad_behavior.xml new file mode 100644 index 000000000..62a724502 --- /dev/null +++ b/apps/Development/res/layout/bad_behavior.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="fill_parent" + android:layout_height="fill_parent"> + + <Button android:id="@+id/bad_behavior_crash_main" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/bad_behavior_crash_main_label" /> + + <Button android:id="@+id/bad_behavior_crash_thread" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/bad_behavior_crash_thread_label" /> + + <Button android:id="@+id/bad_behavior_anr" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/bad_behavior_anr_label" /> + +</LinearLayout> diff --git a/apps/Development/res/values/strings.xml b/apps/Development/res/values/strings.xml index 350f74db7..cb41087f8 100644 --- a/apps/Development/res/values/strings.xml +++ b/apps/Development/res/values/strings.xml @@ -193,4 +193,9 @@ <string name="binding_bind_failed">Bind failed</string> <string name="binding_waiting_for_connection">Waiting for service to be connected...</string> <string name="select_account_to_sync">Select account to sync</string> + + <!-- BadBehaviorActivity --> + <string name="bad_behavior_crash_main_label">Crash the main thread</string> + <string name="bad_behavior_crash_thread_label">Crash an auxiliary thread</string> + <string name="bad_behavior_anr_label">Stop responding for 20 seconds (ANR)</string> </resources> diff --git a/apps/Development/src/com/android/development/BadBehaviorActivity.java b/apps/Development/src/com/android/development/BadBehaviorActivity.java new file mode 100644 index 000000000..b8f47216a --- /dev/null +++ b/apps/Development/src/com/android/development/BadBehaviorActivity.java @@ -0,0 +1,63 @@ +/* + * 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.android.development; + +import android.app.Activity; +import android.os.Bundle; +import android.widget.Button; +import android.view.View; + +public class BadBehaviorActivity extends Activity { + static class BadBehaviorException extends RuntimeException { + BadBehaviorException() { + super("Whatcha gonna do, whatcha gonna do", + new IllegalStateException("When they come for you")); + } + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.bad_behavior); + + Button crash_main = (Button) findViewById(R.id.bad_behavior_crash_main); + crash_main.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { throw new BadBehaviorException(); } + }); + + Button crash_thread = (Button) findViewById(R.id.bad_behavior_crash_thread); + crash_thread.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + new Thread() { + @Override + public void run() { throw new BadBehaviorException(); } + }.start(); + } + }); + + Button anr = (Button) findViewById(R.id.bad_behavior_anr); + anr.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + try { + Thread.sleep(20000); + } catch (InterruptedException e) { + throw new IllegalStateException(e); + } + } + }); + } +} diff --git a/apps/Development/src/com/android/development/ExceptionBrowser.java b/apps/Development/src/com/android/development/ExceptionBrowser.java deleted file mode 100644 index 63270aab5..000000000 --- a/apps/Development/src/com/android/development/ExceptionBrowser.java +++ /dev/null @@ -1,171 +0,0 @@ -/* -** Copyright 2007, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** 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.development; - -import android.app.ListActivity; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.database.ContentObserver; -import android.database.DataSetObserver; -import android.graphics.Typeface; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.provider.Checkin; -import android.server.data.CrashData; -import android.server.data.ThrowableData; -import android.os.Bundle; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.view.KeyEvent; -import android.widget.*; - -import org.apache.commons.codec.binary.Base64; - -import java.io.ByteArrayInputStream; -import java.io.DataInputStream; -import java.io.IOException; - -/** - * - * - */ -public class ExceptionBrowser extends ListActivity { - /** Logging identifier. */ - private static final String TAG = "ExceptionBrowser"; - - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - - Cursor cursor = getContentResolver().query( - Checkin.Crashes.CONTENT_URI, - new String[] { Checkin.Crashes._ID, Checkin.Crashes.DATA }, - null, null, null); - - if (cursor != null) { - startManagingCursor(cursor); - - setListAdapter(new CursorAdapter(this, cursor, true) { - public View newView(Context context, Cursor c, ViewGroup v) { - return new CrashListItem(context); - } - - public void bindView(View view, Context c, Cursor cursor) { - CrashListItem item = (CrashListItem) view; - try { - String data = cursor.getString(1); - CrashData crash = new CrashData( - new DataInputStream( - new ByteArrayInputStream( - Base64.decodeBase64(data.getBytes())))); - - ThrowableData exc = crash.getThrowableData(); - item.setText(exc.getType() + ": " + exc.getMessage()); - item.setCrashData(crash); - } catch (IOException e) { - item.setText("Invalid crash: " + e); - Log.e(TAG, "Invalid crash", e); - } - } - }); - } else { - // No database, no exceptions, empty list. - setListAdapter(new BaseAdapter() { - public int getCount() { - return 0; - } - - public Object getItem(int position) { - throw new AssertionError(); - } - - public long getItemId(int position) { - throw new AssertionError(); - } - - public View getView(int position, View convertView, - ViewGroup parent) { - throw new AssertionError(); - } - }); - } - } - - private static final int UPLOAD_ID = Menu.FIRST; - private static final int CLEAR_ID = Menu.FIRST + 1; - - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - - menu.add(0, UPLOAD_ID, 0, R.string.menu_upload_exceptions); - menu.add(0, CLEAR_ID, 0, R.string.menu_clear_exceptions); - - return true; - } - - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - // Handle all of the possible menu actions. - switch (item.getItemId()) { - case UPLOAD_ID: - sendBroadcast(new Intent(Checkin.TriggerIntent.ACTION)); - break; - case CLEAR_ID: - getContentResolver().delete( - Checkin.Crashes.CONTENT_URI, null, null); - break; - } - return super.onOptionsItemSelected(item); - } - - static class CrashListItem extends TextView { - CrashData crashData = null; - - public CrashListItem(Context context) { - super(context); - setTextSize(10); - setTypeface(Typeface.MONOSPACE); - } - - public CrashData getCrashData() { - return crashData; - } - - public void setCrashData(CrashData crashData) { - this.crashData = crashData; - } - } - - @Override - protected void onListItemClick(ListView l, View view, int pos, long id) { - // TODO: Use a generic VIEW action on the crash's content URI. - CrashData crash = ((CrashListItem) view).getCrashData(); - if (crash != null) { - Intent intent = new Intent(); - intent.setClass(this, StacktraceViewer.class); - intent.putExtra( - CrashData.class.getName(), - crash.getThrowableData().toString()); - startActivity(intent); - } - } -} diff --git a/apps/Development/src/com/android/development/StacktraceViewer.java b/apps/Development/src/com/android/development/StacktraceViewer.java deleted file mode 100644 index 96b052129..000000000 --- a/apps/Development/src/com/android/development/StacktraceViewer.java +++ /dev/null @@ -1,50 +0,0 @@ -/* -** Copyright 2007, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** 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.development; - - -import android.app.Activity; -import android.widget.EditText; -import android.widget.TextView; -import android.text.method.ScrollingMovementMethod; -import android.os.Bundle; -import android.server.data.CrashData; -import android.server.data.ThrowableData; -import android.server.data.StackTraceElementData; -import android.graphics.Typeface; - -/** - * Views a single stack trace. - */ -public class StacktraceViewer extends Activity { - - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - setContentView(R.layout.log_viewer); - - TextView text = (TextView) findViewById(R.id.text); - text.setTextSize(10); - text.setHorizontallyScrolling(true); - text.setTypeface(Typeface.MONOSPACE); - text.setMovementMethod(ScrollingMovementMethod.getInstance()); - - String stacktrace = getIntent().getExtras().getString( - CrashData.class.getName()); - - text.setText(stacktrace); - } -} diff --git a/build/sdk.atree b/build/sdk.atree index 6696deb51..f03d35484 100644 --- a/build/sdk.atree +++ b/build/sdk.atree @@ -83,6 +83,10 @@ development/samples/Snake platforms/${PLATFORM_NAME}/samples/Snake development/samples/SoftKeyboard platforms/${PLATFORM_NAME}/samples/SoftKeyboard development/samples/JetBoy platforms/${PLATFORM_NAME}/samples/JetBoy development/samples/SearchableDictionary platforms/${PLATFORM_NAME}/samples/SearchableDictionary +development/samples/ContactManager platforms/${PLATFORM_NAME}/samples/ContactManager +development/samples/MultiResolution platforms/${PLATFORM_NAME}/samples/MultiResolution +development/samples/Wiktionary platforms/${PLATFORM_NAME}/samples/Wiktionary +development/samples/WiktionarySimple platforms/${PLATFORM_NAME}/samples/WiktionarySimple # dx bin/dx platforms/${PLATFORM_NAME}/tools/dx diff --git a/cmds/monkey/src/com/android/commands/monkey/Monkey.java b/cmds/monkey/src/com/android/commands/monkey/Monkey.java index 9202230f9..38e488437 100644 --- a/cmds/monkey/src/com/android/commands/monkey/Monkey.java +++ b/cmds/monkey/src/com/android/commands/monkey/Monkey.java @@ -23,11 +23,11 @@ import android.content.ComponentName; import android.content.Intent; import android.content.pm.IPackageManager; import android.content.pm.ResolveInfo; +import android.os.Build; import android.os.Debug; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; -import android.server.data.CrashData; import android.view.IWindowManager; import java.io.BufferedReader; @@ -225,26 +225,18 @@ public class Monkey { return allow; } - public boolean appCrashed(String processName, int pid, String shortMsg, String longMsg, - byte[] crashData) { + public boolean appCrashed(String processName, int pid, + String tag, String shortMsg, String longMsg, + long timeMillis, String stackTrace) { System.err.println("// CRASH: " + processName + " (pid " + pid + ")"); System.err.println("// Short Msg: " + shortMsg); System.err.println("// Long Msg: " + longMsg); - if (crashData != null) { - try { - CrashData cd = new CrashData(new DataInputStream(new ByteArrayInputStream( - crashData))); - System.err.println("// Build Label: " + cd.getBuildData().getFingerprint()); - System.err.println("// Build Changelist: " - + cd.getBuildData().getIncrementalVersion()); - System.err.println("// Build Time: " + cd.getBuildData().getTime()); - System.err.println("// ID: " + cd.getId()); - System.err.println("// Tag: " + cd.getActivity()); - System.err.println(cd.getThrowableData().toString("// ")); - } catch (IOException e) { - System.err.println("// BAD STACK CRAWL"); - } - } + System.err.println("// Build Label: " + Build.FINGERPRINT); + System.err.println("// Build Changelist: " + Build.VERSION.INCREMENTAL); + System.err.println("// Build Time: " + Build.TIME); + System.err.println("// ID: "); // TODO: This was never set -- remove? + System.err.println("// Tag: " + tag); + System.err.println("// " + stackTrace.replace("\n", "\n// ")); if (!mIgnoreCrashes) { synchronized (Monkey.this) { diff --git a/host/windows/usb/android_winusb.inf b/host/windows/usb/android_winusb.inf index f88af7e97..54f7aa2e1 100755 --- a/host/windows/usb/android_winusb.inf +++ b/host/windows/usb/android_winusb.inf @@ -34,11 +34,14 @@ HKR,,Icon,,-1 %CompositeAdbInterface% = USB_Install, USB\VID_0BB4&PID_0C03&MI_01
;
;Moto Sholes
-%SingleBootLoaderInterface% = USB_Install, USB\VID_18D1&PID_D00D
-%SingleAdbInterface% = USB_Install, USB\VID_18D1&PID_0002
-%CompositeAdbInterface% = USB_Install, USB\VID_18D1&PID_0002&MI_01
%SingleAdbInterface% = USB_Install, USB\VID_22B8&PID_41DB
%CompositeAdbInterface% = USB_Install, USB\VID_22B8&PID_41DB&MI_01
+;
+;Google NexusOne
+%SingleAdbInterface% = USB_Install, USB\VID_18D1&PID_0D02 +%CompositeAdbInterface% = USB_Install, USB\VID_18D1&PID_0D02&MI_01
+%SingleAdbInterface% = USB_Install, USB\VID_18D1&PID_4E11 +%CompositeAdbInterface% = USB_Install, USB\VID_18D1&PID_4E12&MI_01 [Google.NTamd64]
; HTC Dream
@@ -49,11 +52,14 @@ HKR,,Icon,,-1 %CompositeAdbInterface% = USB_Install, USB\VID_0BB4&PID_0C03&MI_01
;
;Moto Sholes
-%SingleBootLoaderInterface% = USB_Install, USB\VID_18D1&PID_D00D
-%SingleAdbInterface% = USB_Install, USB\VID_18D1&PID_0002
-%CompositeAdbInterface% = USB_Install, USB\VID_18D1&PID_0002&MI_01
%SingleAdbInterface% = USB_Install, USB\VID_22B8&PID_41DB
%CompositeAdbInterface% = USB_Install, USB\VID_22B8&PID_41DB&MI_01
+;
+;Google NexusOne
+%SingleAdbInterface% = USB_Install, USB\VID_18D1&PID_0D02
+%CompositeAdbInterface% = USB_Install, USB\VID_18D1&PID_0D02&MI_01 +%SingleAdbInterface% = USB_Install, USB\VID_18D1&PID_4E11
+%CompositeAdbInterface% = USB_Install, USB\VID_18D1&PID_4E12&MI_01 [USB_Install]
Include = winusb.inf
diff --git a/ndk/docs/CHANGES.TXT b/ndk/docs/CHANGES.TXT index 145933c0b..c6db521e8 100644 --- a/ndk/docs/CHANGES.TXT +++ b/ndk/docs/CHANGES.TXT @@ -56,6 +56,10 @@ IMPORTANT CHANGES: OpenGL ES 2.0 is currently *not* available from Java, and must be used through native code exclusively. + IMPORTANT: OpenGL ES 2.0 is not supported in the Android emulator at this + time. Running/testing any native code that depends on it thus + requires a real device. + - The NDK build script will now remove installed binaries from the application project's path before starting the build. This ensures that: diff --git a/ndk/docs/STABLE-APIS.TXT b/ndk/docs/STABLE-APIS.TXT index 700bb8843..48e6b5aeb 100644 --- a/ndk/docs/STABLE-APIS.TXT +++ b/ndk/docs/STABLE-APIS.TXT @@ -157,8 +157,7 @@ IV. Android-5 Stable Native APIs: ---------------------------------- All the APIs listed below are available for developing native code that runs -on the Eclair experimental branch, which will be used to make the next official -platform system images. +on Android 2.0 system images and above. The OpenGL ES 2.0 Library: @@ -183,3 +182,8 @@ http://android-developers.blogspot.com/2009/04/introducing-glsurfaceview.html The "hello-gl2" sample application demonstrate this. It is used to draw a very simple triangle with the help of a vertex and fragment shaders. + +IMPORTANT NOTE: + The Android emulator does not support OpenGL ES 2.0 hardware emulation + at this time. Running and testing code that uses this API requires a + real device with such capabilities. diff --git a/samples/ApiDemos/AndroidManifest.xml b/samples/ApiDemos/AndroidManifest.xml index f88ea5b6f..4dbf13683 100644 --- a/samples/ApiDemos/AndroidManifest.xml +++ b/samples/ApiDemos/AndroidManifest.xml @@ -553,6 +553,15 @@ </intent-filter> </activity> + <!-- Text-To-Speech Samples --> + + <activity android:name=".app.TextToSpeechActivity" android:label="@string/text_to_speech"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.SAMPLE_CODE" /> + </intent-filter> + </activity> + <!-- ************************************* --> <!-- CONTENT PACKAGE SAMPLES --> <!-- ************************************* --> @@ -1407,6 +1416,25 @@ </intent-filter> </activity> + <activity android:name=".graphics.GLES20Activity" + android:label="Graphics/OpenGL ES/OpenGL ES 2.0" + android:theme="@android:style/Theme.NoTitleBar" + android:configChanges="orientation|keyboardHidden"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.SAMPLE_CODE" /> + </intent-filter> + </activity> + + <activity android:name=".graphics.MatrixPaletteActivity" + android:label="Graphics/OpenGL ES/Matrix Palette Skinning" + android:configChanges="orientation|keyboardHidden"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.SAMPLE_CODE" /> + </intent-filter> + </activity> + <activity android:name=".graphics.TranslucentGLSurfaceViewActivity" android:label="Graphics/OpenGL ES/Translucent GLSurfaceView" android:theme="@style/Theme.Translucent" diff --git a/samples/ApiDemos/res/layout/list_7.xml b/samples/ApiDemos/res/layout/list_7.xml index 2dc777707..dfaf093e7 100644 --- a/samples/ApiDemos/res/layout/list_7.xml +++ b/samples/ApiDemos/res/layout/list_7.xml @@ -20,7 +20,7 @@ android:layout_height="fill_parent" android:paddingLeft="8dip" android:paddingRight="8dip"> - + <ListView android:id="@android:id/list" android:layout_width="fill_parent" android:layout_height="0dip" @@ -31,5 +31,5 @@ android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="@drawable/blue"/> - + </LinearLayout> diff --git a/samples/ApiDemos/res/layout/text_to_speech.xml b/samples/ApiDemos/res/layout/text_to_speech.xml new file mode 100644 index 000000000..0ba60e1a8 --- /dev/null +++ b/samples/ApiDemos/res/layout/text_to_speech.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + > + <Button android:id="@+id/again_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:enabled="false" + android:text="@string/again" /> +</LinearLayout> diff --git a/samples/ApiDemos/res/values/strings.xml b/samples/ApiDemos/res/values/strings.xml index 6f91b3476..b19504b08 100644 --- a/samples/ApiDemos/res/values/strings.xml +++ b/samples/ApiDemos/res/values/strings.xml @@ -221,6 +221,9 @@ <string name="voice_recognition">App/Voice Recognition</string> + <string name="text_to_speech">App/Text-To-Speech</string> + <string name="again">Again</string> + <!-- ============================== --> <!-- app/content examples strings --> <!-- ============================== --> diff --git a/samples/ApiDemos/src/com/example/android/apis/app/TextToSpeechActivity.java b/samples/ApiDemos/src/com/example/android/apis/app/TextToSpeechActivity.java new file mode 100644 index 000000000..a693e8036 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/app/TextToSpeechActivity.java @@ -0,0 +1,136 @@ + /* + * 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.app; + +import android.app.Activity; +import android.os.Bundle; +import android.speech.tts.TextToSpeech; +import android.util.Log; +import android.view.View; +import android.widget.Button; + +import com.example.android.apis.R; + +import java.util.Locale; +import java.util.Random; + +/** + * <p>Demonstrates text-to-speech (TTS). Please note the following steps:</p> + * + * <ol> + * <li>Construct the TextToSpeech object.</li> + * <li>Handle initialization callback in the onInit method. + * The activity implements TextToSpeech.OnInitListener for this purpose.</li> + * <li>Call TextToSpeech.speak to synthesize speech.</li> + * <li>Shutdown TextToSpeech in onDestroy.</li> + * </ol> + * + * <p>Documentation: + * http://developer.android.com/reference/android/speech/tts/package-summary.html + * </p> + * <ul> + */ +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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.google.example.android.contactmanager" + android:versionCode="1" android:versionName="1.0"> + <application android:label="@string/app_name" android:icon="@drawable/icon"> + <activity android:name=".ContactManager" android:label="@string/app_name"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <activity android:name="ContactAdder" android:label="@string/addContactTitle"> + </activity> + + </application> + <uses-sdk android:minSdkVersion="5" android:targetSdkVersion="5" /> + <uses-permission android:name="android.permission.GET_ACCOUNTS" /> + <uses-permission android:name="android.permission.READ_CONTACTS" /> + <uses-permission android:name="android.permission.WRITE_CONTACTS" /> +</manifest> 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 @@ +<p>A sample application that demonstrates how to manually query the system contacts provider using +the new <code><a +href="../../../reference/android/provider/ContactsContract.html">ContactsContract</a></code> API, as +well as manually insert contacts into a specific account.</p> + +<p>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.</p> + +<img alt="Screenshot 1" src="../images/ContactManager1.png" /> +<img alt="Screenshot 2" src="../images/ContactManager2.png" />
\ No newline at end of file diff --git a/samples/ContactManager/res/drawable-hdpi/icon.png b/samples/ContactManager/res/drawable-hdpi/icon.png Binary files differnew file mode 100644 index 000000000..8074c4c57 --- /dev/null +++ b/samples/ContactManager/res/drawable-hdpi/icon.png diff --git a/samples/ContactManager/res/drawable-ldpi/icon.png b/samples/ContactManager/res/drawable-ldpi/icon.png Binary files differnew file mode 100644 index 000000000..1095584ec --- /dev/null +++ b/samples/ContactManager/res/drawable-ldpi/icon.png diff --git a/samples/ContactManager/res/drawable-mdpi/icon.png b/samples/ContactManager/res/drawable-mdpi/icon.png Binary files differnew file mode 100644 index 000000000..a07c69fa5 --- /dev/null +++ b/samples/ContactManager/res/drawable-mdpi/icon.png 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="?android:attr/listPreferredItemHeight" + android:padding="6dip"> + <ImageView + android:id="@+id/accountIcon" + android:layout_width="wrap_content" + android:layout_height="fill_parent" + android:layout_alignParentTop="true" + android:layout_alignParentBottom="true" + android:layout_marginRight="6dip" /> + <TextView + android:id="@+id/secondAccountLine" + android:layout_width="fill_parent" + android:layout_height="26dip" + android:layout_toRightOf="@id/accountIcon" + android:layout_alignParentBottom="true" + android:layout_alignParentRight="true" + android:singleLine="true" + android:ellipsize="marquee" + android:textColor="@android:color/secondary_text_light" /> + <TextView + android:id="@+id/firstAccountLine" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_toRightOf="@id/accountIcon" + android:layout_alignParentRight="true" + android:layout_alignParentTop="true" + android:layout_above="@id/secondAccountLine" + android:layout_alignWithParentIfMissing="true" + android:gravity="center_vertical" + android:textColor="@android:color/primary_text_light"/> +</RelativeLayout> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent"> + <TableLayout android:layout_width="fill_parent" + android:layout_height="fill_parent"> + <TableRow> + <TextView android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/targetAccountLabel"/> + </TableRow> + <TableRow> + <Spinner android:layout_height="wrap_content" + android:layout_width="fill_parent" + android:layout_weight="1" + android:id="@+id/accountSpinner"/> + </TableRow> + <TableRow> + <TextView android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/contactNameLabel"/> + </TableRow> + <TableRow> + <EditText android:id="@+id/contactNameEditText" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_weight="1"/> + </TableRow> + <TableRow> + <TextView android:text="@string/contactPhoneLabel" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + </TableRow> + <TableRow> + <EditText android:id="@+id/contactPhoneEditText" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_weight="1"/> + <Spinner android:id="@+id/contactPhoneTypeSpinner" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + </TableRow> + <TableRow> + <TextView android:text="@string/contactEmailLabel" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + </TableRow> + <TableRow> + <EditText android:id="@+id/contactEmailEditText" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_weight="1"/> + <Spinner android:id="@+id/contactEmailTypeSpinner" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + </TableRow> + <TableRow> + <Button android:layout_height="wrap_content" + android:text="@string/save" + android:id="@+id/contactSaveButton" + android:layout_width="fill_parent" + android:layout_weight="1"/> + </TableRow> + </TableLayout> +</ScrollView> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + <TextView android:text="@+id/contactEntryText" + android:id="@+id/contactEntryText" + android:layout_width="fill_parent" + android:layout_height="wrap_content"/> +</LinearLayout> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="fill_parent" + android:layout_height="fill_parent"> + <ListView android:layout_width="fill_parent" + android:id="@+id/contactList" + android:layout_height="wrap_content" + android:layout_weight="1"/> + <CheckBox android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/showInvisible" + android:text="@string/showInvisible"/> + <Button android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:id="@+id/addContactButton" + android:text="@string/addContactButtonLabel"/> +</LinearLayout> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<resources> + <string name="accountSpinnerLabel">Account</string> + <string name="addContactButtonLabel">Add Contact</string> + <string name="addContactTitle">Add Contact</string> + <string name="allAccounts">All Accounts</string> + <string name="app_name">Contact Manager</string> + <string name="contactCreationFailure">Contact creation failed, check logs.</string> + <string name="contactEmailLabel">Contact Email</string> + <string name="contactNameLabel">Contact Name</string> + <string name="contactPhoneLabel">Contact Phone</string> + <string name="save">Save</string> + <string name="selectAccountLabel">Select</string> + <string name="selectLabel">Select label</string> + <string name="showInvisible">Show Invisible Contacts (Only)</string> + <string name="targetAccountLabel">Target Account</string> + <string name="undefinedTypeLabel">(Undefined)</string> +</resources> 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<AccountData> mAccounts; + private AccountAdapter mAccountAdapter; + private Spinner mAccountSpinner; + private EditText mContactEmailEditText; + private ArrayList<Integer> mContactEmailTypes; + private Spinner mContactEmailTypeSpinner; + private EditText mContactNameEditText; + private EditText mContactPhoneEditText; + private ArrayList<Integer> 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<Integer>(); + 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<Integer>(); + 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<AccountData>(); + mAccountAdapter = new AccountAdapter(this, mAccounts); + mAccountSpinner.setAdapter(mAccountAdapter); + + // Populate list of account types for phone + ArrayAdapter<String> adapter; + adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + Iterator<Integer> 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<String>(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<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); + 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<AccountData> { + public AccountAdapter(Context context, ArrayList<AccountData> 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: </ul> </p> -<img alt="" src="../images/sample_multires.png"/> +<img alt="" src="../images/MultiResolution.png"/> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.example.android.wiktionary" + android:versionCode="1" + android:versionName="1.0"> + <application android:icon="@drawable/app_icon" android:label="@string/app_name" + android:description="@string/app_descrip"> + + <!-- Browser-like Activity to navigate dictionary definitions --> + <activity + android:name=".LookupActivity" + android:theme="@style/LookupTheme" + android:launchMode="singleTop" + android:configChanges="orientation|keyboardHidden"> + + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + <data android:scheme="wiktionary" android:host="lookup" /> + </intent-filter> + + <intent-filter> + <action android:name="android.intent.action.SEARCH" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + + <meta-data android:name="android.app.searchable" android:resource="@xml/searchable" /> + </activity> + + <!-- Broadcast Receiver that will process AppWidget updates --> + <receiver android:name=".WordWidget" android:label="@string/widget_name"> + <intent-filter> + <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> + </intent-filter> + <meta-data android:name="android.appwidget.provider" + android:resource="@xml/widget_word" /> + </receiver> + + <!-- Service to perform web API queries --> + <service android:name=".WordWidget$UpdateService" /> + + </application> + + <meta-data android:name="android.app.default_searchable" android:value=".LookupActivity" /> + + <uses-permission android:name="android.permission.INTERNET" /> + <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="4" /> + +</manifest> 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 @@ +<p>A sample application that demonstrates how to create an interactive Android +home screen widget that launches an application activity.</p> + +<p>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.</p> + +<img alt="" src="../images/WiktionarySimple.png"/> +<img alt="" src="../images/Wiktionary.png"/> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<set xmlns:android="http://schemas.android.com/apk/res/android" android:fillAfter="true"> + <translate + android:fromXDelta="-26" + android:toXDelta="0" + android:duration="400" /> +</set> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<set xmlns:android="http://schemas.android.com/apk/res/android" android:fillAfter="true"> + <translate + android:fromXDelta="0" + android:toXDelta="-26" + android:duration="400" /> +</set> diff --git a/samples/Wiktionary/res/drawable/app_icon.png b/samples/Wiktionary/res/drawable/app_icon.png Binary files differnew file mode 100644 index 000000000..2b1417aad --- /dev/null +++ b/samples/Wiktionary/res/drawable/app_icon.png diff --git a/samples/Wiktionary/res/drawable/ic_menu_shuffle.png b/samples/Wiktionary/res/drawable/ic_menu_shuffle.png Binary files differnew file mode 100755 index 000000000..cb7009dea --- /dev/null +++ b/samples/Wiktionary/res/drawable/ic_menu_shuffle.png diff --git a/samples/Wiktionary/res/drawable/logo_overlay.9.png b/samples/Wiktionary/res/drawable/logo_overlay.9.png Binary files differnew file mode 100644 index 000000000..851ceb15b --- /dev/null +++ b/samples/Wiktionary/res/drawable/logo_overlay.9.png 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@android:color/white" /> + <item android:drawable="@drawable/logo_overlay" /> +</layer-list> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<rotate xmlns:android="http://schemas.android.com/apk/res/android" + android:pivotX="50%" + android:pivotY="50%" + android:fromDegrees="0" + android:toDegrees="360"> + + <shape + android:shape="ring" + android:innerRadiusRatio="4" + android:thicknessRatio="5.333" + android:useLevel="false"> + + <size + android:width="18dip" + android:height="18dip" /> + + <gradient + android:type="sweep" + android:useLevel="false" + android:startColor="#006688cc" + android:centerColor="#886688cc" + android:endColor="#ff6688cc" + android:centerY="0.50" /> + + </shape> + +</rotate> diff --git a/samples/Wiktionary/res/drawable/star_logo.png b/samples/Wiktionary/res/drawable/star_logo.png Binary files differnew file mode 100644 index 000000000..b32d1756c --- /dev/null +++ b/samples/Wiktionary/res/drawable/star_logo.png 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_window_focused="false" android:drawable="@drawable/widget_bg_normal" /> + <item android:state_pressed="true" android:drawable="@drawable/widget_bg_pressed" /> + <item android:state_focused="true" android:drawable="@drawable/widget_bg_selected" /> + <item android:drawable="@drawable/widget_bg_normal" /> +</selector> diff --git a/samples/Wiktionary/res/drawable/widget_bg_normal.9.png b/samples/Wiktionary/res/drawable/widget_bg_normal.9.png Binary files differnew file mode 100644 index 000000000..314eb8ef9 --- /dev/null +++ b/samples/Wiktionary/res/drawable/widget_bg_normal.9.png diff --git a/samples/Wiktionary/res/drawable/widget_bg_pressed.9.png b/samples/Wiktionary/res/drawable/widget_bg_pressed.9.png Binary files differnew file mode 100644 index 000000000..cc23e787b --- /dev/null +++ b/samples/Wiktionary/res/drawable/widget_bg_pressed.9.png diff --git a/samples/Wiktionary/res/drawable/widget_bg_selected.9.png b/samples/Wiktionary/res/drawable/widget_bg_selected.9.png Binary files differnew file mode 100644 index 000000000..ef0cdc066 --- /dev/null +++ b/samples/Wiktionary/res/drawable/widget_bg_selected.9.png 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical" + android:padding="20dip"> + + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:textSize="16sp" + android:text="@string/app_descrip" + android:textColor="?android:attr/textColorPrimaryInverse" /> + + <TextView + android:id="@+id/about_credits" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingTop="20dip" + android:textSize="16sp" + android:text="@string/app_credits" + android:autoLink="web" + android:textColor="?android:attr/textColorPrimaryInverse" /> + +</LinearLayout> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical"> + + <LinearLayout + android:id="@+id/title_bar" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="center_vertical"> + + <ProgressBar + android:id="@+id/progress" + android:layout_width="18dip" + android:layout_height="18dip" + android:layout_marginLeft="10dip" + android:visibility="invisible" + android:indeterminateOnly="true" + android:indeterminateDrawable="@drawable/progress_spin" + android:indeterminateBehavior="repeat" + android:indeterminateDuration="3500" /> + + <TextView + android:id="@+id/title" + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_weight="1" + android:padding="10dip" + style="@style/LookupTitle" /> + + </LinearLayout> + + <WebView + android:id="@+id/webview" + android:layout_width="fill_parent" + android:layout_height="0dip" + android:layout_weight="1" /> + +</LinearLayout> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/widget" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + style="@style/WidgetBackground"> + + <TextView + android:id="@+id/message" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginTop="12dip" + android:padding="10dip" + android:gravity="center" + android:text="@string/widget_loading" + style="@style/Text.Loading" /> + +</LinearLayout> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/widget" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:focusable="true" + style="@style/WidgetBackground"> + + <ImageView + android:id="@+id/icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:src="@drawable/star_logo" /> + + <TextView + android:id="@+id/word_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="14dip" + android:layout_marginBottom="1dip" + android:includeFontPadding="false" + android:singleLine="true" + android:ellipsize="end" + style="@style/Text.WordTitle" /> + + <TextView + android:id="@+id/word_type" + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_toRightOf="@id/word_title" + android:layout_toLeftOf="@id/icon" + android:layout_alignBaseline="@id/word_title" + android:paddingLeft="4dip" + android:includeFontPadding="false" + android:singleLine="true" + android:ellipsize="end" + style="@style/Text.WordType" /> + + <TextView + android:id="@+id/bullet" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/word_title" + android:paddingRight="4dip" + android:includeFontPadding="false" + android:singleLine="true" + style="@style/BulletPoint" /> + + <TextView + android:id="@+id/definition" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_below="@id/word_title" + android:layout_toRightOf="@id/bullet" + android:paddingRight="5dip" + android:paddingBottom="4dip" + android:includeFontPadding="false" + android:lineSpacingMultiplier="0.9" + android:maxLines="4" + android:fadingEdge="vertical" + style="@style/Text.Definition" /> + +</RelativeLayout> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + + <item + android:id="@+id/lookup_search" + android:title="@string/lookup_search" + android:icon="@android:drawable/ic_menu_search" /> + + <item + android:id="@+id/lookup_random" + android:title="@string/lookup_random" + android:icon="@drawable/ic_menu_shuffle" /> + + <item + android:id="@+id/lookup_about" + android:title="@string/lookup_about" + android:icon="@android:drawable/ic_menu_help" /> + +</menu> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<resources> + <string name="app_name">Wiktionary example</string> + <string name="app_descrip">Example of a fast Wiktionary browser and Word-of-day widget</string> + <string name="app_credits">"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"</string> + + <string name="template_user_agent">"%s/%s (Linux; Android)"</string> + <string name="template_wotd_title">"Wiktionary:Word of the day/%s %s"</string> + <string name="template_define_url">"http://en.wiktionary.org/wiki/%s"</string> + + <string name="widget_name">Wiktionary</string> + + <string name="widget_loading">"Loading word\nof day\u2026"</string> + <string name="widget_error">No word of day found</string> + + <string-array name="month_names"> + <item>January</item> + <item>February</item> + <item>March</item> + <item>April</item> + <item>May</item> + <item>June</item> + <item>July</item> + <item>August</item> + <item>September</item> + <item>October</item> + <item>November</item> + <item>December</item> + </string-array> + + + <string name="search_label">Wiktionary search</string> + <string name="search_hint">Define word</string> + + <string name="lookup_search">Search</string> + <string name="lookup_random">Random</string> + <string name="lookup_about">About</string> + + <string name="empty_result">No entry found for this word, or problem reading data.</string> + +</resources> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<resources> + + <style name="WidgetBackground"> + <item name="android:background">@drawable/widget_bg</item> + </style> + + <style name="BulletPoint"> + <item name="android:textSize">13sp</item> + <item name="android:textColor">@android:color/black</item> + </style> + + <style name="Text" /> + + <style name="Text.Loading"> + <item name="android:textSize">14sp</item> + <item name="android:textColor">@android:color/black</item> + </style> + + <style name="Text.WordTitle"> + <item name="android:textSize">16sp</item> + <item name="android:textStyle">bold</item> + <item name="android:textColor">@android:color/black</item> + </style> + + <style name="Text.WordType"> + <item name="android:textSize">14sp</item> + <item name="android:textStyle">italic</item> + <item name="android:textColor">@android:color/black</item> + </style> + + <style name="Text.Definition"> + <item name="android:textSize">13sp</item> + <item name="android:textColor">@android:color/black</item> + </style> + + + <style name="LookupProgress"> + <item name="android:indeterminateOnly">true</item> + <item name="android:indeterminateDrawable">@drawable/progress_spin</item> + <item name="android:indeterminateBehavior">repeat</item> + <item name="android:indeterminateDuration">3500</item> + </style> + + <style name="LookupTitle"> + <item name="android:textSize">30sp</item> + <item name="android:textStyle">bold</item> + <item name="android:textColor">?android:attr/textColorPrimary</item> + </style> + +</resources> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<resources> + <style name="LookupTheme" parent="@android:style/Theme.Light.NoTitleBar"> + <item name="android:windowBackground">@drawable/lookup_bg</item> + </style> +</resources> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<searchable xmlns:android="http://schemas.android.com/apk/res/android" + android:label="@string/search_label" + android:hint="@string/search_hint" /> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" + android:minWidth="146dip" + android:minHeight="72dip" + android:updatePeriodMillis="86400000" + android:initialLayout="@layout/widget_message" /> 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 = "<style>h2 {font-size:1.2em;font-weight:normal;} " + + "a {color:#6688cc;} ol {padding-left:1.5em;} blockquote {margin-left:0em;} " + + ".interProject, .noprint {display:none;} " + + "li, blockquote {margin-top:0.5em;margin-bottom:0.5em;}</style>"; + + /** + * 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<FormatRule> sFormatRules = new ArrayList<FormatRule>(); + + static { + // Format header blocks and wrap outside content in ordered list + sFormatRules.add(new FormatRule("^=+(.+?)=+", "</ol><h2>$1</h2><ol>", + Pattern.MULTILINE)); + + // Indent quoted blocks, handle ordered and bullet lists + sFormatRules.add(new FormatRule("^#+\\*?:(.+?)$", "<blockquote>$1</blockquote>", + Pattern.MULTILINE)); + sFormatRules.add(new FormatRule("^#+:?\\*(.+?)$", "<ul><li>$1</li></ul>", + Pattern.MULTILINE)); + sFormatRules.add(new FormatRule("^#+(.+?)$", "<li>$1</li>", + Pattern.MULTILINE)); + + // Add internal links + sFormatRules.add(new FormatRule("\\[\\[([^:\\|\\]]+)\\]\\]", + String.format("<a href=\"%s://%s/$1\">$1</a>", WIKI_AUTHORITY, WIKI_LOOKUP_HOST))); + sFormatRules.add(new FormatRule("\\[\\[([^:\\|\\]]+)\\|([^\\]]+)\\]\\]", + String.format("<a href=\"%s://%s/$1\">$2</a>", WIKI_AUTHORITY, WIKI_LOOKUP_HOST))); + + // Add bold and italic formatting + sFormatRules.add(new FormatRule("'''(.+?)'''", "<b>$1</b>")); + sFormatRules.add(new FormatRule("([^'])''([^'].*?[^'])''([^'])", "$1<i>$2</i>$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<String> foundSections = new HashSet<String>(); + 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<String> mHistory = new Stack<String>(); + + 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<String, String, String> { + /** + * 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.example.android.simplewiktionary" + android:versionCode="1" + android:versionName="1.0"> + + <application android:icon="@drawable/app_icon" android:label="@string/app_name"> + + <!-- Broadcast Receiver that will process AppWidget updates --> + <receiver android:name=".WordWidget" android:label="@string/widget_name"> + <intent-filter> + <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> + </intent-filter> + <meta-data android:name="android.appwidget.provider" + android:resource="@xml/widget_word" /> + </receiver> + + <!-- Service to perform web API queries --> + <service android:name=".WordWidget$UpdateService" /> + + </application> + + <uses-permission android:name="android.permission.INTERNET" /> + <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="4" /> + +</manifest> 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 @@ +<p>A sample application that demonstrates how to create an interactive widget for display on the Android home screen.</p> + +<p>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.</p> + +<p>A more advanced version of this sample is available in the Wiktionary directory.</p> + +<img alt="" src="../images/WiktionarySimple.png"/> diff --git a/samples/WiktionarySimple/res/drawable/app_icon.png b/samples/WiktionarySimple/res/drawable/app_icon.png Binary files differnew file mode 100644 index 000000000..2b1417aad --- /dev/null +++ b/samples/WiktionarySimple/res/drawable/app_icon.png diff --git a/samples/WiktionarySimple/res/drawable/star_logo.png b/samples/WiktionarySimple/res/drawable/star_logo.png Binary files differnew file mode 100644 index 000000000..b32d1756c --- /dev/null +++ b/samples/WiktionarySimple/res/drawable/star_logo.png 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- The stateful background drawable for a widget --> + <item android:state_window_focused="false" android:drawable="@drawable/widget_bg_normal" /> + <item android:state_pressed="true" android:drawable="@drawable/widget_bg_pressed" /> + <item android:state_focused="true" android:drawable="@drawable/widget_bg_selected" /> + <item android:drawable="@drawable/widget_bg_normal" /> +</selector> diff --git a/samples/WiktionarySimple/res/drawable/widget_bg_normal.9.png b/samples/WiktionarySimple/res/drawable/widget_bg_normal.9.png Binary files differnew file mode 100644 index 000000000..314eb8ef9 --- /dev/null +++ b/samples/WiktionarySimple/res/drawable/widget_bg_normal.9.png diff --git a/samples/WiktionarySimple/res/drawable/widget_bg_pressed.9.png b/samples/WiktionarySimple/res/drawable/widget_bg_pressed.9.png Binary files differnew file mode 100644 index 000000000..cc23e787b --- /dev/null +++ b/samples/WiktionarySimple/res/drawable/widget_bg_pressed.9.png diff --git a/samples/WiktionarySimple/res/drawable/widget_bg_selected.9.png b/samples/WiktionarySimple/res/drawable/widget_bg_selected.9.png Binary files differnew file mode 100644 index 000000000..ef0cdc066 --- /dev/null +++ b/samples/WiktionarySimple/res/drawable/widget_bg_selected.9.png 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/widget" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + style="@style/WidgetBackground"> + + <TextView + android:id="@+id/message" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginTop="12dip" + android:padding="10dip" + android:gravity="center" + android:text="@string/widget_loading" + style="@style/Text.Loading" /> + +</LinearLayout> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/widget" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:focusable="true" + style="@style/WidgetBackground"> + + <ImageView + android:id="@+id/icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:src="@drawable/star_logo" /> + + <TextView + android:id="@+id/word_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="14dip" + android:layout_marginBottom="1dip" + android:includeFontPadding="false" + android:singleLine="true" + android:ellipsize="end" + style="@style/Text.WordTitle" /> + + <TextView + android:id="@+id/word_type" + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_toRightOf="@id/word_title" + android:layout_toLeftOf="@id/icon" + android:layout_alignBaseline="@id/word_title" + android:paddingLeft="4dip" + android:includeFontPadding="false" + android:singleLine="true" + android:ellipsize="end" + style="@style/Text.WordType" /> + + <TextView + android:id="@+id/bullet" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/word_title" + android:paddingRight="4dip" + android:includeFontPadding="false" + android:singleLine="true" + style="@style/BulletPoint" /> + + <TextView + android:id="@+id/definition" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_below="@id/word_title" + android:layout_toRightOf="@id/bullet" + android:paddingRight="5dip" + android:paddingBottom="4dip" + android:includeFontPadding="false" + android:lineSpacingMultiplier="0.9" + android:maxLines="4" + android:fadingEdge="vertical" + style="@style/Text.Definition" /> + +</RelativeLayout> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<resources> + <string name="app_name">Wiktionary simple example</string> + + <string name="template_user_agent">"%s/%s (Linux; Android)"</string> + <string name="template_wotd_title">"Wiktionary:Word of the day/%s %s"</string> + <string name="template_define_url">"http://en.wiktionary.org/wiki/%s"</string> + + <string name="widget_name">Wiktionary simple</string> + + <string name="widget_loading">Loading word\nof day\u2026</string> + <string name="widget_error">No word of\nday found</string> + + <string-array name="month_names"> + <item>January</item> + <item>February</item> + <item>March</item> + <item>April</item> + <item>May</item> + <item>June</item> + <item>July</item> + <item>August</item> + <item>September</item> + <item>October</item> + <item>November</item> + <item>December</item> + </string-array> + +</resources> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<resources> + + <style name="WidgetBackground"> + <item name="android:background">@drawable/widget_bg</item> + </style> + + <style name="BulletPoint"> + <item name="android:textSize">13sp</item> + <item name="android:textColor">@android:color/black</item> + </style> + + <style name="Text" /> + + <style name="Text.Loading"> + <item name="android:textSize">14sp</item> + <item name="android:textColor">@android:color/black</item> + </style> + + <style name="Text.WordTitle"> + <item name="android:textSize">16sp</item> + <item name="android:textStyle">bold</item> + <item name="android:textColor">@android:color/black</item> + </style> + + <style name="Text.WordType"> + <item name="android:textSize">14sp</item> + <item name="android:textStyle">italic</item> + <item name="android:textColor">@android:color/black</item> + </style> + + <style name="Text.Definition"> + <item name="android:textSize">13sp</item> + <item name="android:textColor">@android:color/black</item> + </style> + +</resources> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" + android:minWidth="146dip" + android:minHeight="72dip" + android:updatePeriodMillis="86400000" + android:initialLayout="@layout/widget_message" /> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.demo.notepad1"> + <application android:icon="@drawable/icon"> + <activity android:name=".Notepadv1" android:label="@string/app_name"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> +</manifest>
\ No newline at end of file diff --git a/tutorials/NotepadCodeLab/Notepadv1/res/drawable/icon.png b/tutorials/NotepadCodeLab/Notepadv1/res/drawable/icon.png Binary files differnew file mode 100755 index 000000000..64e3601c2 --- /dev/null +++ b/tutorials/NotepadCodeLab/Notepadv1/res/drawable/icon.png 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + +</LinearLayout>
\ 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="app_name">Notepad v1</string> + <string name="no_notes">No Notes Yet</string> +</resources> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.demo.notepad1"> + <application android:icon="@drawable/icon"> + <activity android:name=".Notepadv1" android:label="@string/app_name"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> +</manifest>
\ No newline at end of file diff --git a/tutorials/NotepadCodeLab/Notepadv1Solution/res/drawable/icon.png b/tutorials/NotepadCodeLab/Notepadv1Solution/res/drawable/icon.png Binary files differnew file mode 100755 index 000000000..64e3601c2 --- /dev/null +++ b/tutorials/NotepadCodeLab/Notepadv1Solution/res/drawable/icon.png 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <ListView android:id="@id/android:list" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + <TextView android:id="@id/android:empty" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/no_notes"/> + +</LinearLayout>
\ 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<TextView android:id="@+id/text1" + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/>
\ 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="app_name">Notepad v1</string> + <string name="no_notes">No Notes Yet</string> + <string name="menu_insert">Add Item</string> +</resources> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.demo.notepad2"> + <application android:icon="@drawable/icon"> + <activity android:name=".Notepadv2" android:label="@string/app_name"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> +</manifest>
\ No newline at end of file diff --git a/tutorials/NotepadCodeLab/Notepadv2/res/drawable/icon.png b/tutorials/NotepadCodeLab/Notepadv2/res/drawable/icon.png Binary files differnew file mode 100755 index 000000000..64e3601c2 --- /dev/null +++ b/tutorials/NotepadCodeLab/Notepadv2/res/drawable/icon.png 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 @@ +<?xml version="1.0" encoding="utf-8"?> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" android:layout_width="fill_parent" + android:layout_height="fill_parent"> + + <LinearLayout android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content"> + + <TextView android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/title" /> + <EditText android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1"/> + </LinearLayout> + + <TextView android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/body" /> + <EditText android:id="@+id/body" android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:scrollbars="vertical" /> + + <Button android:id="@+id/confirm" + android:text="@string/confirm" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + +</LinearLayout>
\ 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <ListView android:id="@+id/android:list" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + <TextView android:id="@+id/android:empty" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/no_notes"/> +</LinearLayout> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<TextView android:id="@+id/text1" xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="app_name">Notepad v2</string> + <string name="no_notes">No Notes Yet</string> + <string name="menu_insert">Add Note</string> + <string name="menu_delete">Delete Note</string> + <string name="title">Title</string> + <string name="body">Body</string> + <string name="confirm">Confirm</string> + <string name="edit_note">Edit Note</string> +</resources> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.demo.notepad2"> + <application android:icon="@drawable/icon"> + <activity android:name=".Notepadv2" android:label="@string/app_name"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <activity android:name=".NoteEdit"></activity> + </application> +</manifest>
\ No newline at end of file diff --git a/tutorials/NotepadCodeLab/Notepadv2Solution/res/drawable/icon.png b/tutorials/NotepadCodeLab/Notepadv2Solution/res/drawable/icon.png Binary files differnew file mode 100755 index 000000000..64e3601c2 --- /dev/null +++ b/tutorials/NotepadCodeLab/Notepadv2Solution/res/drawable/icon.png 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 @@ +<?xml version="1.0" encoding="utf-8"?> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" android:layout_width="fill_parent" + android:layout_height="fill_parent"> + + <LinearLayout android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content"> + + <TextView android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/title" /> + <EditText android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1"/> + </LinearLayout> + + <TextView android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/body" /> + <EditText android:id="@+id/body" android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:scrollbars="vertical" /> + + <Button android:id="@+id/confirm" + android:text="@string/confirm" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + +</LinearLayout>
\ 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <ListView android:id="@+id/android:list" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + <TextView android:id="@+id/android:empty" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/no_notes"/> +</LinearLayout> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<TextView android:id="@+id/text1" xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="app_name">Notepad v2</string> + <string name="no_notes">No Notes Yet</string> + <string name="menu_insert">Add Note</string> + <string name="menu_delete">Delete Note</string> + <string name="title">Title</string> + <string name="body">Body</string> + <string name="confirm">Confirm</string> + <string name="edit_note">Edit Note</string> +</resources> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.demo.notepad3"> + <application android:icon="@drawable/icon"> + <activity android:name=".Notepadv3" android:label="@string/app_name"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <activity android:name=".NoteEdit"/> + </application> +</manifest>
\ No newline at end of file diff --git a/tutorials/NotepadCodeLab/Notepadv3/res/drawable/icon.png b/tutorials/NotepadCodeLab/Notepadv3/res/drawable/icon.png Binary files differnew file mode 100755 index 000000000..64e3601c2 --- /dev/null +++ b/tutorials/NotepadCodeLab/Notepadv3/res/drawable/icon.png 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 @@ +<?xml version="1.0" encoding="utf-8"?> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" android:layout_width="fill_parent" + android:layout_height="fill_parent"> + + <LinearLayout android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content"> + + <TextView android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/title" /> + <EditText android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1"/> + </LinearLayout> + + <TextView android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/body" /> + <EditText android:id="@+id/body" android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:scrollbars="vertical" /> + + <Button android:id="@+id/confirm" + android:text="@string/confirm" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + +</LinearLayout>
\ 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <ListView android:id="@+id/android:list" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + <TextView android:id="@+id/android:empty" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/no_notes"/> +</LinearLayout> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<TextView android:id="@+id/text1" xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="app_name">Notepad v3</string> + <string name="no_notes">No Notes Yet</string> + <string name="menu_insert">Add Note</string> + <string name="menu_delete">Delete Note</string> + <string name="title">Title</string> + <string name="body">Body</string> + <string name="confirm">Confirm</string> + <string name="edit_note">Edit Note</string> +</resources> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.demo.notepad3"> + <application android:icon="@drawable/icon"> + <activity android:name=".Notepadv3" android:label="@string/app_name"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <activity android:name=".NoteEdit"/> + </application> +</manifest>
\ No newline at end of file diff --git a/tutorials/NotepadCodeLab/Notepadv3Solution/res/drawable/icon.png b/tutorials/NotepadCodeLab/Notepadv3Solution/res/drawable/icon.png Binary files differnew file mode 100755 index 000000000..64e3601c2 --- /dev/null +++ b/tutorials/NotepadCodeLab/Notepadv3Solution/res/drawable/icon.png 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 @@ +<?xml version="1.0" encoding="utf-8"?> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" android:layout_width="fill_parent" + android:layout_height="fill_parent"> + + <LinearLayout android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content"> + + <TextView android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/title" /> + <EditText android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1"/> + </LinearLayout> + + <TextView android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/body" /> + <EditText android:id="@+id/body" android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:scrollbars="vertical" /> + + <Button android:id="@+id/confirm" + android:text="@string/confirm" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + +</LinearLayout>
\ 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <ListView android:id="@+id/android:list" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + <TextView android:id="@+id/android:empty" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/no_notes"/> +</LinearLayout> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<TextView android:id="@+id/text1" xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="app_name">Notepad v3</string> + <string name="no_notes">No Notes Yet</string> + <string name="menu_insert">Add Note</string> + <string name="menu_delete">Delete Note</string> + <string name="title">Title</string> + <string name="body">Body</string> + <string name="confirm">Confirm</string> + <string name="edit_note">Edit Note</string> +</resources> 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; + } +} |
