diff options
106 files changed, 4686 insertions, 1474 deletions
diff --git a/Android.mk b/Android.mk index 83c4b5b1fe4e..42cb097c2c95 100644 --- a/Android.mk +++ b/Android.mk @@ -449,8 +449,6 @@ web_docs_sample_code_flags := \ resources/samples/training/ads-and-ux "Mobile Advertisement Integration" \ -samplecode $(sample_dir)/MultiResolution \ resources/samples/MultiResolution "Multiple Resolutions" \ - -samplecode $(sample_dir)/NFCDemo \ - resources/samples/NFCDemo "NFC Demo" \ -samplecode $(sample_dir)/training/multiscreen/newsreader \ resources/samples/newsreader "News Reader" \ -samplecode $(sample_dir)/NotePad \ diff --git a/api/current.txt b/api/current.txt index 85f363399eae..4a8cd3c3c391 100644 --- a/api/current.txt +++ b/api/current.txt @@ -436,7 +436,7 @@ package android { field public static final int fadeEnabled = 16843390; // 0x101027e field public static final int fadeOffset = 16843383; // 0x1010277 field public static final int fadeScrollbars = 16843434; // 0x10102aa - field public static final deprecated int fadingEdge = 16842975; // 0x10100df + field public static final int fadingEdge = 16842975; // 0x10100df field public static final int fadingEdgeLength = 16842976; // 0x10100e0 field public static final int fastScrollAlwaysVisible = 16843573; // 0x1010335 field public static final int fastScrollEnabled = 16843302; // 0x1010226 @@ -5351,6 +5351,7 @@ package android.content { method public static android.content.Intent makeMainActivity(android.content.ComponentName); method public static android.content.Intent makeMainSelectorActivity(java.lang.String, java.lang.String); method public static android.content.Intent makeRestartActivityTask(android.content.ComponentName); + method public static java.lang.String normalizeMimeType(java.lang.String); method public static android.content.Intent parseIntent(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public static android.content.Intent parseUri(java.lang.String, int) throws java.net.URISyntaxException; method public android.content.Intent putCharSequenceArrayListExtra(java.lang.String, java.util.ArrayList<java.lang.CharSequence>); @@ -5399,13 +5400,16 @@ package android.content { method public android.content.Intent setClassName(java.lang.String, java.lang.String); method public android.content.Intent setComponent(android.content.ComponentName); method public android.content.Intent setData(android.net.Uri); + method public android.content.Intent setDataAndNormalize(android.net.Uri); method public android.content.Intent setDataAndType(android.net.Uri, java.lang.String); + method public android.content.Intent setDataAndTypeAndNormalize(android.net.Uri, java.lang.String); method public void setExtrasClassLoader(java.lang.ClassLoader); method public android.content.Intent setFlags(int); method public android.content.Intent setPackage(java.lang.String); method public void setSelector(android.content.Intent); method public void setSourceBounds(android.graphics.Rect); method public android.content.Intent setType(java.lang.String); + method public android.content.Intent setTypeAndNormalize(java.lang.String); method public deprecated java.lang.String toURI(); method public java.lang.String toUri(int); method public void writeToParcel(android.os.Parcel, int); @@ -6822,7 +6826,7 @@ package android.database { public abstract interface Cursor { method public abstract void close(); method public abstract void copyStringToBuffer(int, android.database.CharArrayBuffer); - method public abstract void deactivate(); + method public abstract deprecated void deactivate(); method public abstract byte[] getBlob(int); method public abstract int getColumnCount(); method public abstract int getColumnIndex(java.lang.String); @@ -11834,6 +11838,7 @@ package android.net { method public abstract boolean isHierarchical(); method public boolean isOpaque(); method public abstract boolean isRelative(); + method public android.net.Uri normalize(); method public static android.net.Uri parse(java.lang.String); method public abstract java.lang.String toString(); method public static android.net.Uri withAppendedPath(android.net.Uri, java.lang.String); @@ -12636,6 +12641,8 @@ package android.nfc { method public short getTnf(); method public byte[] getType(); method public deprecated byte[] toByteArray(); + method public java.lang.String toMimeType(); + method public android.net.Uri toUri(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; field public static final byte[] RTD_ALTERNATIVE_CARRIER; @@ -14427,6 +14434,7 @@ package android.os { field public static final int HONEYCOMB_MR2 = 13; // 0xd field public static final int ICE_CREAM_SANDWICH = 14; // 0xe field public static final int ICE_CREAM_SANDWICH_MR1 = 15; // 0xf + field public static final int JELLY_BEAN = 10000; // 0x2710 } public final class Bundle implements java.lang.Cloneable android.os.Parcelable { @@ -21391,6 +21399,7 @@ package android.util { field public static final int DENSITY_MEDIUM = 160; // 0xa0 field public static final int DENSITY_TV = 213; // 0xd5 field public static final int DENSITY_XHIGH = 320; // 0x140 + field public static final int DENSITY_XXHIGH = 480; // 0x1e0 field public float density; field public int densityDpi; field public int heightPixels; diff --git a/cmds/stagefright/record.cpp b/cmds/stagefright/record.cpp index 613435df15e8..770305860895 100644 --- a/cmds/stagefright/record.cpp +++ b/cmds/stagefright/record.cpp @@ -38,7 +38,7 @@ static const int32_t kVideoBitRate = 512 * 1024; static const int32_t kAudioBitRate = 12200; static const int64_t kDurationUs = 10000000LL; // 10 seconds -#if 1 +#if 0 class DummySource : public MediaSource { public: @@ -318,7 +318,7 @@ int main(int argc, char **argv) { sp<MetaData> encMeta = new MetaData; encMeta->setCString(kKeyMIMEType, - 1 ? MEDIA_MIMETYPE_AUDIO_AMR_WB : MEDIA_MIMETYPE_AUDIO_AAC); + 0 ? MEDIA_MIMETYPE_AUDIO_AMR_WB : MEDIA_MIMETYPE_AUDIO_AAC); encMeta->setInt32(kKeySampleRate, kSampleRate); encMeta->setInt32(kKeyChannelCount, kNumChannels); encMeta->setInt32(kKeyMaxInputSize, 8192); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 4fe9cef2b7c2..9661b9e0d32b 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -1442,9 +1442,10 @@ public class ActivityManager { public int getLauncherLargeIconDensity() { final Resources res = mContext.getResources(); final int density = res.getDisplayMetrics().densityDpi; + final int sw = res.getConfiguration().smallestScreenWidthDp; - if ((res.getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) - != Configuration.SCREENLAYOUT_SIZE_XLARGE) { + if (sw < 600) { + // Smaller than approx 7" tablets, use the regular icon size. return density; } @@ -1456,9 +1457,13 @@ public class ActivityManager { case DisplayMetrics.DENSITY_HIGH: return DisplayMetrics.DENSITY_XHIGH; case DisplayMetrics.DENSITY_XHIGH: - return DisplayMetrics.DENSITY_MEDIUM * 2; + return DisplayMetrics.DENSITY_XXHIGH; + case DisplayMetrics.DENSITY_XXHIGH: + return DisplayMetrics.DENSITY_XHIGH * 2; default: - return density; + // The density is some abnormal value. Return some other + // abnormal value that is a reasonable scaling of it. + return (int)(density*1.5f); } } @@ -1471,9 +1476,10 @@ public class ActivityManager { public int getLauncherLargeIconSize() { final Resources res = mContext.getResources(); final int size = res.getDimensionPixelSize(android.R.dimen.app_icon_size); + final int sw = res.getConfiguration().smallestScreenWidthDp; - if ((res.getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) - != Configuration.SCREENLAYOUT_SIZE_XLARGE) { + if (sw < 600) { + // Smaller than approx 7" tablets, use the regular icon size. return size; } @@ -1487,9 +1493,13 @@ public class ActivityManager { case DisplayMetrics.DENSITY_HIGH: return (size * DisplayMetrics.DENSITY_XHIGH) / DisplayMetrics.DENSITY_HIGH; case DisplayMetrics.DENSITY_XHIGH: - return (size * DisplayMetrics.DENSITY_MEDIUM * 2) / DisplayMetrics.DENSITY_XHIGH; + return (size * DisplayMetrics.DENSITY_XXHIGH) / DisplayMetrics.DENSITY_XHIGH; + case DisplayMetrics.DENSITY_XXHIGH: + return (size * DisplayMetrics.DENSITY_XHIGH*2) / DisplayMetrics.DENSITY_XXHIGH; default: - return size; + // The density is some abnormal value. Return some other + // abnormal value that is a reasonable scaling of it. + return (int)(size*1.5f); } } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 455d2f0d3db7..9807b89a2b10 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -3741,7 +3741,6 @@ public final class ActivityThread { } final void handleTrimMemory(int level) { - WindowManagerImpl.getDefault().trimMemory(level); ArrayList<ComponentCallbacks2> callbacks; synchronized (mPackages) { @@ -3752,6 +3751,7 @@ public final class ActivityThread { for (int i=0; i<N; i++) { callbacks.get(i).onTrimMemory(level); } + WindowManagerImpl.getDefault().trimMemory(level); } private void setupGraphicsSupport(LoadedApk info) { @@ -3804,7 +3804,7 @@ public final class ActivityThread { // implementation to use the pool executor. Normally, we use the // serialized executor as the default. This has to happen in the // main thread so the main looper is set right. - if (data.appInfo.targetSdkVersion <= 12) { + if (data.appInfo.targetSdkVersion <= android.os.Build.VERSION_CODES.HONEYCOMB_MR1) { AsyncTask.setDefaultExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java index 8fa95b461ba4..d04e9db37a4a 100644 --- a/core/java/android/app/SearchDialog.java +++ b/core/java/android/app/SearchDialog.java @@ -551,7 +551,6 @@ public class SearchDialog extends Dialog { try { // If the intent was created from a suggestion, it will always have an explicit // component here. - Log.i(LOG_TAG, "Starting (as ourselves) " + intent.toUri(0)); getContext().startActivity(intent); // If the search switches to a different activity, // SearchDialogWrapper#performActivityResuming diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index e3b1f541d211..fbc1b2b7eca1 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -43,6 +43,7 @@ import java.net.URISyntaxException; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; +import java.util.Locale; import java.util.Set; /** @@ -4420,22 +4421,24 @@ public class Intent implements Parcelable, Cloneable { /** * Set the data this intent is operating on. This method automatically - * clears any type that was previously set by {@link #setType}. + * clears any type that was previously set by {@link #setType} or + * {@link #setTypeAndNormalize}. * - * <p><em>Note: scheme and host name matching in the Android framework is - * case-sensitive, unlike the formal RFC. As a result, - * you should always ensure that you write your Uri with these elements - * using lower case letters, and normalize any Uris you receive from - * outside of Android to ensure the scheme and host is lower case.</em></p> + * <p><em>Note: scheme matching in the Android framework is + * case-sensitive, unlike the formal RFC. As a result, + * you should always write your Uri with a lower case scheme, + * or use {@link Uri#normalize} or + * {@link #setDataAndNormalize} + * to ensure that the scheme is converted to lower case.</em> * - * @param data The URI of the data this intent is now targeting. + * @param data The Uri of the data this intent is now targeting. * * @return Returns the same Intent object, for chaining multiple calls * into a single statement. * * @see #getData - * @see #setType - * @see #setDataAndType + * @see #setDataAndNormalize + * @see android.net.Intent#normalize */ public Intent setData(Uri data) { mData = data; @@ -4444,16 +4447,45 @@ public class Intent implements Parcelable, Cloneable { } /** - * Set an explicit MIME data type. This is used to create intents that - * only specify a type and not data, for example to indicate the type of - * data to return. This method automatically clears any data that was - * previously set by {@link #setData}. + * Normalize and set the data this intent is operating on. + * + * <p>This method automatically clears any type that was + * previously set (for example, by {@link #setType}). + * + * <p>The data Uri is normalized using + * {@link android.net.Uri#normalize} before it is set, + * so really this is just a convenience method for + * <pre> + * setData(data.normalize()) + * </pre> + * + * @param data The Uri of the data this intent is now targeting. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #getData + * @see #setType + * @see android.net.Uri#normalize + */ + public Intent setDataAndNormalize(Uri data) { + return setData(data.normalize()); + } + + /** + * Set an explicit MIME data type. + * + * <p>This is used to create intents that only specify a type and not data, + * for example to indicate the type of data to return. + * + * <p>This method automatically clears any data that was + * previously set (for example by {@link #setData}). * * <p><em>Note: MIME type matching in the Android framework is * case-sensitive, unlike formal RFC MIME types. As a result, * you should always write your MIME types with lower case letters, - * and any MIME types you receive from outside of Android should be - * converted to lower case before supplying them here.</em></p> + * or use {@link #normalizeMimeType} or {@link #setTypeAndNormalize} + * to ensure that it is converted to lower case.</em> * * @param type The MIME type of the data being handled by this intent. * @@ -4461,8 +4493,9 @@ public class Intent implements Parcelable, Cloneable { * into a single statement. * * @see #getType - * @see #setData + * @see #setTypeAndNormalize * @see #setDataAndType + * @see #normalizeMimeType */ public Intent setType(String type) { mData = null; @@ -4471,26 +4504,58 @@ public class Intent implements Parcelable, Cloneable { } /** + * Normalize and set an explicit MIME data type. + * + * <p>This is used to create intents that only specify a type and not data, + * for example to indicate the type of data to return. + * + * <p>This method automatically clears any data that was + * previously set (for example by {@link #setData}). + * + * <p>The MIME type is normalized using + * {@link #normalizeMimeType} before it is set, + * so really this is just a convenience method for + * <pre> + * setType(Intent.normalizeMimeType(type)) + * </pre> + * + * @param type The MIME type of the data being handled by this intent. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #getType + * @see #setData + * @see #normalizeMimeType + */ + public Intent setTypeAndNormalize(String type) { + return setType(normalizeMimeType(type)); + } + + /** * (Usually optional) Set the data for the intent along with an explicit * MIME data type. This method should very rarely be used -- it allows you * to override the MIME type that would ordinarily be inferred from the * data with your own type given here. * - * <p><em>Note: MIME type, Uri scheme, and host name matching in the + * <p><em>Note: MIME type and Uri scheme matching in the * Android framework is case-sensitive, unlike the formal RFC definitions. * As a result, you should always write these elements with lower case letters, - * and normalize any MIME types or Uris you receive from - * outside of Android to ensure these elements are lower case before - * supplying them here.</em></p> + * or use {@link #normalizeMimeType} or {@link android.net.Uri#normalize} or + * {@link #setDataAndTypeAndNormalize} + * to ensure that they are converted to lower case.</em> * - * @param data The URI of the data this intent is now targeting. + * @param data The Uri of the data this intent is now targeting. * @param type The MIME type of the data being handled by this intent. * * @return Returns the same Intent object, for chaining multiple calls * into a single statement. * - * @see #setData * @see #setType + * @see #setData + * @see #normalizeMimeType + * @see android.net.Uri#normalize + * @see #setDataAndTypeAndNormalize */ public Intent setDataAndType(Uri data, String type) { mData = data; @@ -4499,6 +4564,35 @@ public class Intent implements Parcelable, Cloneable { } /** + * (Usually optional) Normalize and set both the data Uri and an explicit + * MIME data type. This method should very rarely be used -- it allows you + * to override the MIME type that would ordinarily be inferred from the + * data with your own type given here. + * + * <p>The data Uri and the MIME type are normalize using + * {@link android.net.Uri#normalize} and {@link #normalizeMimeType} + * before they are set, so really this is just a convenience method for + * <pre> + * setDataAndType(data.normalize(), Intent.normalizeMimeType(type)) + * </pre> + * + * @param data The Uri of the data this intent is now targeting. + * @param type The MIME type of the data being handled by this intent. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #setType + * @see #setData + * @see #setDataAndType + * @see #normalizeMimeType + * @see android.net.Uri#normalize + */ + public Intent setDataAndTypeAndNormalize(Uri data, String type) { + return setDataAndType(data.normalize(), normalizeMimeType(type)); + } + + /** * Add a new category to the intent. Categories provide additional detail * about the action the intent is perform. When resolving an intent, only * activities that provide <em>all</em> of the requested categories will be @@ -5566,7 +5660,7 @@ public class Intent implements Parcelable, Cloneable { * * <ul> * <li> action, as set by {@link #setAction}. - * <li> data URI and MIME type, as set by {@link #setData(Uri)}, + * <li> data Uri and MIME type, as set by {@link #setData(Uri)}, * {@link #setType(String)}, or {@link #setDataAndType(Uri, String)}. * <li> categories, as set by {@link #addCategory}. * <li> package, as set by {@link #setPackage}. @@ -6229,4 +6323,38 @@ public class Intent implements Parcelable, Cloneable { return intent; } + + /** + * Normalize a MIME data type. + * + * <p>A normalized MIME type has white-space trimmed, + * content-type parameters removed, and is lower-case. + * This aligns the type with Android best practices for + * intent filtering. + * + * <p>For example, "text/plain; charset=utf-8" becomes "text/plain". + * "text/x-vCard" becomes "text/x-vcard". + * + * <p>All MIME types received from outside Android (such as user input, + * or external sources like Bluetooth, NFC, or the Internet) should + * be normalized before they are used to create an Intent. + * + * @param type MIME data type to normalize + * @return normalized MIME data type, or null if the input was null + * @see {@link #setType} + * @see {@link #setTypeAndNormalize} + */ + public static String normalizeMimeType(String type) { + if (type == null) { + return null; + } + + type = type.trim().toLowerCase(Locale.US); + + final int semicolonIndex = type.indexOf(';'); + if (semicolonIndex != -1) { + type = type.substring(0, semicolonIndex); + } + return type; + } } diff --git a/core/java/android/database/Cursor.java b/core/java/android/database/Cursor.java index a9a71cfb5a4b..59ec89d5382d 100644 --- a/core/java/android/database/Cursor.java +++ b/core/java/android/database/Cursor.java @@ -341,6 +341,7 @@ public interface Cursor { * Deactivates the Cursor, making all calls on it fail until {@link #requery} is called. * Inactive Cursors use fewer resources than active Cursors. * Calling {@link #requery} will make the cursor active again. + * @deprecated Since {@link #requery()} is deprecated, so too is this. */ void deactivate(); diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java index 946300f37537..82bb23ecb159 100644 --- a/core/java/android/database/sqlite/SQLiteCursor.java +++ b/core/java/android/database/sqlite/SQLiteCursor.java @@ -65,8 +65,7 @@ public class SQLiteCursor extends AbstractWindowedCursor { * interface. For a query such as: {@code SELECT name, birth, phone FROM * myTable WHERE ... LIMIT 1,20 ORDER BY...} the column names (name, birth, * phone) would be in the projection argument and everything from - * {@code FROM} onward would be in the params argument. This constructor - * has package scope. + * {@code FROM} onward would be in the params argument. * * @param db a reference to a Database object that is already constructed * and opened. This param is not used any longer @@ -86,8 +85,7 @@ public class SQLiteCursor extends AbstractWindowedCursor { * interface. For a query such as: {@code SELECT name, birth, phone FROM * myTable WHERE ... LIMIT 1,20 ORDER BY...} the column names (name, birth, * phone) would be in the projection argument and everything from - * {@code FROM} onward would be in the params argument. This constructor - * has package scope. + * {@code FROM} onward would be in the params argument. * * @param editTable the name of the table used for this query * @param query the {@link SQLiteQuery} object associated with this cursor object. diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index 0fb49bcad14c..defe7aa0abcc 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -28,6 +28,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; +import java.util.Locale; import java.util.RandomAccess; import java.util.Set; import libcore.net.UriCodec; @@ -1716,6 +1717,38 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { return (!"false".equals(flag) && !"0".equals(flag)); } + /** + * Return a normalized representation of this Uri. + * + * <p>A normalized Uri has a lowercase scheme component. + * This aligns the Uri with Android best practices for + * intent filtering. + * + * <p>For example, "HTTP://www.android.com" becomes + * "http://www.android.com" + * + * <p>All URIs received from outside Android (such as user input, + * or external sources like Bluetooth, NFC, or the Internet) should + * be normalized before they are used to create an Intent. + * + * <p class="note">This method does <em>not</em> validate bad URI's, + * or 'fix' poorly formatted URI's - so do not use it for input validation. + * A Uri will always be returned, even if the Uri is badly formatted to + * begin with and a scheme component cannot be found. + * + * @return normalized Uri (never null) + * @see {@link android.content.Intent#setData} + * @see {@link #setNormalizedData} + */ + public Uri normalize() { + String scheme = getScheme(); + if (scheme == null) return this; // give up + String lowerScheme = scheme.toLowerCase(Locale.US); + if (scheme.equals(lowerScheme)) return this; // no change + + return buildUpon().scheme(lowerScheme).build(); + } + /** Identifies a null parcelled Uri. */ private static final int NULL_TYPE_ID = 0; diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl index 0b93ad03fe5f..d2afbb9905f0 100644 --- a/core/java/android/nfc/INfcAdapter.aidl +++ b/core/java/android/nfc/INfcAdapter.aidl @@ -17,7 +17,6 @@ package android.nfc; import android.app.PendingIntent; -import android.content.ComponentName; import android.content.IntentFilter; import android.nfc.NdefMessage; import android.nfc.Tag; @@ -44,4 +43,6 @@ interface INfcAdapter void setForegroundDispatch(in PendingIntent intent, in IntentFilter[] filters, in TechListParcel techLists); void setForegroundNdefPush(in NdefMessage msg, in INdefPushCallback callback); + + void dispatch(in Tag tag, in NdefMessage message); } diff --git a/core/java/android/nfc/NdefMessage.java b/core/java/android/nfc/NdefMessage.java index 38bc16d6c223..c83144f7e2a8 100644 --- a/core/java/android/nfc/NdefMessage.java +++ b/core/java/android/nfc/NdefMessage.java @@ -92,9 +92,7 @@ public final class NdefMessage implements Parcelable { * @throws FormatException if the data cannot be parsed */ public NdefMessage(byte[] data) throws FormatException { - if (data == null) { - throw new NullPointerException("null data"); - } + if (data == null) throw new NullPointerException("data is null"); ByteBuffer buffer = ByteBuffer.wrap(data); mRecords = NdefRecord.parse(buffer, false); @@ -112,9 +110,8 @@ public final class NdefMessage implements Parcelable { */ public NdefMessage(NdefRecord record, NdefRecord ... records) { // validate - if (record == null) { - throw new NullPointerException("record cannot be null"); - } + if (record == null) throw new NullPointerException("record cannot be null"); + for (NdefRecord r : records) { if (r == null) { throw new NullPointerException("record cannot be null"); @@ -147,7 +144,12 @@ public final class NdefMessage implements Parcelable { /** * Get the NDEF Records inside this NDEF Message.<p> - * An NDEF Message always has one or more NDEF Records. + * An {@link NdefMessage} always has one or more NDEF Records: so the + * following code to retrieve the first record is always safe + * (no need to check for null or array length >= 1): + * <pre> + * NdefRecord firstRecord = ndefMessage.getRecords()[0]; + * </pre> * * @return array of one or more NDEF records. */ diff --git a/core/java/android/nfc/NdefRecord.java b/core/java/android/nfc/NdefRecord.java index b4c488b7c0f8..0e9e8f4bfcf0 100644 --- a/core/java/android/nfc/NdefRecord.java +++ b/core/java/android/nfc/NdefRecord.java @@ -16,6 +16,7 @@ package android.nfc; +import android.content.Intent; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; @@ -25,6 +26,7 @@ import java.nio.charset.Charsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Locale; /** * Represents an immutable NDEF Record. @@ -305,9 +307,9 @@ public final class NdefRecord implements Parcelable { * @return Android application NDEF record */ public static NdefRecord createApplicationRecord(String packageName) { - if (packageName.length() == 0) { - throw new IllegalArgumentException("empty package name"); - } + if (packageName == null) throw new NullPointerException("packageName is null"); + if (packageName.length() == 0) throw new IllegalArgumentException("packageName is empty"); + return new NdefRecord(TNF_EXTERNAL_TYPE, RTD_ANDROID_APP, null, packageName.getBytes(Charsets.UTF_8)); } @@ -318,32 +320,27 @@ public final class NdefRecord implements Parcelable { * Uses the well known URI type representation: {@link #TNF_WELL_KNOWN} * and {@link #RTD_URI}. This is the most efficient encoding * of a URI into NDEF.<p> + * The uri parameter will be normalized with + * {@link Uri#normalize} to set the scheme to lower case to + * follow Android best practices for intent filtering. + * However the unchecked exception + * {@link IllegalArgumentException} may be thrown if the uri + * parameter has serious problems, for example if it is empty, so always + * catch this exception if you are passing user-generated data into this + * method.<p> + * * Reference specification: NFCForum-TS-RTD_URI_1.0 * * @param uri URI to encode. * @return an NDEF Record containing the URI - * @throws IllegalArugmentException if a valid record cannot be created + * @throws IllegalArugmentException if the uri is empty or invalid */ public static NdefRecord createUri(Uri uri) { - return createUri(uri.toString()); - } + if (uri == null) throw new NullPointerException("uri is null"); - /** - * Create a new NDEF Record containing a URI.<p> - * Use this method to encode a URI (or URL) into an NDEF Record.<p> - * Uses the well known URI type representation: {@link #TNF_WELL_KNOWN} - * and {@link #RTD_URI}. This is the most efficient encoding - * of a URI into NDEF.<p> - * Reference specification: NFCForum-TS-RTD_URI_1.0 - * - * @param uriString string URI to encode. - * @return an NDEF Record containing the URI - * @throws IllegalArugmentException if a valid record cannot be created - */ - public static NdefRecord createUri(String uriString) { - if (uriString.length() == 0) { - throw new IllegalArgumentException("empty uriString"); - } + uri = uri.normalize(); + String uriString = uri.toString(); + if (uriString.length() == 0) throw new IllegalArgumentException("uri is empty"); byte prefix = 0; for (int i = 1; i < URI_PREFIX_MAP.length; i++) { @@ -361,28 +358,72 @@ public final class NdefRecord implements Parcelable { } /** + * Create a new NDEF Record containing a URI.<p> + * Use this method to encode a URI (or URL) into an NDEF Record.<p> + * Uses the well known URI type representation: {@link #TNF_WELL_KNOWN} + * and {@link #RTD_URI}. This is the most efficient encoding + * of a URI into NDEF.<p> + * The uriString parameter will be normalized with + * {@link Uri#normalize} to set the scheme to lower case to + * follow Android best practices for intent filtering. + * However the unchecked exception + * {@link IllegalArgumentException} may be thrown if the uriString + * parameter has serious problems, for example if it is empty, so always + * catch this exception if you are passing user-generated data into this + * method.<p> + * + * Reference specification: NFCForum-TS-RTD_URI_1.0 + * + * @param uriString string URI to encode. + * @return an NDEF Record containing the URI + * @throws IllegalArugmentException if the uriString is empty or invalid + */ + public static NdefRecord createUri(String uriString) { + return createUri(Uri.parse(uriString)); + } + + /** * Create a new NDEF Record containing MIME data.<p> * Use this method to encode MIME-typed data into an NDEF Record, * such as "text/plain", or "image/jpeg".<p> - * Expects US-ASCII characters in mimeType. The encoding of the - * mimeData depends on the mimeType.<p> + * The mimeType parameter will be normalized with + * {@link Intent#normalizeMimeType} to follow Android best + * practices for intent filtering, for example to force lower-case. + * However the unchecked exception + * {@link IllegalArgumentException} may be thrown + * if the mimeType parameter has serious problems, + * for example if it is empty, so always catch this + * exception if you are passing user-generated data into this method. + * <p> * For efficiency, This method might not make an internal copy of the * mimeData byte array, so take care not - * to re-use the mimeData byte array while still using the returned + * to modify the mimeData byte array while still using the returned * NdefRecord. * - * @param mimeType MIME type, expects US-ASCII characters only + * @param mimeType a valid MIME type * @param mimeData MIME data as bytes * @return an NDEF Record containing the MIME-typed data - * @throws IllegalArugmentException if a valid record cannot be created + * @throws IllegalArugmentException if the mimeType is empty or invalid + * */ public static NdefRecord createMime(String mimeType, byte[] mimeData) { - if (mimeType.length() == 0) { - throw new IllegalArgumentException("empty mimeType"); + if (mimeType == null) throw new NullPointerException("mimeType is null"); + + // We only do basic MIME type validation: trying to follow the + // RFCs strictly only ends in tears, since there are lots of MIME + // types in common use that are not strictly valid as per RFC rules + mimeType = Intent.normalizeMimeType(mimeType); + if (mimeType.length() == 0) throw new IllegalArgumentException("mimeType is empty"); + int slashIndex = mimeType.indexOf('/'); + if (slashIndex == 0) throw new IllegalArgumentException("mimeType must have major type"); + if (slashIndex == mimeType.length() - 1) { + throw new IllegalArgumentException("mimeType must have minor type"); } + // missing '/' is allowed - return new NdefRecord(TNF_MIME_MEDIA, mimeType.getBytes(Charsets.US_ASCII), null, - mimeData); + // MIME RFCs suggest ASCII encoding for content-type + byte[] typeBytes = mimeType.getBytes(Charsets.US_ASCII); + return new NdefRecord(TNF_MIME_MEDIA, typeBytes, null, mimeData); } /** @@ -391,32 +432,38 @@ public final class NdefRecord implements Parcelable { * The data is typed by a domain name (usually your Android package name) and * a domain-specific type. This data is packaged into a "NFC Forum External * Type" NDEF Record.<p> - * Both the domain and type used to construct an external record are case - * insensitive, and this implementation will encode all characters to lower - * case. Only a subset of ASCII characters are allowed for the domain - * and type. There are no restrictions on the payload data.<p> + * NFC Forum requires that the domain and type used in an external record + * are treated as case insensitive, however Android intent filtering is + * always case sensitive. So this method will force the domain and type to + * lower-case before creating the NDEF Record.<p> + * The unchecked exception {@link IllegalArgumentException} will be thrown + * if the domain and type have serious problems, for example if either field + * is empty, so always catch this + * exception if you are passing user-generated data into this method.<p> + * There are no such restrictions on the payload data.<p> * For efficiency, This method might not make an internal copy of the * data byte array, so take care not - * to re-use the data byte array while still using the returned + * to modify the data byte array while still using the returned * NdefRecord. * * Reference specification: NFCForum-TS-RTD_1.0 * @param domain domain-name of issuing organization * @param type domain-specific type of data * @param data payload as bytes - * @throws IllegalArugmentException if a valid record cannot be created + * @throws IllegalArugmentException if either domain or type are empty or invalid */ public static NdefRecord createExternal(String domain, String type, byte[] data) { - if (domain.length() == 0 || type.length() == 0) { - throw new IllegalArgumentException("empty domain or type"); - } - byte[] byteDomain = domain.getBytes(Charsets.US_ASCII); - ensureValidDomain(byteDomain); - toLowerCase(byteDomain); - byte[] byteType = type.getBytes(Charsets.US_ASCII); - ensureValidWkt(byteType); - toLowerCase(byteType); + if (domain == null) throw new NullPointerException("domain is null"); + if (type == null) throw new NullPointerException("type is null"); + + domain = domain.trim().toLowerCase(Locale.US); + type = type.trim().toLowerCase(Locale.US); + + if (domain.length() == 0) throw new IllegalArgumentException("domain is empty"); + if (type.length() == 0) throw new IllegalArgumentException("type is empty"); + byte[] byteDomain = domain.getBytes(Charsets.UTF_8); + byte[] byteType = type.getBytes(Charsets.UTF_8); byte[] b = new byte[byteDomain.length + 1 + byteType.length]; System.arraycopy(byteDomain, 0, b, 0, byteDomain.length); b[byteDomain.length] = ':'; @@ -574,51 +621,113 @@ public final class NdefRecord implements Parcelable { } /** - * Helper to return the NdefRecord as a URI. - * TODO: Consider making a member method instead of static - * TODO: Consider more validation that this is a URI record - * TODO: Make a public API - * @hide + * Map this record to a MIME type, or return null if it cannot be mapped.<p> + * Currently this method considers all {@link #TNF_MIME_MEDIA} records to + * be MIME records, as well as some {@link #TNF_WELL_KNOWN} records such as + * {@link #RTD_TEXT}. If this is a MIME record then the MIME type as string + * is returned, otherwise null is returned.<p> + * This method does not perform validation that the MIME type is + * actually valid. It always attempts to + * return a string containing the type if this is a MIME record.<p> + * The returned MIME type will by normalized to lower-case using + * {@link Intent#normalizeMimeType}.<p> + * The MIME payload can be obtained using {@link #getPayload}. + * + * @return MIME type as a string, or null if this is not a MIME record */ - public static Uri parseWellKnownUriRecord(NdefRecord record) throws FormatException { - byte[] payload = record.getPayload(); - if (payload.length < 2) { - throw new FormatException("Payload is not a valid URI (missing prefix)"); + public String toMimeType() { + switch (mTnf) { + case NdefRecord.TNF_WELL_KNOWN: + if (Arrays.equals(mType, NdefRecord.RTD_TEXT)) { + return "text/plain"; + } + break; + case NdefRecord.TNF_MIME_MEDIA: + String mimeType = new String(mType, Charsets.US_ASCII); + return Intent.normalizeMimeType(mimeType); } + return null; + } - /* - * payload[0] contains the URI Identifier Code, per the - * NFC Forum "URI Record Type Definition" section 3.2.2. - * - * payload[1]...payload[payload.length - 1] contains the rest of - * the URI. - */ - int prefixIndex = (payload[0] & 0xff); - if (prefixIndex < 0 || prefixIndex >= URI_PREFIX_MAP.length) { - throw new FormatException("Payload is not a valid URI (invalid prefix)"); + /** + * Map this record to a URI, or return null if it cannot be mapped.<p> + * Currently this method considers the following to be URI records: + * <ul> + * <li>{@link #TNF_ABSOLUTE_URI} records.</li> + * <li>{@link #TNF_WELL_KNOWN} with a type of {@link #RTD_URI}.</li> + * <li>{@link #TNF_WELL_KNOWN} with a type of {@link #RTD_SMART_POSTER} + * and containing a URI record in the NDEF message nested in the payload. + * </li> + * <li>{@link #TNF_EXTERNAL_TYPE} records.</li> + * </ul> + * If this is not a URI record by the above rules, then null is returned.<p> + * This method does not perform validation that the URI is + * actually valid: it always attempts to create and return a URI if + * this record appears to be a URI record by the above rules.<p> + * The returned URI will be normalized to have a lower case scheme + * using {@link Uri#normalize}.<p> + * + * @return URI, or null if this is not a URI record + */ + public Uri toUri() { + return toUri(false); + } + + private Uri toUri(boolean inSmartPoster) { + switch (mTnf) { + case TNF_WELL_KNOWN: + if (Arrays.equals(mType, RTD_SMART_POSTER) && !inSmartPoster) { + try { + // check payload for a nested NDEF Message containing a URI + NdefMessage nestedMessage = new NdefMessage(mPayload); + for (NdefRecord nestedRecord : nestedMessage.getRecords()) { + Uri uri = nestedRecord.toUri(true); + if (uri != null) { + return uri; + } + } + } catch (FormatException e) { } + } else if (Arrays.equals(mType, RTD_URI)) { + return parseWktUri().normalize(); + } + break; + + case TNF_ABSOLUTE_URI: + Uri uri = Uri.parse(new String(mType, Charsets.UTF_8)); + return uri.normalize(); + + case TNF_EXTERNAL_TYPE: + if (inSmartPoster) { + break; + } + return Uri.parse("vnd.android.nfc://ext/" + new String(mType, Charsets.US_ASCII)); } - String prefix = URI_PREFIX_MAP[prefixIndex]; - byte[] fullUri = concat(prefix.getBytes(Charsets.UTF_8), - Arrays.copyOfRange(payload, 1, payload.length)); - return Uri.parse(new String(fullUri, Charsets.UTF_8)); + return null; } - private static byte[] concat(byte[]... arrays) { - int length = 0; - for (byte[] array : arrays) { - length += array.length; + /** + * Return complete URI of {@link #TNF_WELL_KNOWN}, {@link #RTD_URI} records. + * @return complete URI, or null if invalid + */ + private Uri parseWktUri() { + if (mPayload.length < 2) { + return null; } - byte[] result = new byte[length]; - int pos = 0; - for (byte[] array : arrays) { - System.arraycopy(array, 0, result, pos, array.length); - pos += array.length; + + // payload[0] contains the URI Identifier Code, as per + // NFC Forum "URI Record Type Definition" section 3.2.2. + int prefixIndex = (mPayload[0] & (byte)0xFF); + if (prefixIndex < 0 || prefixIndex >= URI_PREFIX_MAP.length) { + return null; } - return result; + String prefix = URI_PREFIX_MAP[prefixIndex]; + String suffix = new String(Arrays.copyOfRange(mPayload, 1, mPayload.length), + Charsets.UTF_8); + return Uri.parse(prefix + suffix); } /** - * Main parsing method.<p> + * Main record parsing method.<p> * Expects NdefMessage to begin immediately, allows trailing data.<p> * Currently has strict validation of all fields as per NDEF 1.0 * specification section 2.5. We will attempt to keep this as strict as @@ -902,42 +1011,4 @@ public final class NdefRecord implements Parcelable { } return s; } - - /** Ensure valid 'DNS-char' as per RFC2234 */ - private static void ensureValidDomain(byte[] bs) { - for (int i = 0; i < bs.length; i++) { - byte b = bs[i]; - if ((b >= 'A' && b <= 'Z') || - (b >= 'a' && b <= 'z') || - (b >= '0' && b <= '9') || - b == '.' || b == '-') { - continue; - } - throw new IllegalArgumentException("invalid character in domain"); - } - } - - /** Ensure valid 'WKT-char' as per RFC2234 */ - private static void ensureValidWkt(byte[] bs) { - for (int i = 0; i < bs.length; i++) { - byte b = bs[i]; - if ((b >= 'A' && b <= 'Z') || - (b >= 'a' && b <= 'z') || - (b >= '0' && b <= '9') || - b == '(' || b == ')' || b == '+' || b == ',' || b == '-' || - b == ':' || b == '=' || b == '@' || b == ';' || b == '$' || - b == '_' || b == '!' || b == '*' || b == '\'' || b == '.') { - continue; - } - throw new IllegalArgumentException("invalid character in type"); - } - } - - private static void toLowerCase(byte[] b) { - for (int i = 0; i < b.length; i++) { - if (b[i] >= 'A' && b[i] <= 'Z') { - b[i] += 0x20; - } - } - } } diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index 53a0341f562a..224a8bc4f590 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -66,6 +66,9 @@ public final class NfcAdapter { * <p>If the tag has an NDEF payload this intent is started before * {@link #ACTION_TECH_DISCOVERED}. If any activities respond to this intent neither * {@link #ACTION_TECH_DISCOVERED} or {@link #ACTION_TAG_DISCOVERED} will be started. + * + * <p>The MIME type or data URI of this intent are normalized before dispatch - + * so that MIME, URI scheme and URI host are always lower-case. */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED"; @@ -151,9 +154,13 @@ public final class NfcAdapter { public static final String EXTRA_TAG = "android.nfc.extra.TAG"; /** - * Optional extra containing an array of {@link NdefMessage} present on the discovered tag for - * the {@link #ACTION_NDEF_DISCOVERED}, {@link #ACTION_TECH_DISCOVERED}, and - * {@link #ACTION_TAG_DISCOVERED} intents. + * Extra containing an array of {@link NdefMessage} present on the discovered tag.<p> + * This extra is mandatory for {@link #ACTION_NDEF_DISCOVERED} intents, + * and optional for {@link #ACTION_TECH_DISCOVERED}, and + * {@link #ACTION_TAG_DISCOVERED} intents.<p> + * When this extra is present there will always be at least one + * {@link NdefMessage} element. Most NDEF tags have only one NDEF message, + * but we use an array for future compatibility. */ public static final String EXTRA_NDEF_MESSAGES = "android.nfc.extra.NDEF_MESSAGES"; @@ -386,10 +393,10 @@ public final class NfcAdapter { */ @Deprecated public static NfcAdapter getDefaultAdapter() { - // introduce in API version 9 (GB 2.3) + // introduced in API version 9 (GB 2.3) // deprecated in API version 10 (GB 2.3.3) // removed from public API in version 16 (ICS MR2) - // will need to maintain this as a hidden API for a while longer... + // should maintain as a hidden API for binary compatibility for a little longer Log.w(TAG, "WARNING: NfcAdapter.getDefaultAdapter() is deprecated, use " + "NfcAdapter.getDefaultAdapter(Context) instead", new Exception()); @@ -803,6 +810,7 @@ public final class NfcAdapter { * @throws IllegalStateException if the Activity has already been paused * @deprecated use {@link #setNdefPushMessage} instead */ + @Deprecated public void disableForegroundNdefPush(Activity activity) { if (activity == null) { throw new NullPointerException(); @@ -875,6 +883,24 @@ public final class NfcAdapter { } /** + * Inject a mock NFC tag.<p> + * Used for testing purposes. + * <p class="note">Requires the + * {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission. + * @hide + */ + public void dispatch(Tag tag, NdefMessage message) { + if (tag == null) { + throw new NullPointerException("tag cannot be null"); + } + try { + sService.dispatch(tag, message); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + } + } + + /** * @hide */ public INfcAdapterExtras getNfcAdapterExtrasInterface() { diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 88fea910395c..c106092122dc 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -167,6 +167,8 @@ public class Build { * medium density normal size screens unless otherwise indicated). * They can still explicitly specify screen support either way with the * supports-screens manifest tag. + * <li> {@link android.widget.TabHost} will use the new dark tab + * background design. * </ul> */ public static final int DONUT = 4; @@ -208,6 +210,13 @@ public class Build { /** * November 2010: Android 2.3 + * + * <p>Applications targeting this or a later release will get these + * new changes in behavior:</p> + * <ul> + * <li> The application's notification icons will be shown on the new + * dark status bar background, so must be visible in this situation. + * </ul> */ public static final int GINGERBREAD = 9; @@ -224,14 +233,34 @@ public class Build { * <ul> * <li> The default theme for applications is now dark holographic: * {@link android.R.style#Theme_Holo}. + * <li> On large screen devices that do not have a physical menu + * button, the soft (compatibility) menu is disabled. * <li> The activity lifecycle has changed slightly as per * {@link android.app.Activity}. + * <li> An application will crash if it does not call through + * to the super implementation of its + * {@link android.app.Activity#onPause Activity.onPause()} method. * <li> When an application requires a permission to access one of * its components (activity, receiver, service, provider), this * permission is no longer enforced when the application wants to * access its own component. This means it can require a permission * on a component that it does not itself hold and still access that * component. + * <li> {@link android.content.Context#getSharedPreferences + * Context.getSharedPreferences()} will not automatically reload + * the preferences if they have changed on storage, unless + * {@link android.content.Context#MODE_MULTI_PROCESS} is used. + * <li> {@link android.view.ViewGroup#setMotionEventSplittingEnabled} + * will default to true. + * <li> {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} + * is enabled by default on windows. + * <li> {@link android.widget.PopupWindow#isSplitTouchEnabled() + * PopupWindow.isSplitTouchEnabled()} will return true by default. + * <li> {@link android.widget.GridView} and {@link android.widget.ListView} + * will use {@link android.view.View#setActivated View.setActivated} + * for selected items if they do not implement {@link android.widget.Checkable}. + * <li> {@link android.widget.Scroller} will be constructed with + * "flywheel" behavior enabled by default. * </ul> */ public static final int HONEYCOMB = 11; @@ -266,13 +295,26 @@ public class Build { * preferred over the older screen size buckets and for older devices * the appropriate buckets will be inferred from them.</p> * - * <p>New {@link android.content.pm.PackageManager#FEATURE_SCREEN_PORTRAIT} + * <p>Applications targeting this or a later release will get these + * new changes in behavior:</p> + * <ul> + * <li><p>New {@link android.content.pm.PackageManager#FEATURE_SCREEN_PORTRAIT} * and {@link android.content.pm.PackageManager#FEATURE_SCREEN_LANDSCAPE} - * features are introduced in this release. Applications that target + * features were introduced in this release. Applications that target * previous platform versions are assumed to require both portrait and * landscape support in the device; when targeting Honeycomb MR1 or * greater the application is responsible for specifying any specific * orientation it requires.</p> + * <li><p>{@link android.os.AsyncTask} will use the serial executor + * by default when calling {@link android.os.AsyncTask#execute}.</p> + * <li><p>{@link android.content.pm.ActivityInfo#configChanges + * ActivityInfo.configChanges} will have the + * {@link android.content.pm.ActivityInfo#CONFIG_SCREEN_SIZE} and + * {@link android.content.pm.ActivityInfo#CONFIG_SMALLEST_SCREEN_SIZE} + * bits set; these need to be cleared for older applications because + * some developers have done absolute comparisons against this value + * instead of correctly masking the bits they are interested in. + * </ul> */ public static final int HONEYCOMB_MR2 = 13; @@ -306,14 +348,31 @@ public class Build { * <li> The fadingEdge attribute on views will be ignored (fading edges is no * longer a standard part of the UI). A new requiresFadingEdge attribute allows * applications to still force fading edges on for special cases. + * <li> {@link android.content.Context#bindService Context.bindService()} + * will not automatically add in {@link android.content.Context#BIND_WAIVE_PRIORITY}. + * <li> App Widgets will have standard padding automatically added around + * them, rather than relying on the padding being baked into the widget itself. + * <li> An exception will be thrown if you try to change the type of a + * window after it has been added to the window manager. Previously this + * would result in random incorrect behavior. + * <li> {@link android.view.animation.AnimationSet} will parse out + * the duration, fillBefore, fillAfter, repeatMode, and startOffset + * XML attributes that are defined. + * <li> {@link android.app.ActionBar#setHomeButtonEnabled + * ActionBar.setHomeButtonEnabled()} is false by default. * </ul> */ public static final int ICE_CREAM_SANDWICH = 14; /** - * Android 4.0.3. + * December 2011: Android 4.0.3. */ public static final int ICE_CREAM_SANDWICH_MR1 = 15; + + /** + * Next up on Android! + */ + public static final int JELLY_BEAN = CUR_DEVELOPMENT; } /** The type of build, like "user" or "eng". */ diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java index 0f26a34ece8d..5dc206fe730b 100644 --- a/core/java/android/text/style/SuggestionSpan.java +++ b/core/java/android/text/style/SuggestionSpan.java @@ -25,6 +25,7 @@ import android.os.SystemClock; import android.text.ParcelableSpan; import android.text.TextPaint; import android.text.TextUtils; +import android.util.Log; import android.widget.TextView; import java.util.Arrays; @@ -114,7 +115,7 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { * @param context Context for the application * @param locale locale Locale of the suggestions * @param suggestions Suggestions for the string under the span. Only the first up to - * {@link SuggestionSpan#SUGGESTIONS_MAX_SIZE} will be considered. + * {@link SuggestionSpan#SUGGESTIONS_MAX_SIZE} will be considered. Null values not permitted. * @param flags Additional flags indicating how this span is handled in TextView * @param notificationTargetClass if not null, this class will get notified when the user * selects one of the suggestions. @@ -124,10 +125,13 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { final int N = Math.min(SUGGESTIONS_MAX_SIZE, suggestions.length); mSuggestions = Arrays.copyOf(suggestions, N); mFlags = flags; - if (context != null && locale == null) { + if (locale != null) { + mLocaleString = locale.toString(); + } else if (context != null) { mLocaleString = context.getResources().getConfiguration().locale.toString(); } else { - mLocaleString = locale.toString(); + Log.e("SuggestionSpan", "No locale or context specified in SuggestionSpan constructor"); + mLocaleString = ""; } if (notificationTargetClass != null) { diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java index 519b98056b9f..a43d36c14660 100644 --- a/core/java/android/util/DisplayMetrics.java +++ b/core/java/android/util/DisplayMetrics.java @@ -57,6 +57,13 @@ public class DisplayMetrics { public static final int DENSITY_XHIGH = 320; /** + * Standard quantized DPI for extra-extra-high-density screens. Applications + * should not generally worry about this density; relying on XHIGH graphics + * being scaled up to it should be sufficient for almost all cases. + */ + public static final int DENSITY_XXHIGH = 480; + + /** * The reference density used throughout the system. */ public static final int DENSITY_DEFAULT = DENSITY_MEDIUM; diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index 4592ae6c1a57..1c9cbbfea53b 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -238,6 +238,15 @@ public abstract class HardwareRenderer { private static native void nSetupShadersDiskCache(String cacheFile); /** + * Notifies EGL that the frame is about to be rendered. + */ + private static void beginFrame() { + nBeginFrame(); + } + + private static native void nBeginFrame(); + + /** * Interface used to receive callbacks whenever a view is drawn by * a hardware renderer instance. */ @@ -808,6 +817,7 @@ public abstract class HardwareRenderer { } void onPreDraw(Rect dirty) { + } void onPostDraw() { @@ -832,6 +842,8 @@ public abstract class HardwareRenderer { dirty = null; } + beginFrame(); + onPreDraw(dirty); HardwareCanvas canvas = mCanvas; diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 5c63366f1b40..dda695fcca91 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -2661,8 +2661,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager child.onAnimationStart(); } - more = a.getTransformation(drawingTime, mChildTransformation, - scalingRequired ? mAttachInfo.mApplicationScale : 1f); + more = a.getTransformation(drawingTime, mChildTransformation, 1f); if (scalingRequired && mAttachInfo.mApplicationScale != 1f) { if (mInvalidationTransformation == null) { mInvalidationTransformation = new Transformation(); diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java index bc2a270dcd7d..3563d4d31eef 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -140,7 +140,8 @@ public interface InputConnection { /** * Delete <var>beforeLength</var> characters of text before the current cursor * position, and delete <var>afterLength</var> characters of text after the - * current cursor position, excluding composing text. + * current cursor position, excluding composing text. Before and after refer + * to the order of the characters in the string, not to their visual representation. * * * @param beforeLength The number of characters to be deleted before the diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index b41e6f5e7907..0985e1465b0b 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -651,19 +651,7 @@ public final class InputMethodManager { } } - if (mServedInputConnection != null) { - // We need to tell the previously served view that it is no - // longer the input target, so it can reset its state. Schedule - // this call on its window's Handler so it will be on the correct - // thread and outside of our lock. - Handler vh = mServedView.getHandler(); - if (vh != null) { - // This will result in a call to reportFinishInputConnection() - // below. - vh.sendMessage(vh.obtainMessage(ViewRootImpl.FINISH_INPUT_CONNECTION, - mServedInputConnection)); - } - } + notifyInputConnectionFinished(); mServedView = null; mCompletions = null; @@ -671,7 +659,25 @@ public final class InputMethodManager { clearConnectionLocked(); } } - + + /** + * Notifies the served view that the current InputConnection will no longer be used. + */ + private void notifyInputConnectionFinished() { + if (mServedView != null && mServedInputConnection != null) { + // We need to tell the previously served view that it is no + // longer the input target, so it can reset its state. Schedule + // this call on its window's Handler so it will be on the correct + // thread and outside of our lock. + Handler vh = mServedView.getHandler(); + if (vh != null) { + // This will result in a call to reportFinishInputConnection() below. + vh.sendMessage(vh.obtainMessage(ViewRootImpl.FINISH_INPUT_CONNECTION, + mServedInputConnection)); + } + } + } + /** * Called from the FINISH_INPUT_CONNECTION message above. * @hide @@ -681,7 +687,7 @@ public final class InputMethodManager { ic.finishComposingText(); } } - + public void displayCompletions(View view, CompletionInfo[] completions) { checkFocus(); synchronized (mH) { @@ -831,7 +837,7 @@ public final class InputMethodManager { * shown with {@link #SHOW_FORCED}. */ public static final int HIDE_NOT_ALWAYS = 0x0002; - + /** * Synonym for {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)} * without a result: request to hide the soft input window from the @@ -993,7 +999,7 @@ public final class InputMethodManager { tba.fieldId = view.getId(); InputConnection ic = view.onCreateInputConnection(tba); if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic); - + synchronized (mH) { // Now that we are locked again, validate that our state hasn't // changed. @@ -1012,6 +1018,8 @@ public final class InputMethodManager { // Hook 'em up and let 'er rip. mCurrentTextBoxAttribute = tba; mServedConnecting = false; + // Notify the served view that its previous input connection is finished + notifyInputConnectionFinished(); mServedInputConnection = ic; IInputContext servedContext; if (ic != null) { @@ -1115,7 +1123,7 @@ public final class InputMethodManager { } } - void scheduleCheckFocusLocked(View view) { + static void scheduleCheckFocusLocked(View view) { Handler vh = view.getHandler(); if (vh != null && !vh.hasMessages(ViewRootImpl.CHECK_FOCUS)) { // This will result in a call to checkFocus() below. diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 369763537ad2..b255c57c1b03 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -60,6 +60,10 @@ import android.os.StrictMode; import android.os.SystemClock; import android.provider.Settings; import android.speech.tts.TextToSpeech; +import android.text.Editable; +import android.text.InputType; +import android.text.Selection; +import android.text.TextUtils; import android.util.AttributeSet; import android.util.EventLog; import android.util.Log; @@ -363,39 +367,126 @@ public class WebView extends AbsoluteLayout } /** - * InputConnection used for ContentEditable. This captures the 'delete' - * commands and sends delete key presses. + * InputConnection used for ContentEditable. This captures changes + * to the text and sends them either as key strokes or text changes. */ private class WebViewInputConnection extends BaseInputConnection { + // Used for mapping characters to keys typed. + private KeyCharacterMap mKeyCharacterMap; + public WebViewInputConnection() { - super(WebView.this, false); + super(WebView.this, true); + } + + @Override + public boolean setComposingText(CharSequence text, int newCursorPosition) { + Editable editable = getEditable(); + int start = getComposingSpanStart(editable); + int end = getComposingSpanEnd(editable); + if (start < 0 || end < 0) { + start = Selection.getSelectionStart(editable); + end = Selection.getSelectionEnd(editable); + } + if (end < start) { + int temp = end; + end = start; + start = temp; + } + setNewText(start, end, text); + return super.setComposingText(text, newCursorPosition); + } + + @Override + public boolean commitText(CharSequence text, int newCursorPosition) { + setComposingText(text, newCursorPosition); + finishComposingText(); + return true; } - private void sendKeyPress(int keyCode) { + @Override + public boolean deleteSurroundingText(int leftLength, int rightLength) { + Editable editable = getEditable(); + int cursorPosition = Selection.getSelectionEnd(editable); + int startDelete = Math.max(0, cursorPosition - leftLength); + int endDelete = Math.min(editable.length(), + cursorPosition + rightLength); + setNewText(startDelete, endDelete, ""); + return super.deleteSurroundingText(leftLength, rightLength); + } + + /** + * Sends a text change to webkit indirectly. If it is a single- + * character add or delete, it sends it as a key stroke. If it cannot + * be represented as a key stroke, it sends it as a field change. + * @param start The start offset (inclusive) of the text being changed. + * @param end The end offset (exclusive) of the text being changed. + * @param text The new text to replace the changed text. + */ + private void setNewText(int start, int end, CharSequence text) { + Editable editable = getEditable(); + CharSequence original = editable.subSequence(start, end); + boolean isCharacterAdd = false; + boolean isCharacterDelete = false; + int textLength = text.length(); + int originalLength = original.length(); + if (textLength > originalLength) { + isCharacterAdd = (textLength == originalLength + 1) + && TextUtils.regionMatches(text, 0, original, 0, + originalLength); + } else if (originalLength > textLength) { + isCharacterDelete = (textLength == originalLength - 1) + && TextUtils.regionMatches(text, 0, original, 0, + textLength); + } + if (isCharacterAdd) { + sendCharacter(text.charAt(textLength - 1)); + mTextGeneration++; + } else if (isCharacterDelete) { + sendDeleteKey(); + mTextGeneration++; + } else if (textLength != originalLength || + !TextUtils.regionMatches(text, 0, original, 0, + textLength)) { + // Send a message so that key strokes and text replacement + // do not come out of order. + Message replaceMessage = mPrivateHandler.obtainMessage( + REPLACE_TEXT, start, end, text.toString()); + mPrivateHandler.sendMessage(replaceMessage); + } + } + + /** + * Send a single character to the WebView as a key down and up event. + * @param c The character to be sent. + */ + private void sendCharacter(char c) { + if (mKeyCharacterMap == null) { + mKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); + } + char[] chars = new char[1]; + chars[0] = c; + KeyEvent[] events = mKeyCharacterMap.getEvents(chars); + if (events != null) { + for (KeyEvent event : events) { + sendKeyEvent(event); + } + } + } + + /** + * Send the delete character as a key down and up event. + */ + private void sendDeleteKey() { long eventTime = SystemClock.uptimeMillis(); sendKeyEvent(new KeyEvent(eventTime, eventTime, - KeyEvent.ACTION_DOWN, keyCode, 0, 0, + KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_SOFT_KEYBOARD)); sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime, - KeyEvent.ACTION_UP, keyCode, 0, 0, + KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_SOFT_KEYBOARD)); } - - @Override - public boolean deleteSurroundingText(int beforeLength, int afterLength) { - // Look for one-character delete and send it as a key press. - if (beforeLength == 1 && afterLength == 0) { - sendKeyPress(KeyEvent.KEYCODE_DEL); - } else if (beforeLength == 0 && afterLength == 1){ - sendKeyPress(KeyEvent.KEYCODE_FORWARD_DEL); - } else if (mWebViewCore != null) { - mWebViewCore.sendMessage(EventHub.DELETE_SURROUNDING_TEXT, - beforeLength, afterLength); - } - return super.deleteSurroundingText(beforeLength, afterLength); - } } @@ -799,6 +890,8 @@ public class WebView extends AbsoluteLayout static final int EXIT_FULLSCREEN_VIDEO = 140; static final int COPY_TO_CLIPBOARD = 141; + static final int INIT_EDIT_FIELD = 142; + static final int REPLACE_TEXT = 143; private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID; private static final int LAST_PACKAGE_MSG_ID = HIT_TEST_RESULT; @@ -4951,12 +5044,18 @@ public class WebView extends AbsoluteLayout @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { - outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN + outAttrs.inputType = EditorInfo.IME_FLAG_NO_FULLSCREEN | EditorInfo.TYPE_CLASS_TEXT - | EditorInfo.TYPE_TEXT_VARIATION_NORMAL; + | EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT + | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE + | EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT + | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES; + outAttrs.imeOptions = EditorInfo.IME_ACTION_NONE; + if (mInputConnection == null) { mInputConnection = new WebViewInputConnection(); } + outAttrs.initialCapsMode = mInputConnection.getCursorCapsMode(InputType.TYPE_CLASS_TEXT); return mInputConnection; } @@ -8963,6 +9062,31 @@ public class WebView extends AbsoluteLayout copyToClipboard((String) msg.obj); break; + case INIT_EDIT_FIELD: + if (mInputConnection != null) { + mTextGeneration = 0; + String text = (String)msg.obj; + mInputConnection.beginBatchEdit(); + Editable editable = mInputConnection.getEditable(); + editable.replace(0, editable.length(), text); + int start = msg.arg1; + int end = msg.arg2; + mInputConnection.setComposingRegion(end, end); + mInputConnection.setSelection(start, end); + mInputConnection.endBatchEdit(); + } + break; + + case REPLACE_TEXT:{ + String text = (String)msg.obj; + int start = msg.arg1; + int end = msg.arg2; + int cursorPosition = start + text.length(); + replaceTextfieldText(start, end, text, + cursorPosition, cursorPosition); + break; + } + default: super.handleMessage(msg); break; @@ -9088,10 +9212,13 @@ public class WebView extends AbsoluteLayout */ private void updateTextSelectionFromMessage(int nodePointer, int textGeneration, WebViewCore.TextSelectionData data) { - if (inEditingMode() - && mWebTextView.isSameTextField(nodePointer) - && textGeneration == mTextGeneration) { - mWebTextView.setSelectionFromWebKit(data.mStart, data.mEnd); + if (textGeneration == mTextGeneration) { + if (inEditingMode() + && mWebTextView.isSameTextField(nodePointer)) { + mWebTextView.setSelectionFromWebKit(data.mStart, data.mEnd); + } else if (mInputConnection != null){ + mInputConnection.setSelection(data.mStart, data.mEnd); + } } } diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index fe5c04c0abfd..fe5158177540 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -647,18 +647,6 @@ public final class WebViewCore { int end, int textGeneration); /** - * Delete text near the cursor. - * @param nativeClass The pointer to the native class (mNativeClass) - * @param leftLength The number of characters to the left of the cursor to - * delete - * @param rightLength The number of characters to the right of the cursor - * to delete. - */ - private native void nativeDeleteSurroundingText(int nativeClass, - int leftLength, - int rightLength); - - /** * Set the selection to (start, end) in the focused textfield. If start and * end are out of order, swap them. * @param nativeClass Pointer to the C++ WebViewCore object mNativeClass @@ -1576,11 +1564,6 @@ public final class WebViewCore { deleteSelectionData.mStart, deleteSelectionData.mEnd, msg.arg1); break; - case DELETE_SURROUNDING_TEXT: - nativeDeleteSurroundingText(mNativeClass, - msg.arg1, msg.arg2); - break; - case SET_SELECTION: nativeSetSelection(mNativeClass, msg.arg1, msg.arg2); break; @@ -2739,6 +2722,15 @@ public final class WebViewCore { WebView.FIND_AGAIN).sendToTarget(); } + // called by JNI + private void initEditField(String text, int start, int end) { + if (mWebView == null) { + return; + } + Message.obtain(mWebView.mPrivateHandler, + WebView.INIT_EDIT_FIELD, start, end, text).sendToTarget(); + } + private native void nativeUpdateFrameCacheIfLoading(int nativeClass); private native void nativeRevealSelection(int nativeClass); private native String nativeRequestLabel(int nativeClass, int framePtr, diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java index a10615956a20..570f0f95d603 100644 --- a/core/java/android/widget/SpellChecker.java +++ b/core/java/android/widget/SpellChecker.java @@ -102,7 +102,8 @@ public class SpellChecker implements SpellCheckerSessionListener { mTextServicesManager = (TextServicesManager) mTextView.getContext(). getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); - if (!mTextServicesManager.isSpellCheckerEnabled()) { + if (!mTextServicesManager.isSpellCheckerEnabled() + || mTextServicesManager.getCurrentSpellCheckerSubtype(true) == null) { mSpellCheckerSession = null; } else { mSpellCheckerSession = mTextServicesManager.newSpellCheckerSession( @@ -341,56 +342,15 @@ public class SpellChecker implements SpellCheckerSessionListener { final int end = editable.getSpanEnd(spellCheckSpan); if (start < 0 || end <= start) return; // span was removed in the meantime - // Other suggestion spans may exist on that region, with identical suggestions, filter - // them out to avoid duplicates. - SuggestionSpan[] suggestionSpans = editable.getSpans(start, end, SuggestionSpan.class); - final int length = suggestionSpans.length; - for (int i = 0; i < length; i++) { - final int spanStart = editable.getSpanStart(suggestionSpans[i]); - final int spanEnd = editable.getSpanEnd(suggestionSpans[i]); - if (spanStart != start || spanEnd != end) { - // Nulled (to avoid new array allocation) if not on that exact same region - suggestionSpans[i] = null; - } - } - final int suggestionsCount = suggestionsInfo.getSuggestionsCount(); - String[] suggestions; if (suggestionsCount <= 0) { // A negative suggestion count is possible - suggestions = ArrayUtils.emptyArray(String.class); - } else { - int numberOfSuggestions = 0; - suggestions = new String[suggestionsCount]; - - for (int i = 0; i < suggestionsCount; i++) { - final String spellSuggestion = suggestionsInfo.getSuggestionAt(i); - if (spellSuggestion == null) break; - boolean suggestionFound = false; - - for (int j = 0; j < length && !suggestionFound; j++) { - if (suggestionSpans[j] == null) break; - - String[] suggests = suggestionSpans[j].getSuggestions(); - for (int k = 0; k < suggests.length; k++) { - if (spellSuggestion.equals(suggests[k])) { - // The suggestion is already provided by an other SuggestionSpan - suggestionFound = true; - break; - } - } - } - - if (!suggestionFound) { - suggestions[numberOfSuggestions++] = spellSuggestion; - } - } + return; + } - if (numberOfSuggestions != suggestionsCount) { - String[] newSuggestions = new String[numberOfSuggestions]; - System.arraycopy(suggestions, 0, newSuggestions, 0, numberOfSuggestions); - suggestions = newSuggestions; - } + String[] suggestions = new String[suggestionsCount]; + for (int i = 0; i < suggestionsCount; i++) { + suggestions[i] = suggestionsInfo.getSuggestionAt(i); } SuggestionSpan suggestionSpan = new SuggestionSpan(mTextView.getContext(), suggestions, diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 9fb26ae7455d..39a4f42a79e6 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -6795,6 +6795,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + if (changed) mTextDisplayListIsValid = false; + } + /** * Returns true if anything changed. */ @@ -9838,17 +9844,34 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener String[] suggestions = suggestionSpan.getSuggestions(); int nbSuggestions = suggestions.length; for (int suggestionIndex = 0; suggestionIndex < nbSuggestions; suggestionIndex++) { - SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions]; - suggestionInfo.suggestionSpan = suggestionSpan; - suggestionInfo.suggestionIndex = suggestionIndex; - suggestionInfo.text.replace(0, suggestionInfo.text.length(), - suggestions[suggestionIndex]); + String suggestion = suggestions[suggestionIndex]; + + boolean suggestionIsDuplicate = false; + for (int i = 0; i < mNumberOfSuggestions; i++) { + if (mSuggestionInfos[i].text.toString().equals(suggestion)) { + SuggestionSpan otherSuggestionSpan = mSuggestionInfos[i].suggestionSpan; + final int otherSpanStart = spannable.getSpanStart(otherSuggestionSpan); + final int otherSpanEnd = spannable.getSpanEnd(otherSuggestionSpan); + if (spanStart == otherSpanStart && spanEnd == otherSpanEnd) { + suggestionIsDuplicate = true; + break; + } + } + } - mNumberOfSuggestions++; - if (mNumberOfSuggestions == MAX_NUMBER_SUGGESTIONS) { - // Also end outer for loop - spanIndex = nbSpans; - break; + if (!suggestionIsDuplicate) { + SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions]; + suggestionInfo.suggestionSpan = suggestionSpan; + suggestionInfo.suggestionIndex = suggestionIndex; + suggestionInfo.text.replace(0, suggestionInfo.text.length(), suggestion); + + mNumberOfSuggestions++; + + if (mNumberOfSuggestions == MAX_NUMBER_SUGGESTIONS) { + // Also end outer for loop + spanIndex = nbSpans; + break; + } } } } @@ -9857,7 +9880,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener highlightTextDifferences(mSuggestionInfos[i], spanUnionStart, spanUnionEnd); } - // Add to dictionary item if there is a span with the misspelled flag + // Add "Add to dictionary" item if there is a span with the misspelled flag if (misspelledSpan != null) { final int misspelledStart = spannable.getSpanStart(misspelledSpan); final int misspelledEnd = spannable.getSpanEnd(misspelledSpan); @@ -9915,8 +9938,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener suggestionInfo.text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // Add the text before and after the span. - suggestionInfo.text.insert(0, mText.toString().substring(unionStart, spanStart)); - suggestionInfo.text.append(mText.toString().substring(spanEnd, unionEnd)); + final String textAsString = text.toString(); + suggestionInfo.text.insert(0, textAsString.substring(unionStart, spanStart)); + suggestionInfo.text.append(textAsString.substring(spanEnd, unionEnd)); } @Override diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java index 32e733baa98a..9579bce468da 100644 --- a/core/java/com/android/internal/widget/EditableInputConnection.java +++ b/core/java/com/android/internal/widget/EditableInputConnection.java @@ -35,6 +35,11 @@ public class EditableInputConnection extends BaseInputConnection { private final TextView mTextView; + // Keeps track of nested begin/end batch edit to ensure this connection always has a + // balanced impact on its associated TextView. + // A negative value means that this connection has been finished by the InputMethodManager. + private int mBatchEditNesting; + public EditableInputConnection(TextView textview) { super(textview, true); mTextView = textview; @@ -48,19 +53,35 @@ public class EditableInputConnection extends BaseInputConnection { } return null; } - + @Override public boolean beginBatchEdit() { - mTextView.beginBatchEdit(); - return true; + synchronized(this) { + if (mBatchEditNesting >= 0) { + mTextView.beginBatchEdit(); + mBatchEditNesting++; + return true; + } + } + return false; } - + @Override public boolean endBatchEdit() { - mTextView.endBatchEdit(); - return true; + synchronized(this) { + if (mBatchEditNesting > 0) { + // When the connection is reset by the InputMethodManager and finishComposingText + // is called, some endBatchEdit calls may still be asynchronously received from the + // IME. Do not take these into account, thus ensuring that this IC's final + // contribution to mTextView's nested batch edit count is zero. + mTextView.endBatchEdit(); + mBatchEditNesting--; + return true; + } + } + return false; } - + @Override public boolean clearMetaKeyStates(int states) { final Editable content = getEditable(); @@ -76,7 +97,24 @@ public class EditableInputConnection extends BaseInputConnection { } return true; } - + + @Override + public boolean finishComposingText() { + final boolean superResult = super.finishComposingText(); + synchronized(this) { + if (mBatchEditNesting < 0) { + // The connection was already finished + return false; + } + while (mBatchEditNesting > 0) { + endBatchEdit(); + } + // Will prevent any further calls to begin or endBatchEdit + mBatchEditNesting = -1; + } + return superResult; + } + @Override public boolean commitCompletion(CompletionInfo text) { if (DEBUG) Log.v(TAG, "commitCompletion " + text); diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 905a1713add5..6893ffbaf6c5 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -404,7 +404,7 @@ public class LockPatternUtils { saveLockPassword(null, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); setLockPatternEnabled(false); saveLockPattern(null); - setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); + setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED); setLong(PASSWORD_TYPE_ALTERNATE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED); } diff --git a/core/jni/android_view_HardwareRenderer.cpp b/core/jni/android_view_HardwareRenderer.cpp index 09809ecf49fb..cdcde51e9e2b 100644 --- a/core/jni/android_view_HardwareRenderer.cpp +++ b/core/jni/android_view_HardwareRenderer.cpp @@ -22,6 +22,8 @@ #include <EGL/egl_cache.h> +EGLAPI void EGLAPIENTRY eglBeginFrame(EGLDisplay dpy, EGLSurface surface); + namespace android { // ---------------------------------------------------------------------------- @@ -36,6 +38,12 @@ static void android_view_HardwareRenderer_setupShadersDiskCache(JNIEnv* env, job env->ReleaseStringUTFChars(diskCachePath, cacheArray); } +static void android_view_HardwareRenderer_beginFrame(JNIEnv* env, jobject clazz) { + EGLDisplay dpy = eglGetCurrentDisplay(); + EGLSurface surf = eglGetCurrentSurface(EGL_DRAW); + eglBeginFrame(dpy, surf); +} + // ---------------------------------------------------------------------------- // JNI Glue // ---------------------------------------------------------------------------- @@ -45,6 +53,8 @@ const char* const kClassPathName = "android/view/HardwareRenderer"; static JNINativeMethod gMethods[] = { { "nSetupShadersDiskCache", "(Ljava/lang/String;)V", (void*) android_view_HardwareRenderer_setupShadersDiskCache }, + { "nBeginFrame", "()V", + (void*) android_view_HardwareRenderer_beginFrame }, }; int register_android_view_HardwareRenderer(JNIEnv* env) { diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 545a55529a8a..b514bf5311cc 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -1830,12 +1830,12 @@ <!-- Defines whether the vertical scrollbar track should always be drawn. --> <attr name="scrollbarAlwaysDrawVerticalTrack" format="boolean" /> - <!-- {@deprecated This attribute is deprecated and will be ignored as of - API level 14 (<code>android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH</code>). + <!-- This attribute is deprecated and will be ignored as of + API level 14 ({@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}). Using fading edges may introduce noticeable performance degradations and should be used only when required by the application's visual design. To request fading edges with API level 14 and above, - use the <code>requiresFadingEdge</code> attribute instead.} --> + use the <code>android:requiresFadingEdge</code> attribute instead. --> <attr name="fadingEdge"> <!-- No edge is faded. --> <flag name="none" value="0x00000000" /> diff --git a/core/tests/coretests/src/android/util/InternalSelectionView.java b/core/tests/coretests/src/android/util/InternalSelectionView.java index babf38d308ec..a0fb0f18a4fa 100644 --- a/core/tests/coretests/src/android/util/InternalSelectionView.java +++ b/core/tests/coretests/src/android/util/InternalSelectionView.java @@ -36,6 +36,11 @@ import android.util.AttributeSet; * entire width of the view. The height of the view is divided evenly among * the rows. * + * Note: If the height of the view does not divide exactly to the number of rows, + * the last row's height is inflated with the remainder. For example, if the + * view height is 22 and there are two rows, the height of the first row is + * 10 and the second 22. + * * Notice what this view does to be a good citizen w.r.t its internal selection: * 1) calls {@link View#requestRectangleOnScreen} each time the selection changes due to * internal navigation. @@ -138,9 +143,6 @@ public class InternalSelectionView extends View { @Override protected void onDraw(Canvas canvas) { - - int rowHeight = getRowHeight(); - int rectTop = mPaddingTop; int rectLeft = mPaddingLeft; int rectRight = getWidth() - mPaddingRight; @@ -149,6 +151,8 @@ public class InternalSelectionView extends View { mPainter.setColor(Color.BLACK); mPainter.setAlpha(0x20); + int rowHeight = getRowHeight(i); + // draw background rect mTempRect.set(rectLeft, rectTop, rectRight, rectTop + rowHeight); canvas.drawRect(mTempRect, mPainter); @@ -178,12 +182,19 @@ public class InternalSelectionView extends View { } } - private int getRowHeight() { - return (getHeight() - mPaddingTop - mPaddingBottom) / mNumRows; + private int getRowHeight(int row) { + final int availableHeight = getHeight() - mPaddingTop - mPaddingBottom; + final int desiredRowHeight = availableHeight / mNumRows; + if (row < mNumRows - 1) { + return desiredRowHeight; + } else { + final int residualHeight = availableHeight % mNumRows; + return desiredRowHeight + residualHeight; + } } public void getRectForRow(Rect rect, int row) { - final int rowHeight = getRowHeight(); + final int rowHeight = getRowHeight(row); final int top = mPaddingTop + row * rowHeight; rect.set(mPaddingLeft, top, @@ -197,10 +208,7 @@ public class InternalSelectionView extends View { requestRectangleOnScreen(mTempRect); } - - /* (non-Javadoc) - * @see android.view.KeyEvent.Callback#onKeyDown(int, android.view.KeyEvent) - */ + @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch(event.getKeyCode()) { case KeyEvent.KEYCODE_DPAD_UP: diff --git a/core/tests/coretests/src/android/widget/focus/ListOfInternalSelectionViews.java b/core/tests/coretests/src/android/widget/focus/ListOfInternalSelectionViews.java index 6518341c7ac3..53b866c92ef8 100644 --- a/core/tests/coretests/src/android/widget/focus/ListOfInternalSelectionViews.java +++ b/core/tests/coretests/src/android/widget/focus/ListOfInternalSelectionViews.java @@ -17,6 +17,7 @@ package android.widget.focus; import android.app.Activity; +import android.graphics.Point; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; @@ -110,7 +111,10 @@ public class ListOfInternalSelectionViews extends Activity { @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); - mScreenHeight = getWindowManager().getDefaultDisplay().getHeight(); + + Point size = new Point(); + getWindowManager().getDefaultDisplay().getSize(size); + mScreenHeight = size.y; Bundle extras = getIntent().getExtras(); if (extras != null) { diff --git a/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java b/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java index 909a8c9a845d..a78b0c932c4b 100644 --- a/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java +++ b/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java @@ -90,7 +90,7 @@ public class RequestFocusTest extends ActivityInstrumentationTestCase<RequestFoc fail("requestFocus from wrong thread should raise exception."); } catch (AndroidRuntimeException e) { // Expected. The actual exception is not public, so we can't catch it. - assertEquals("android.view.ViewAncestor$CalledFromWrongThreadException", + assertEquals("android.view.ViewRootImpl$CalledFromWrongThreadException", e.getClass().getName()); } } diff --git a/core/tests/coretests/src/android/widget/focus/ScrollingThroughListOfFocusablesTest.java b/core/tests/coretests/src/android/widget/focus/ScrollingThroughListOfFocusablesTest.java index eb9192a43c13..795e09ca0c60 100644 --- a/core/tests/coretests/src/android/widget/focus/ScrollingThroughListOfFocusablesTest.java +++ b/core/tests/coretests/src/android/widget/focus/ScrollingThroughListOfFocusablesTest.java @@ -20,10 +20,9 @@ import android.graphics.Rect; import android.test.InstrumentationTestCase; import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.MediumTest; +import android.util.InternalSelectionView; import android.view.KeyEvent; import android.widget.ListView; -import android.widget.focus.ListOfInternalSelectionViews; -import android.util.InternalSelectionView; /** @@ -51,6 +50,10 @@ public class ScrollingThroughListOfFocusablesTest extends InstrumentationTestCas mNumRowsPerItem, // 5 internally selectable rows per item mScreenHeightFactor)); // each item is 5 / 4 screen height tall mListView = mActivity.getListView(); + // Make sure we have some fading edge regardless of ListView style. + mListView.setVerticalFadingEdgeEnabled(true); + mListView.setFadingEdgeLength(10); + ensureNotInTouchMode(); } @Override @@ -67,12 +70,12 @@ public class ScrollingThroughListOfFocusablesTest extends InstrumentationTestCas assertEquals(mNumRowsPerItem, mActivity.getNumRowsPerItem()); } - // TODO: needs to be adjusted to pass on non-HVGA displays - // @MediumTest + @MediumTest public void testScrollingDownInFirstItem() throws Exception { for (int i = 0; i < mNumRowsPerItem; i++) { assertEquals(0, mListView.getSelectedItemPosition()); + InternalSelectionView view = mActivity.getSelectedView(); assertInternallySelectedRowOnScreen(view, i); @@ -90,13 +93,12 @@ public class ScrollingThroughListOfFocusablesTest extends InstrumentationTestCas mListView.getSelectedView(); // 1 pixel tolerance in case height / 4 is not an even number - final int fadingEdge = mListView.getBottom() - mListView.getVerticalFadingEdgeLength(); + final int bottomFadingEdgeTop = + mListView.getBottom() - mListView.getVerticalFadingEdgeLength(); assertTrue("bottom of view should be just above fading edge", - view.getBottom() >= fadingEdge - 1 && - view.getBottom() <= fadingEdge); + view.getBottom() == bottomFadingEdgeTop); } - // make sure fading edge is the expected view { assertEquals("should be a second view visible due to the fading edge", @@ -109,7 +111,6 @@ public class ScrollingThroughListOfFocusablesTest extends InstrumentationTestCas } } - @MediumTest public void testScrollingToSecondItem() throws Exception { @@ -223,4 +224,12 @@ public class ScrollingThroughListOfFocusablesTest extends InstrumentationTestCas assertTrue("bottom of row " + row + " should be on sreen", mTempRect.bottom < mActivity.getScreenHeight()); } + + private void ensureNotInTouchMode() { + // If in touch mode inject a DPAD down event to exit that mode. + if (mListView.isInTouchMode()) { + sendKeys(KeyEvent.KEYCODE_DPAD_DOWN); + getInstrumentation().waitForIdleSync(); + } + } } diff --git a/docs/html/design/get-started/ui-overview.html b/docs/html/design/get-started/ui-overview.html index b08c7430827b..bd5ff9c84bac 100644 --- a/docs/html/design/get-started/ui-overview.html +++ b/docs/html/design/get-started/ui-overview.html @@ -153,12 +153,12 @@ recently used app at the bottom.</p> </div> </div> -<h2>UI Bars</h2> +<h2>System Bars</h2> -<p>The UI bars are screen areas dedicated to the display of notifications, communication of device -status, and device navigation. Typically the UI bars are displayed concurrently with your app. Apps -that display immersive content, such as movies or images, can temporarily hide the UI bars to allow -the user to enjoy full screen content without distraction.</p> +<p>The system bars are screen areas dedicated to the display of notifications, communication of device +status, and device navigation. Typically the system bars are displayed concurrently with your app. +Apps that display immersive content, such as movies or images, can temporarily hide the system bars +to allow the user to enjoy full screen content without distraction.</p> <img src="../static/content/ui_overview_system_ui.png"> @@ -177,8 +177,9 @@ the user to enjoy full screen content without distraction.</p> Recents, and also displays a menu for apps written for Android 2.3 or earlier.</p> </li> <li> -<h4>System Bar</h4> -<p>Combines the status and navigation bars for display on tablet form factors.</p> +<h4>Combined Bar</h4> +<p>On tablet form factors the status and navigation bars are combined into a single bar at the + bottom of the screen.</p> </li> </ol> diff --git a/docs/html/design/patterns/app-structure.html b/docs/html/design/patterns/app-structure.html index b87f4021879e..fb9205b4bf81 100644 --- a/docs/html/design/patterns/app-structure.html +++ b/docs/html/design/patterns/app-structure.html @@ -159,9 +159,9 @@ layouts that are visually engaging and appropriate for the data type and screen <img src="../static/content/app_structure_market.png"> <div class="figure-caption"> - Market's start screen primarily allows navigation into the stores for Apps, Music, Books, and - Games. It is also enriched with tailored recommendations and promotions that surface content - of interest to the user. Search is readily available from the action bar. + Market's start screen primarily allows navigation into the stores for Apps, Music, Books, + Movies and Games. It is also enriched with tailored recommendations and promotions that + surface content of interest to the user. Search is readily available from the action bar. </div> </div> diff --git a/docs/html/design/patterns/navigation.html b/docs/html/design/patterns/navigation.html index aabfc39c8373..cad36828ada6 100644 --- a/docs/html/design/patterns/navigation.html +++ b/docs/html/design/patterns/navigation.html @@ -131,7 +131,7 @@ return the user to the Home screen, or even to a different application.</p> <p>The Back key also supports a few behaviors not directly tied to screen-to-screen navigation:</p> <ul> <li>Back dismisses floating windows (dialogs, popups)</li> -<li>Back dismisses contextual action bars, and remove highlight from selected items</li> +<li>Back dismisses contextual action bars, and removes the highlight from the selected items</li> <li>Back hides the onscreen keyboard (IME)</li> </ul> <h2>Navigation Within Your App</h2> @@ -189,18 +189,19 @@ screen widget can bypass the Inbox screen, taking the user directly to a convers <h4>App-to-app navigation</h4> <p>When navigating deep into your app's hierarchy directly from another app via an intent, Back will return to the referring app.</p> -<p>The Up button is handled is follows: +<p>The Up button is handled as follows: - If the destination screen is typically reached from one particular screen within your app, Up should navigate to that screen. - Otherwise, Up should navigate to the topmost ("Home") screen of your app.</p> -<p>For example, after choosing to share a book being view in Market, the user navigates directly to the -Gmail's compose screen. From there, Up returns to the Inbox (which happens to be both the typical -referrer to compose, as well as the topmost screen of the app), while Back returns to Market.</p> +<p>For example, after choosing to share a book being viewed in Market, the user navigates directly to +Gmail's compose screen. From there, Up returns to the Inbox (which happens to be both the +typical referrer to compose, as well as the topmost screen of the app), while Back returns to +Market.</p> <img src="../static/content/navigation_from_outside_up.png"> <h4>System-to-app navigation</h4> -<p>If the your app was reached via the system mechanisms of notifications or home screen widgets, Up +<p>If your app was reached via the system mechanisms of notifications or home screen widgets, Up behaves as described for app-to-app navigation, above.</p> <p>For the Back key, you should make navigation more predictably by inserting into the task's back stack the complete upward navigation path to the app's topmost screen. This way, a user who has diff --git a/docs/html/design/patterns/notifications.html b/docs/html/design/patterns/notifications.html index acec30647263..c5045aed9e77 100644 --- a/docs/html/design/patterns/notifications.html +++ b/docs/html/design/patterns/notifications.html @@ -200,7 +200,7 @@ stacked (see <em>Stacked notifications</em> below) and references multiple items the user is taken to a hierarchy level below your app's top-level, insert navigation into your app's back stack to allow them to navigate to your app's top level using the system back key. For more information, see the chapter on <em>System-to-app navigation</em> in the -<a href="../patterns/notifications.html">Navigation</a> design pattern.</p> +<a href="../patterns/navigation.html">Navigation</a> design pattern.</p> <h4>Timestamps for time sensitive events</h4> <p>By default, standard Android notifications include a timestamp in the upper right corner. Consider whether the timestamp is valuable in the context of your notification. If the timestamp is not diff --git a/docs/html/design/static/download/action_bar_icons-v4.0.zip b/docs/html/design/static/download/action_bar_icons-v4.0.zip Binary files differnew file mode 100644 index 000000000000..e255931f9765 --- /dev/null +++ b/docs/html/design/static/download/action_bar_icons-v4.0.zip diff --git a/docs/html/guide/guide_toc.cs b/docs/html/guide/guide_toc.cs index ee4c48ecd846..4e5badd695ef 100644 --- a/docs/html/guide/guide_toc.cs +++ b/docs/html/guide/guide_toc.cs @@ -87,10 +87,24 @@ <span class="en">Content Providers</span> </a></div> <ul> - <li><a href="<?cs var:toroot ?>guide/topics/providers/calendar-provider.html"> - <span class="en">Calendar Provider</span></a> - <span class="new">new!</span> - </li> + <li> + <a href="<?cs var:toroot ?>guide/topics/providers/content-provider-basics.html"> + <span class="en">Content Provider Basics</span> + </a> + <span class="new">new!</span> + </li> + <li> + <a href="<?cs var:toroot ?>guide/topics/providers/content-provider-creating.html"> + <span class="en">Creating a Content Provider</span> + </a> + <span class="new">new!</span> + </li> + <li> + <a href="<?cs var:toroot ?>guide/topics/providers/calendar-provider.html"> + <span class="en">Calendar Provider</span> + </a> + <span class="new">new!</span> + </li> </ul> </li> <li><a href="<?cs var:toroot ?>guide/topics/intents/intents-filters.html"> diff --git a/docs/html/guide/practices/ui_guidelines/icon_design.jd b/docs/html/guide/practices/ui_guidelines/icon_design.jd index 96aecf5b868b..1c6618576f13 100644 --- a/docs/html/guide/practices/ui_guidelines/icon_design.jd +++ b/docs/html/guide/practices/ui_guidelines/icon_design.jd @@ -42,8 +42,6 @@ Templates Pack, v4.0 »</a></li> Templates Pack, v2.3 »</a></li> <li><a href="{@docRoot}shareables/icon_templates-v2.0.zip">Android Icon Templates Pack, v2.0 »</a></li> -<li><a href="{@docRoot}shareables/icon_templates-v1.0.zip">Android Icon -Templates Pack, v1.0 »</a></li> </ol> <h2>See also</h2> diff --git a/docs/html/guide/topics/providers/content-provider-basics.jd b/docs/html/guide/topics/providers/content-provider-basics.jd new file mode 100644 index 000000000000..40b5c3fd4c7b --- /dev/null +++ b/docs/html/guide/topics/providers/content-provider-basics.jd @@ -0,0 +1,1215 @@ +page.title=Content Provider Basics +@jd:body +<div id="qv-wrapper"> +<div id="qv"> + + + <!-- In this document --> +<h2>In this document</h2> +<ol> + <li> + <a href="#Basics">Overview</a> + <ol> + <li> + <a href="#ClientProvider">Accessing a provider</a> + </li> + <li> + <a href="#ContentURIs">Content URIs</a> + </li> + </ol> + </li> + <li> + <a href="#SimpleQuery">Retrieving Data from the Provider</a> + <ol> + <li> + <a href="#RequestPermissions">Requesting read access permission</a> + </li> + <li> + <a href="#Query">Constructing the query</a> + </li> + <li> + <a href="#DisplayResults">Displaying query results</a> + </li> + <li> + <a href="#GettingResults">Getting data from query results</a> + </li> + </ol> + </li> + <li> + <a href="#Permissions">Content Provider Permissions</a> + </li> + <li> + <a href="#Modifications">Inserting, Updating, and Deleting Data</a> + <ol> + <li> + <a href="#Inserting">Inserting data</a> + </li> + <li> + <a href="#Updating">Updating data</a> + </li> + <li> + <a href="#Deleting">Deleting data</a> + </li> + </ol> + </li> + <li> + <a href="#DataTypes">Provider Data Types</a> + </li> + <li> + <a href="#AltForms">Alternative Forms of Provider Access</a> + <ol> + <li> + <a href="#Batch">Batch access</a> + </li> + <li> + <a href="#Intents">Data access via intents</a> + </li> + </ol> + </li> + <li> + <a href="#ContractClasses">Contract Classes</a> + </li> + <li> + <a href="#MIMETypeReference">MIME Type Reference</a> + </li> +</ol> + + <!-- Key Classes --> +<h2>Key classes</h2> + <ol> + <li> + {@link android.content.ContentProvider} + </li> + <li> + {@link android.content.ContentResolver} + </li> + <li> + {@link android.database.Cursor} + </li> + <li> + {@link android.net.Uri} + </li> + </ol> + + <!-- Related Samples --> +<h2>Related Samples</h2> + <ol> + <li> + <a + href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/view/List2.html"> + Cursor (People)</a> + </li> + <li> + <a + href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/view/List7.html"> + Cursor (Phones)</a> + </li> + </ol> + + <!-- See also --> +<h2>See also</h2> + <ol> + <li> + <a href="{@docRoot}guide/topics/providers/content-provider-creating.html"> + Creating a Content Provider</a> + </li> + <li> + <a href="{@docRoot}guide/topics/providers/calendar-provider.html"> + Calendar Provider</a> + </li> + </ol> +</div> +</div> + + <!-- Intro paragraphs --> +<p> + A content provider manages access to a central repository of data. The provider and + is part of an Android application, which often provides its own UI for working with + the data. However, content providers are primarily intended to be used by other + applications, which access the provider using a provider client object. Together, providers + and provider clients offer a consistent, standard interface to data that also handles + inter-process communication and secure data access. +</p> +<p> + This topic describes the basics of the following: +</p> + <ul> + <li>How content providers work.</li> + <li>The API you use retrieve data from a content provider.</li> + <li>The API you use to insert, update, or delete data in a content provider.</li> + <li>Other API features that facilitate working with providers.</li> + </ul> + + <!-- Basics --> +<h2 id="Basics">Overview</h2> +<p> + A content provider presents data to external applications as one or more tables that are + similar to the tables found in a relational database. A row represents an instance of some type + of data the provider collects, and each row in the column represents an individual piece of + data collected for an instance. +</p> +<p> + For example, one of the built-in providers in the Android platform is the user dictionary, which + stores the spellings of non-standard words that the user wants to keep. Table 1 illustrates what + the data might look like in this provider's table: +</p> +<p class="table-caption"> + <strong>Table 1:</strong> Sample user dictionary table. +</p> +<table id="table1" style="width: 50%;"> + <tr> + <th style="width:20%" align="center" scope="col">word</th> + <th style="width:20%" align="center" scope="col">app id</th> + <th style="width:20%" align="center" scope="col">frequency</th> + <th style="width:20%" align="center" scope="col">locale</th> + <th style="width:20%" align="center" scope="col">_ID</th> + </tr> + <tr> + <td align="center" scope="row">mapreduce</td> + <td align="center">user1</td> + <td align="center">100</td> + <td align="center">en_US</td> + <td align="center">1</td> + </tr> + <tr> + <td align="center" scope="row">precompiler</td> + <td align="center">user14</td> + <td align="center">200</td> + <td align="center">fr_FR</td> + <td align="center">2</td> + </tr> + <tr> + <td align="center" scope="row">applet</td> + <td align="center">user2</td> + <td align="center">225</td> + <td align="center">fr_CA</td> + <td align="center">3</td> + </tr> + <tr> + <td align="center" scope="row">const</td> + <td align="center">user1</td> + <td align="center">255</td> + <td align="center">pt_BR</td> + <td align="center">4</td> + </tr> + <tr> + <td align="center" scope="row">int</td> + <td align="center">user5</td> + <td align="center">100</td> + <td align="center">en_UK</td> + <td align="center">5</td> + </tr> +</table> +<p> + In table 1, each row represents an instance of a word that might not be + found in a standard dictionary. Each column represents some data for that word, such as the + locale in which it was first encountered. The column headers are column names that are stored in + the provider. To refer to a row's locale, you refer to its <code>locale</code> column. For + this provider, the <code>_ID</code> column serves as a "primary key" column that + the provider automatically maintains. +</p> +<p class="note"> + <strong>Note:</strong> A provider isn't required to have a primary key, and it isn't required + to use <code>_ID</code> as the column name of a primary key if one is present. However, + if you want to bind data from a provider to a {@link android.widget.ListView}, one of the + column names has to be <code>_ID</code>. This requirement is explained in more detail in the + section <a href="#DisplayResults">Displaying query results</a>. +</p> +<h3 id="ClientProvider">Accessing a provider</h3> +<p> + An application accesses the data from a content provider with + a {@link android.content.ContentResolver} client object. This object has methods that call + identically-named methods in the provider object, an instance of one of the concrete + subclasses of {@link android.content.ContentProvider}. The + {@link android.content.ContentResolver} methods provide the basic + "CRUD" (create, retrieve, update, and delete) functions of persistent storage. +</p> +<p> + The {@link android.content.ContentResolver} object in the client application's + process and the {@link android.content.ContentProvider} object in the application that owns + the provider automatically handle inter-process communication. + {@link android.content.ContentProvider} also acts as an abstraction layer between its + repository of data and the external appearance of data as tables. +</p> +<p class="note"> + <strong>Note:</strong> To access a provider, your application usually has to request specific + permissions in its manifest file. This is described in more detail in the section + <a href="#Permissions">Content Provider Permissions</a> +</p> +<p> + For example, to get a list of the words and their locales from the User Dictionary Provider, + you call {@link android.content.ContentResolver#query(Uri, String[], String, String[], String) + ContentResolver.query()}. + The {@link android.content.ContentResolver#query(Uri, String[], String, String[], String) + query()} method calls the + {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) + ContentProvider.query()} method defined by the User Dictionary Provider. The following lines + of code show a + {@link android.content.ContentResolver#query(Uri, String[], String, String[], String) + ContentResolver.query()} call: +<p> +<pre> +// Queries the user dictionary and returns results +mCursor = getContentResolver().query( + UserDictionary.Words.CONTENT_URI, // The content URI of the words table + mProjection, // The columns to return for each row + mSelectionClause // Selection criteria + mSelectionArgs, // Selection criteria + mSortOrder); // The sort order for the returned rows +</pre> +<p> + Table 2 shows how the arguments to + {@link android.content.ContentResolver#query(Uri, String[], String, String[], String) + query(Uri,projection,selection,selectionArgs,sortOrder)} match an SQL SELECT statement: +</p> +<p class="table-caption"> + <strong>Table 2:</strong> Query() compared to SQL query. +</p> +<table id="table2" style="width: 75%;"> + <tr> + <th style="width:25%" align="center" scope="col">query() argument</th> + <th style="width:25%" align="center" scope="col">SELECT keyword/parameter</th> + <th style="width:50%" align="center" scope="col">Notes</th> + </tr> + <tr> + <td align="center"><code>Uri</code></td> + <td align="center"><code>FROM <em>table_name</em></code></td> + <td><code>Uri</code> maps to the table in the provider named <em>table_name</em>.</td> + </tr> + <tr> + <td align="center"><code>projection</code></td> + <td align="center"><code><em>col,col,col,...</em></code></td> + <td> + <code>projection</code> is an array of columns that should be included for each row + retrieved. + </td> + </tr> + <tr> + <td align="center"><code>selection</code></td> + <td align="center"><code>WHERE <em>col</em> = <em>value</em></code></td> + <td><code>selection</code> specifies the criteria for selecting rows.</td> + </tr> + <tr> + <td align="center"><code>selectionArgs</code></td> + <td align="center"> + (No exact equivalent. Selection arguments replace <code>?</code> placeholders in the + selection clause.) + </td> + </tr> + <tr> + <td align="center"><code>sortOrder</code></td> + <td align="center"><code>ORDER BY <em>col,col,...</em></code></td> + <td> + <code>sortOrder</code> specifies the order in which rows appear in the returned + {@link android.database.Cursor}. + </td> + </tr> +</table> +<h3 id="ContentURIs">Content URIs</h3> +<p> + A <strong>content URI</strong> is a URI that identifies data in a provider. Content URIs + include the symbolic name of the entire provider (its <strong>authority</strong>) and a + name that points to a table (a <strong>path</strong>). When you call + a client method to access a table in a provider, the content URI for the table is one of + the arguments. +</p> +<p> + In the preceding lines of code, the constant + {@link android.provider.UserDictionary.Words#CONTENT_URI} contains the content URI of + the user dictionary's "words" table. The {@link android.content.ContentResolver} + object parses out the URI's authority, and uses it to "resolve" the provider by + comparing the authority to a system table of known providers. The + {@link android.content.ContentResolver} can then dispatch the query arguments to the correct + provider. +</p> +<p> + The {@link android.content.ContentProvider} uses the path part of the content URI to choose the + table to access. A provider usually has a <strong>path</strong> for each table it exposes. +</p> +<p> + In the previous lines of code, the full URI for the "words" table is: +</p> +<pre> +content://user_dictionary/words +</pre> +<p> + where the <code>user_dictionary</code> string is the provider's authority, and + <code>words</code> string is the table's path. The string + <code>content://</code> (the <strong>scheme</strong>) is always present, + and identifies this as a content URI. +</p> +<p> + Many providers allow you to access a single row in a table by appending an ID value + to the end of the URI. For example, to retrieve a row whose <code>_ID</code> is + <code>4</code> from user dictionary, you can use this content URI: +</p> +<pre> +Uri singleUri = ContentUri.withAppendedId(UserDictionary.Words.CONTENT_URI,4); +</pre> +<p> + You often use id values when you've retrieved a set of rows and then want to update or delete + one of them. +</p> +<p class="note"> + <strong>Note:</strong> The {@link android.net.Uri} and {@link android.net.Uri.Builder} classes + contain convenience methods for constructing well-formed Uri objects from strings. The + {@link android.content.ContentUris} contains convenience methods for appending id values to + a URI. The previous snippet uses {@link android.content.ContentUris#withAppendedId(Uri, long) + withAppendedId()} to append an id to the UserDictionary content URI. +</p> + + + <!-- Retrieving Data from the Provider --> +<h2 id="SimpleQuery">Retrieving Data from the Provider</h2> +<p> + This section describes how to retrieve data from a provider, using the User Dictionary Provider + as an example. +</p> +<p class="note"> + For the sake of clarity, the code snippets in this section call + {@link android.content.ContentResolver#query(Uri, String[], String, String[], String) + ContentResolver.query()} on the "UI thread"". In actual code, however, you should + do queries asynchronously on a separate thread. One way to do this is to use the + {@link android.content.CursorLoader} class, which is described + in more detail in the <a href="{@docRoot}guide/topics/fundamentals/loaders.html"> + Loaders</a> guide. Also, the lines of code are snippets only; they don't show a complete + application. +</p> +<p> + To retrieve data from a provider, follow these basic steps: +</p> +<ol> + <li> + Request the read access permission for the provider. + </li> + <li> + Define the code that sends a query to the provider. + </li> +</ol> +<h3 id="RequestPermissions">Requesting read access permission</h3> +<p> + To retrieve data from a provider, your application needs "read access permission" for the + provider. You can't request this permission at run-time; instead, you have to specify that + you need this permission in your manifest, using the + <code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"> + <uses-permission></a></code> element and the exact permission name defined by the + provider. When you specify this element in your manifest, you are in effect "requesting" this + permission for your application. When users install your application, they implicitly grant + this request. +</p> +<p> + To find the exact name of the read access permission for the provider you're using, as well + as the names for other access permissions used by the provider, look in the provider's + documentation. +</p> +<p> + The role of permissions in accessing providers is described in more detail in the section + <a href="#Permissions">Content Provider Permissions</a>. +</p> +<p> + The User Dictionary Provider defines the permission + <code>android.permission.READ_USER_DICTIONARY</code> in its manifest file, so an + application that wants to read from the provider must request this permission. +</p> +<!-- Constructing the query --> +<h3 id="Query">Constructing the query</h3> +<p> + The next step in retrieving data a provider is to construct a query. This first snippet + defines some variables for accessing the User Dictionary Provider: +</p> +<pre class="prettyprint"> + +// A "projection" defines the columns that will be returned for each row +String[] mProjection = +{ + UserDictionary.Words._ID, // Contract class constant for the _ID column name + UserDictionary.Words.WORD, // Contract class constant for the word column name + UserDictionary.Words.LOCALE // Contract class constant for the locale column name +}; + +// Defines a string to contain the selection clause +String mSelectionClause = null; + +// Initializes an array to contain selection arguments +String[] mSelectionArgs = {""}; + +</pre> +<p> + The next snippet shows how to use + {@link android.content.ContentResolver#query(Uri, String[], String, String[], String) + ContentResolver.query()}, using the User Dictionary Provider as an example. + A provider client query is similar to an SQL query, and it contains a set of columns to return, + a set of selection criteria, and a sort order. +</p> +<p> + The set of columns that the query should return is called a <strong>projection</strong> + (the variable <code>mProjection</code>). +</p> +<p> + The expression that specifies the rows to retrieve is split into a selection clause and + selection arguments. The selection clause is a combination of logical and Boolean expressions, + column names, and values (the variable <code>mSelection</code>). If you specify the replaceable + parameter <code>?</code> instead of a value, the query method retrieves the value from the + selection arguments array (the variable <code>mSelectionArgs</code>). +</p> +<p> + In the next snippet, if the user doesn't enter a word, the selection clause is set to + <code>null</code>, and the query returns all the words in the provider. If the user enters + a word, the selection clause is set to <code>UserDictionary.Words.Word + " = ?"</code> and + the first element of selection arguments array is set to the word the user enters. +</p> +<pre class="prettyprint"> +/* + * This defines a one-element String array to contain the selection argument. + */ +String[] mSelectionArgs = {""}; + +// Gets a word from the UI +mSearchString = mSearchWord.getText().toString(); + +// Remember to insert code here to check for invalid or malicious input. + +// If the word is the empty string, gets everything +if (TextUtils.isEmpty(mSearchString)) { + // Setting the selection clause to null will return all words + mSelectionClause = null; + mSelectionArgs[0] = ""; + +} else { + // Constructs a selection clause that matches the word that the user entered. + mSelectionClause = " = ?"; + + // Moves the user's input string to the selection arguments. + mSelectionArgs[0] = mSearchString; + +} + +// Does a query against the table and returns a Cursor object +mCursor = getContentResolver().query( + UserDictionary.Words.CONTENT_URI, // The content URI of the words table + mProjection, // The columns to return for each row + mSelectionClause // Either null, or the word the user entered + mSelectionArgs, // Either empty, or the string the user entered + mSortOrder); // The sort order for the returned rows + +// Some providers return null if an error occurs, others throw an exception +if (null == mCursor) { + /* + * Insert code here to handle the error. Be sure not to use the cursor! You may want to + * call android.util.Log.e() to log this error. + * + */ +// If the Cursor is empty, the provider found no matches +} else if (mCursor.getCount() < 1) { + + /* + * Insert code here to notify the user that the search was unsuccessful. This isn't necessarily + * an error. You may want to offer the user the option to insert a new row, or re-type the + * search term. + */ + +} else { + // Insert code here to do something with the results + +} +</pre> +<p> + This query is analogous to the SQL statement: +</p> +<pre> +SELECT _ID, word, frequency, locale FROM words WHERE word = <userinput> ORDER BY word ASC; +</pre> +<p> + In this SQL statement, the actual column names are used instead of contract class constants. +</p> +<h4 id="Injection">Protecting against malicious input</h4> +<p> + If the data managed by the content provider is in an SQL database, including external untrusted + data into raw SQL statements can lead to SQL injection. +</p> +<p> + Consider this selection clause: +</p> +<pre> +// Constructs a selection clause by concatenating the user's input to the column name +String mSelectionClause = "var = " + mUserInput; +</pre> +<p> + If you do this, you're allowing the user to concatenate malicious SQL onto your SQL statement. + For example, the user could enter "nothing; DROP TABLE *;" for <code>mUserInput</code>, which + would result in the selection clause <code>var = nothing; DROP TABLE *;</code>. Since the + selection clause is treated as an SQL statement, this might cause the provider to erase all of + the tables in the underlying SQLite database (unless the provider is set up to catch + <a href="http://en.wikipedia.org/wiki/SQL_injection">SQL injection</a> attempts). +</p> +<p> + To avoid this problem, use a selection clause that uses <code>?</code> as a replaceable + parameter and a separate array of selection arguments. When you do this, the user input + is bound directly to the query rather than being interpreted as part of an SQL statement. + Because it's not treated as SQL, the user input can't inject malicious SQL. Instead of using + concatenation to include the user input, use this selection clause: +</p> +<pre> +// Constructs a selection clause with a replaceable parameter +String mSelectionClause = "var = ?"; +</pre> +<p> + Set up the array of selection arguments like this: +</p> +<pre> +// Defines an array to contain the selection arguments +String[] selectionArgs = {""}; +</pre> +<p> + Put a value in the selection arguments array like this: +</p> +<pre> +// Sets the selection argument to the user's input +selectionArgs[0] = mUserInput; +</pre> +<p> + A selection clause that uses <code>?</code> as a replaceable parameter and an array of + selection arguments array are preferred way to specify a selection, even the provider isn't + based on an SQL database. +</p> +<!-- Displaying the results --> +<h3 id="DisplayResults">Displaying query results</h3> +<p> + The {@link android.content.ContentResolver#query(Uri, String[], String, String[], String) + ContentResolver.query()} client method always returns a {@link android.database.Cursor} + containing the columns specified by the query's projection for the rows that match the query's + selection criteria. A {@link android.database.Cursor} object provides random read access to the + rows and columns it contains. Using {@link android.database.Cursor} methods, + you can iterate over the rows in the results, determine the data type of each column, get the + data out of a column, and examine other properties of the results. Some + {@link android.database.Cursor} implementations automatically update the object when the + provider's data changes, or trigger methods in an observer object when the + {@link android.database.Cursor} changes, or both. +</p> +<p class="note"> + <strong>Note:</strong> A provider may restrict access to columns based on the nature of the + object making the query. For example, the Contacts Provider restricts access for some columns to + sync adapters, so it won't return them to an activity or service. +</p> +<p> + If no rows match the selection criteria, the provider + returns a {@link android.database.Cursor} object for which + {@link android.database.Cursor#getCount() Cursor.getCount()} is 0 (an empty cursor). +</p> +<p> + If an internal error occurs, the results of the query depend on the particular provider. It may + choose to return <code>null</code>, or it may throw an {@link java.lang.Exception}. +</p> +<p> + Since a {@link android.database.Cursor} is a "list" of rows, a good way to display the + contents of a {@link android.database.Cursor} is to link it to a {@link android.widget.ListView} + via a {@link android.widget.SimpleCursorAdapter}. +</p> +<p> + The following snippet continues the code from the previous snippet. It creates a + {@link android.widget.SimpleCursorAdapter} object containing the {@link android.database.Cursor} + retrieved by the query, and sets this object to be the adapter for a + {@link android.widget.ListView}: +</p> +<pre class="prettyprint"> +// Defines a list of columns to retrieve from the Cursor and load into an output row +String[] mWordListColumns = +{ + UserDictionary.Words.WORD, // Contract class constant containing the word column name + UserDictionary.Words.LOCALE // Contract class constant containing the locale column name +}; + +// Defines a list of View IDs that will receive the Cursor columns for each row +int[] mWordListItems = { R.id.dictWord, R.id.locale}; + +// Creates a new SimpleCursorAdapter +mCursorAdapter = new SimpleCursorAdapter( + getApplicationContext(), // The application's Context object + R.layout.wordlistrow, // A layout in XML for one row in the ListView + mCursor, // The result from the query + mWordListColumns, // A string array of column names in the cursor + mWordListItems, // An integer array of view IDs in the row layout + 0); // Flags (usually none are needed) + +// Sets the adapter for the ListView +mWordList.setAdapter(mCursorAdapter); +</pre> +<p class="note"> + <strong>Note:</strong> To back a {@link android.widget.ListView} with a + {@link android.database.Cursor}, the cursor must contain a column named <code>_ID</code>. + Because of this, the query shown previously retrieves the <code>_ID</code> column for the + "words" table, even though the {@link android.widget.ListView} doesn't display it. + This restriction also explains why most providers have a <code>_ID</code> column for each of + their tables. +</p> + + <!-- Getting data from query results --> +<h3 id="GettingResults">Getting data from query results</h3> +<p> + Rather than simply displaying query results, you can use them for other tasks. For + example, you can retrieve spellings from the user dictionary and then look them up in + other providers. To do this, you iterate over the rows in the {@link android.database.Cursor}: +</p> +<pre class="prettyprint"> + +// Determine the column index of the column named "word" +int index = mCursor.getColumnIndex(UserDictionary.Words.WORD); + +/* + * Only executes if the cursor is valid. The User Dictionary Provider returns null if + * an internal error occurs. Other providers may throw an Exception instead of returning null. + */ + +if (mCursor != null) { + /* + * Moves to the next row in the cursor. Before the first movement in the cursor, the + * "row pointer" is -1, and if you try to retrieve data at that position you will get an + * exception. + */ + while (mCursor.moveToNext()) { + + // Gets the value from the column. + newWord = mCursor.getString(index); + + // Insert code here to process the retrieved word. + + ... + + // end of while loop + } +} else { + + // Insert code here to report an error if the cursor is null or the provider threw an exception. +} +</pre> +<p> + {@link android.database.Cursor} implementations contain several "get" methods for + retrieving different types of data from the object. For example, the previous snippet + uses {@link android.database.Cursor#getString(int) getString()}. They also have a + {@link android.database.Cursor#getType(int) getType()} method that returns a value indicating + the data type of the column. +</p> + + + <!-- Requesting permissions --> +<h2 id="Permissions">Content Provider Permissions</h2> +<p> + A provider's application can specify permissions that other applications must have in order to + access the provider's data. These permissions ensure that the user knows what data + an application will try to access. Based on the provider's requirements, other applications + request the permissions they need in order to access the provider. End users see the requested + permissions when they install the application. +</p> +<p> + If a provider's application doesn't specify any permissions, then other applications have no + access to the provider's data. However, components in the provider's application always have + full read and write access, regardless of the specified permissions. +</p> +<p> + As noted previously, the User Dictionary Provider requires the + <code>android.permission.READ_USER_DICTIONARY</code> permission to retrieve data from it. + The provider has the separate <code>android.permission.WRITE_USER_DICTIONARY</code> + permission for inserting, updating, or deleting data. +</p> +<p> + To get the permissions needed to access a provider, an application requests them with a + <code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"> + <uses-permission></a></code> element in its manifest file. + When the Android Package Manager installs the application, a user must approve all of the + permissions the application requests. If the user approves all of them, Package Manager + continues the installation; if the user doesn't approve them, Package Manager + aborts the installation. +</p> +<p> + The following + <code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"> + <uses-permission></a></code> element requests read access to the User Dictionary Provider: +</p> +<pre> + <uses-permission android:name="android.permission.READ_USER_DICTIONARY"> +</pre> +<p> + The impact of permissions on provider access is explained in more detail in the + <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a> guide. +</p> + + +<!-- Inserting, Updating, and Deleting Data --> +<h2 id="Modifications">Inserting, Updating, and Deleting Data</h2> +<p> + In the same way that you retrieve data from a provider, you also use the interaction between + a provider client and the provider's {@link android.content.ContentProvider} to modify data. + You call a method of {@link android.content.ContentResolver} with arguments that are passed to + the corresponding method of {@link android.content.ContentProvider}. The provider and provider + client automatically handle security and inter-process communication. +</p> +<h3 id="Inserting">Inserting data</h3> +<p> + To insert data into a provider, you call the + {@link android.content.ContentResolver#insert(Uri,ContentValues) ContentResolver.insert()} + method. This method inserts a new row into the provider and returns a content URI for that row. + This snippet shows how to insert a new word into the User Dictionary Provider: +</p> +<pre class="prettyprint"> +// Defines a new Uri object that receives the result of the insertion +Uri mNewUri; + +... + +// Defines an object to contain the new values to insert +ContentValues mNewValues = new ContentValues(); + +/* + * Sets the values of each column and inserts the word. The arguments to the "put" + * method are "column name" and "value" + */ +mNewValues.put(UserDictionary.Words.APP_ID, "example.user"); +mNewValues.put(UserDictionary.Words.LOCALE, "en_US"); +mNewValues.put(UserDictionary.Words.WORD, "insert"); +mNewValues.put(UserDictionary.Words.FREQUENCY, "100"); + +mNewUri = getContentResolver().insert( + UserDictionary.Word.CONTENT_URI, // the user dictionary content URI + mNewValues // the values to insert +); +</pre> +<p> + The data for the new row goes into a single {@link android.content.ContentValues} object, which + is similar in form to a one-row cursor. The columns in this object don't need to have the + same data type, and if you don't want to specify a value at all, you can set a column + to <code>null</code> using {@link android.content.ContentValues#putNull(String) + ContentValues.putNull()}. +</p> +<p> + The snippet doesn't add the <code>_ID</code> column, because this column is maintained + automatically. The provider assigns a unique value of <code>_ID</code> to every row that is + added. Providers usually use this value as the table's primary key. +</p> +<p> + The content URI returned in <code>newUri</code> identifies the newly-added row, with + the following format: +</p> +<pre> +content://user_dictionary/words/<id_value> +</pre> +<p> + The <code><id_value></code> is the contents of <code>_ID</code> for the new row. + Most providers can detect this form of content URI automatically and then perform the requested + operation on that particular row. +</p> +<p> + To get the value of <code>_ID</code> from the returned {@link android.net.Uri}, call + {@link android.content.ContentUris#parseId(Uri) ContentUris.parseId()}. +</p> +<h3 id="Updating">Updating data</h3> +<p> + To update a row, you use a {@link android.content.ContentValues} object with the updated + values just as you do with an insertion, and selection criteria just as you do with a query. + The client method you use is + {@link android.content.ContentResolver#update(Uri, ContentValues, String, String[]) + ContentResolver.update()}. You only need to add values to the + {@link android.content.ContentValues} object for columns you're updating. If you want to clear + the contents of a column, set the value to <code>null</code>. +</p> +<p> + The following snippet changes all the rows whose locale has the language "en" to a + have a locale of <code>null</code>. The return value is the number of rows that were updated: +</p> +<pre> +// Defines an object to contain the updated values +ContentValues mUpdateValues = new ContentValues(); + +// Defines selection criteria for the rows you want to update +String mSelectionClause = UserDictionary.Words.LOCALE + "LIKE ?"; +String[] mSelectionArgs = {"en_%"}; + +// Defines a variable to contain the number of updated rows +int mRowsUpdated = 0; + +... + +/* + * Sets the updated value and updates the selected words. + */ +mUpdateValues.putNull(UserDictionary.Words.LOCALE); + +mRowsUpdated = getContentResolver().update( + UserDictionary.Words.CONTENT_URI, // the user dictionary content URI + mUpdateValues // the columns to update + mSelectionClause // the column to select on + mSelectionArgs // the value to compare to +); +</pre> +<p> + You should also sanitize user input when you call + {@link android.content.ContentResolver#update(Uri, ContentValues, String, String[]) + ContentResolver.update()}. To learn more about this, read the section + <a href="#Injection">Protecting against malicious input</a>. +</p> +<h3 id="Deleting">Deleting data</h3> +<p> + Deleting rows is similar to retrieving row data: you specify selection criteria for the rows + you want to delete and the client method returns the number of deleted rows. + The following snippet deletes rows whose appid matches "user". The method returns the + number of deleted rows. +</p> +<pre> + +// Defines selection criteria for the rows you want to delete +String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?"; +String[] mSelectionArgs = {"user"}; + +// Defines a variable to contain the number of rows deleted +int mRowsDeleted = 0; + +... + +// Deletes the words that match the selection criteria +mRowsDeleted = getContentResolver().delete( + UserDictionary.Words.CONTENT_URI, // the user dictionary content URI + mSelectionClause // the column to select on + mSelectionArgs // the value to compare to +); +</pre> +<p> + You should also sanitize user input when you call + {@link android.content.ContentResolver#delete(Uri, String, String[]) + ContentResolver.delete()}. To learn more about this, read the section + <a href="#Injection">Protecting against malicious input</a>. +</p> +<!-- Provider Data Types --> +<h2 id="DataTypes">Provider Data Types</h2> +<p> + Content providers can offer many different data types. The User Dictionary Provider offers only + text, but providers can also offer the following formats: +</p> + <ul> + <li> + integer + </li> + <li> + long integer (long) + </li> + <li> + floating point + </li> + <li> + long floating point (double) + </li> + </ul> +<p> + Another data type that providers often use is Binary Large OBject (BLOB) implemented as a + 64KB byte array. You can see the available data types by looking at the + {@link android.database.Cursor} class "get" methods. +</p> +<p> + The data type for each column in a provider is usually listed in its documentation. + The data types for the User Dictionary Provider are listed in the reference documentation + for its contract class {@link android.provider.UserDictionary.Words} (contract classes are + described in the section <a href="#ContractClasses">Contract Classes</a>). + You can also determine the data type by calling {@link android.database.Cursor#getType(int) + Cursor.getType()}. +</p> +<p> + Providers also maintain MIME data type information for each content URI they define. You can + use the MIME type information to find out if your application can handle data that the + provider offers, or to choose a type of handling based on the MIME type. You usually need the + MIME type when you are working with a provider that contains complex + data structures or files. For example, the {@link android.provider.ContactsContract.Data} + table in the Contacts Provider uses MIME types to label the type of contact data stored in each + row. To get the MIME type corresponding to a content URI, call + {@link android.content.ContentResolver#getType(Uri) ContentResolver.getType()}. +</p> +<p> + The section <a href="#MIMETypeReference">MIME Type Reference</a> describes the + syntax of both standard and custom MIME types. +</p> + + +<!-- Alternative Forms of Provider Access --> +<h2 id="AltForms">Alternative Forms of Provider Access</h2> +<p> + Three alternative forms of provider access are important in application development: +</p> +<ul> + <li> + <a href="#Batch">Batch access</a>: You can create a batch of access calls with methods in + the {@link android.content.ContentProviderOperation} class, and then apply them with + {@link android.content.ContentResolver#applyBatch(String, ArrayList) + ContentResolver.applyBatch()}. + </li> + <li> + Asynchronous queries: You should do queries in a separate thread. One way to do this is to + use a {@link android.content.CursorLoader} object. The examples in the + <a href="{@docRoot}guide/topics/fundamentals/loaders.html">Loaders</a> guide demonstrate + how to do this. + </li> + <li> + <a href="#Intents">Data access via intents</a>: Although you can't send an intent + directly to a provider, you can send an intent to the provider's application, which is + usually the best-equipped to modify the provider's data. + </li> +</ul> +<p> + Batch access and modification via intents are described in the following sections. +</p> +<h3 id="Batch">Batch access</h3> +<p> + Batch access to a provider is useful for inserting a large number of rows, or for inserting + rows in multiple tables in the same method call, or in general for performing a set of + operations across process boundaries as a transaction (an atomic operation). +</p> +<p> + To access a provider in "batch mode", + you create an array of {@link android.content.ContentProviderOperation} objects and then + dispatch them to a content provider with + {@link android.content.ContentResolver#applyBatch(String, ArrayList) + ContentResolver.applyBatch()}. You pass the content provider's <em>authority</em> to this + method, rather than a particular content URI, which allows each + {@link android.content.ContentProviderOperation} object in the array to work against a + different table. A call to {@link android.content.ContentResolver#applyBatch(String, ArrayList) + ContentResolver.applyBatch()} returns an array of results. +</p> +<p> + The description of the {@link android.provider.ContactsContract.RawContacts} contract class + includes a code snippet that demonstrates batch insertion. The + <a href="{@docRoot}resources/samples/ContactManager/index.html">Contact Manager</a> + sample application contains an example of batch access in its <code>ContactAdder.java</code> + source file. +</p> +<div class="sidebox-wrapper"> +<div class="sidebox"> +<h2>Displaying data using a helper app</h2> +<p> + If your application <em>does</em> have access permissions, you still may want to use an + intent to display data in another application. For example, the Calendar application accepts an + {@link android.content.Intent#ACTION_VIEW} intent, which displays a particular date or event. + This allows you to display calendar information without having to create your own UI. + To learn more about this feature, see the + <a href="{@docRoot}guide/topics/providers/calendar-provider.html">Calendar Provider</a> guide. +</p> +<p> + The application to which you send the intent doesn't have to be the application + associated with the provider. For example, you can retrieve a contact from the + Contact Provider, then send an {@link android.content.Intent#ACTION_VIEW} intent + containing the content URI for the contact's image to an image viewer. +</p> +</div> +</div> +<h3 id="Intents">Data access via intents</h3> +<p> + Intents can provide indirect access to a content provider. You allow the user to access + data in a provider even if your application doesn't have access permissions, either by + getting a result intent back from an application that has permissions, or by activating an + application that has permissions and letting the user do work in it. +</p> +<h4>Getting access with temporary permissions</h4> +<p> + You can access data in a content provider, even if you don't have the proper access + permissions, by sending an intent to an application that does have the permissions and + receiving back a result intent containing "URI" permissions. + These are permissions for a specific content URI that last until the activity that receives + them is finished. The application that has permanent permissions grants temporary + permissions by setting a flag in the result intent: +</p> +<ul> + <li> + <strong>Read permission:</strong> + {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION} + </li> + <li> + <strong>Write permission:</strong> + {@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION} + </li> +</ul> +<p class="note"> + <strong>Note:</strong> These flags don't give general read or write access to the provider + whose authority is contained in the content URI. The access is only for the URI itself. +</p> +<p> + A provider defines URI permissions for content URIs in its manifest, using the + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#gprmsn"> + android:grantUriPermission</a></code> + attribute of the + {@code <a href="guide/topics/manifest/provider-element.html"><provider></a>} + element, as well as the + {@code <a href="guide/topics/manifest/grant-uri-permission-element.html"> + <grant-uri-permission></a>} child element of the + {@code <a href="guide/topics/manifest/provider-element.html"><provider></a>} + element. The URI permissions mechanism is explained in more detail in the + <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a> guide, + in the section "URI Permissions". +</p> +<p> + For example, you can retrieve data for a contact in the Contacts Provider, even if you don't + have the {@link android.Manifest.permission#READ_CONTACTS} permission. You might want to do + this in an application that sends e-greetings to a contact on his or her birthday. Instead of + requesting {@link android.Manifest.permission#READ_CONTACTS}, which gives you access to all of + the user's contacts and all of their information, you prefer to let the user control which + contacts are used by your application. To do this, you use the following process: +</p> +<ol> + <li> + Your application sends an intent containing the action + {@link android.content.Intent#ACTION_PICK} and the "contacts" MIME type + {@link android.provider.ContactsContract.RawContacts#CONTENT_ITEM_TYPE}, using the + method {@link android.app.Activity#startActivityForResult(Intent, int) + startActivityForResult()}. + </li> + <li> + Because this intent matches the intent filter for the + People app's "selection" activity, the activity will come to the foreground. + </li> + <li> + In the selection activity, the user selects a + contact to update. When this happens, the selection activity calls + {@link android.app.Activity#setResult(int, Intent) setResult(resultcode, intent)} + to set up a intent to give back to your application. The intent contains the content URI + of the contact the user selected, and the "extras" flags + {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION}. These flags grant URI + permission to your app to read data for the contact pointed to by the + content URI. The selection activity then calls {@link android.app.Activity#finish()} to + return control to your application. + </li> + <li> + Your activity returns to the foreground, and the system calls your activity's + {@link android.app.Activity#onActivityResult(int, int, Intent) onActivityResult()} + method. This method receives the result intent created by the selection activity in + the People app. + </li> + <li> + With the content URI from the result intent, you can read the contact's data + from the Contacts Provider, even though you didn't request permanent read access permission + to the provider in your manifest. You can then get the contact's birthday information + or his or her email address and then send the e-greeting. + </li> +</ol> +<h4>Using another application</h4> +<p> + A simple way to allow the user to modify data to which you don't have access permissions is to + activate an application that has permissions and let the user do the work there. +</p> +<p> + For example, the Calendar application accepts an + {@link android.content.Intent#ACTION_INSERT} intent, which allows you to activate the + application's insert UI. You can pass "extras" data in this intent, which the application + uses to pre-populate the UI. Because recurring events have a complex syntax, the preferred + way of inserting events into the Calendar Provider is to activate the Calendar app with an + {@link android.content.Intent#ACTION_INSERT} and then let the user insert the event there. +</p> +<!-- Contract Classes --> +<h2 id="ContractClasses">Contract Classes</h2> +<p> + A contract class defines constants that help applications work with the content URIs, column + names, intent actions, and other features of a content provider. Contract classes are not + included automatically with a provider; the provider's developer has to define them and then + make them available to other developers. Many of the providers included with the Android + platform have corresponding contract classes in the package {@link android.provider}. +</p> +<p> + For example, the User Dictionary Provider has a contract class + {@link android.provider.UserDictionary} containing content URI and column name constants. The + content URI for the "words" table is defined in the constant + {@link android.provider.UserDictionary.Words#CONTENT_URI UserDictionary.Words.CONTENT_URI}. + The {@link android.provider.UserDictionary.Words} class also contains column name constants, + which are used in the example snippets in this guide. For example, a query projection can be + defined as: +</p> +<pre> +String[] mProjection = +{ + UserDictionary.Words._ID, + UserDictionary.Words.WORD, + UserDictionary.Words.LOCALE +}; +</pre> +<p> + Another contract class is {@link android.provider.ContactsContract} for the Contacts Provider. + The reference documentation for this class includes example code snippets. One of its + subclasses, {@link android.provider.ContactsContract.Intents.Insert}, is a contract + class that contains constants for intents and intent data. +</p> + + +<!-- MIME Type Reference --> +<h2 id="MIMETypeReference">MIME Type Reference</h2> +<p> + Content providers can return standard MIME media types, or custom MIME type strings, or both. +</p> +<p> + MIME types have the format +</p> +<pre> +<em>type</em>/<em>subtype</em> +</pre> +<p> + For example, the well-known MIME type <code>text/html</code> has the <code>text</code> type and + the <code>html</code> subtype. If the provider returns this type for a URI, it means that a + query using that URI will return text containing HTML tags. +</p> +<p> + Custom MIME type strings, also called "vendor-specific" MIME types, have more + complex <em>type</em> and <em>subtype</em> values. The <em>type</em> value is always +</p> +<pre> +vnd.android.cursor.<strong>dir</strong> +</pre> +<p> + for multiple rows, or +</p> +<pre> +vnd.android.cursor.<strong>item</strong> +</pre> +<p> + for a single row. +</p> +<p> + The <em>subtype</em> is provider-specific. The Android built-in providers usually have a simple + subtype. For example, the when the Contacts application creates a row for a telephone number, + it sets the following MIME type in the row: +</p> +<pre> +vnd.android.cursor.item/phone_v2 +</pre> +<p> + Notice that the subtype value is simply <code>phone_v2</code>. +</p> +<p> + Other provider developers may create their own pattern of subtypes based on the provider's + authority and table names. For example, consider a provider that contains train timetables. + The provider's authority is <code>com.example.trains</code>, and it contains the tables + Line1, Line2, and Line3. In response to the content URI +</p> +<p> +<pre> +content://com.example.trains/Line1 +</pre> +<p> + for table Line1, the provider returns the MIME type +</p> +<pre> +vnd.android.cursor.<strong>dir</strong>/vnd.example.line1 +</pre> +<p> + In response to the content URI +</p> +<pre> +content://com.example.trains/Line2/5 +</pre> +<p> + for row 5 in table Line2, the provider returns the MIME type +</p> +<pre> +vnd.android.cursor.<strong>item</strong>/vnd.example.line2 +</pre> +<p> + Most content providers define contract class constants for the MIME types they use. The + Contacts Provider contract class {@link android.provider.ContactsContract.RawContacts}, + for example, defines the constant + {@link android.provider.ContactsContract.RawContacts#CONTENT_ITEM_TYPE} for the MIME type of + a single raw contact row. +</p> +<p> + Content URIs for single rows are described in the section + <a href="#ContentURIs">Content URIs</a>. +</p> diff --git a/docs/html/guide/topics/providers/content-provider-creating.jd b/docs/html/guide/topics/providers/content-provider-creating.jd new file mode 100644 index 000000000000..4ebdb502138c --- /dev/null +++ b/docs/html/guide/topics/providers/content-provider-creating.jd @@ -0,0 +1,1215 @@ +page.title=Creating a Content Provider +@jd:body +<div id="qv-wrapper"> +<div id="qv"> + + +<h2>In this document</h2> +<ol> + <li> + <a href="#DataStorage">Designing Data Storage</a> + </li> + <li> + <a href="#ContentURI">Designing Content URIs</a> + </li> + <li> + <a href="#ContentProvider">Implementing the ContentProvider Class</a> + <ol> + <li> + <a href="#RequiredAccess">Required Methods</a> + </li> + <li> + <a href="#Query">Implementing the query() method</a> + </li> + <li> + <a href="#Insert">Implementing the insert() method</a> + </li> + <li> + <a href="#Delete">Implementing the delete() method</a> + </li> + <li> + <a href="#Update">Implementing the update() method</a> + </li> + <li> + <a href="#OnCreate">Implementing the onCreate() method</a> + </li> + </ol> + </li> + <li> + <a href="#MIMETypes">Implementing Content Provider MIME Types</a> + <ol> + <li> + <a href="#TableMIMETypes">MIME types for tables</a> + </li> + <li> + <a href="#FileMIMETypes">MIME types for files</a> + </li> + </ol> + </li> + <li> + <a href="#ContractClass">Implementing a Contract Class</a> + </li> + <li> + <a href="#Permissions">Implementing Content Provider Permissions</a> + </li> + <li> + <a href="#ProviderElement">The <provider> Element</a> + </li> + <li> + <a href="#Intents">Intents and Data Access</a> + </li> +</ol> +<h2>Key classes</h2> + <ol> + <li> + {@link android.content.ContentProvider} + </li> + <li> + {@link android.database.Cursor} + </li> + <li> + {@link android.net.Uri} + </li> + </ol> +<h2>Related Samples</h2> + <ol> + <li> + <a + href="{@docRoot}resources/samples/NotePad/index.html"> + Note Pad sample application + </a> + </li> + </ol> +<h2>See also</h2> + <ol> + <li> + <a href="{@docRoot}guide/topics/providers/content-provider-basics.html"> + Content Provider Basics</a> + </li> + <li> + <a href="{@docRoot}guide/topics/providers/calendar-provider.html"> + Calendar Provider</a> + </li> + </ol> +</div> +</div> + + +<p> + A content provider manages access to a central repository of data. You implement a + provider as one or more classes in an Android application, along with elements in + the manifest file. One of your classes implements a subclass + {@link android.content.ContentProvider}, which is the interface between your provider and + other applications. Although content providers are meant to make data available to other + applications, you may of course have activities in your application that allow the user + to query and modify the data managed by your provider. +</p> +<p> + The rest of this topic is a basic list of steps for building a content provider and a list + of APIs to use. +</p> + + +<!-- Before You Start Building --> +<h2 id="BeforeYouStart">Before You Start Building</h2> +<p> + Before you start building a provider, do the following: +</p> +<ol> + <li> + <strong>Decide if you need a content provider</strong>. You need to build a content + provider if you want to provide one or more of the following features: + <ul> + <li>You want to offer complex data or files to other applications.</li> + <li>You want to allow users to copy complex data from your app into other apps.</li> + <li>You want to provide custom search suggestions using the search framework.</li> + </ul> + <p> + You <em>don't</em> need a provider to use an SQLite database if the use is entirely within + your own application. + </p> + </li> + <li> + If you haven't done so already, read the topic + <a href="{@docRoot}guide/topics/providers/content-provider-basics.html"> + Content Provider Basics</a> to learn more about providers. + </li> +</ol> +<p> + Next, follow these steps to build your provider: +</p> +<ol> + <li> + Design the raw storage for your data. A content provider offers data in two ways: + <dl> + <dt> + File data + </dt> + <dd> + Data that normally goes into files, such as + photos, audio, or videos. Store the files in your application's private + space. In response to a request for a file from another application, your + provider can offer a handle to the file. + </dd> + <dt> + "Structured" data + </dt> + <dd> + Data that normally goes into a database, array, or similar structure. + Store the data in a form that's compatible with tables of rows and columns. A row + represents an entity, such as a person or an item in inventory. A column represents + some data for the entity, such a person's name or an item's price. A common way to + store this type of data is in an SQLite database, but you can use any type of + persistent storage. To learn more about the storage types available in the + Android system, see the section <a href="#DataStorage"> + Designing Data Storage</a>. + </dd> + </dl> + </li> + <li> + Define a concrete implementation of the {@link android.content.ContentProvider} class and + its required methods. This class is the interface between your data and the rest of the + Android system. For more information about this class, see the section + <a href="#ContentProvider">Implementing the ContentProvider Class</a>. + </li> + <li> + Define the provider's authority string, its content URIs, and column names. If you want + the provider's application to handle intents, also define intent actions, extras data, + and flags. Also define the permissions that you will require for applications that want + to access your data. You should consider defining all of these values as constants in a + separate contract class; later, you can expose this class to other developers. For more + information about content URIs, see the + section <a href="#ContentURI">Designing Content URIs</a>. + For more information about intents, see the + section <a href="#Intents">Intents and Data Access</a>. + </li> + <li> + Add other optional pieces, such as sample data or an implementation + of {@link android.content.AbstractThreadedSyncAdapter} that can synchronize data between + the provider and cloud-based data. + </li> +</ol> + + +<!-- Designing Data Storage --> +<h2 id="DataStorage">Designing Data Storage</h2> +<p> + A content provider is the interface to data saved in a structured format. Before you create + the interface, you must decide how to store the data. You can store the data in any form you + like, and then design the interface to read and write the data as necessary. +</p> +<p> + These are some of the data storage technologies that are available in Android: +</p> +<ul> + <li> + The Android system includes an SQLite database API that Android's own providers use + to store table-oriented data. The + {@link android.database.sqlite.SQLiteOpenHelper} class helps you create databases, and the + {@link android.database.sqlite.SQLiteDatabase} class is the base class for accessing + databases. + <p> + Remember that you don't have to use a database to implement your repository. A provider + appears externally as a set of tables, similar to a relational database, but this is + not a requirement for the provider's internal implementation. + </p> + </li> + <li> + For storing file data, Android has a variety of file-oriented APIs. + To learn more about file storage, read the topic + <a href="{@docRoot}guide/topics/data/data-storage.html">Data Storage</a>. If you're + designing a provider that offers media-related data such as music or videos, you can + have a provider that combines table data and files. + </li> + <li> + For working with network-based data, use classes in {@link java.net} and + {@link android.net}. You can also synchronize network-based data to a local data + store such as a database, and then offer the data as tables or files. + The <a href="{@docRoot}resources/samples/SampleSyncAdapter/index.html"> + Sample Sync Adapter</a> sample application demonstrates this type of synchronization. + </li> +</ul> +<h3 id="DataDesign"> + Data design considerations +</h3> +<p> + Here are some tips for designing your provider's data structure: +</p> +<ul> + <li> + Table data should always have a "primary key" column that the provider maintains + as a unique numeric value for each row. You can use this value to link the row to related + rows in other tables (using it as a "foreign key"). Although you can use any name + for this column, using {@link android.provider.BaseColumns#_ID BaseColumns._ID} is the best + choice, because linking the results of a provider query to a + {@link android.widget.ListView} requires one of the retrieved columns to have the name + <code>_ID</code>. + </li> + <li> + If you want to provide bitmap images or other very large pieces of file-oriented data, store + the data in a file and then provide it indirectly rather than storing it directly in a + table. If you do this, you need to tell users of your provider that they need to use a + {@link android.content.ContentResolver} file method to access the data. + </li> + <li> + Use the Binary Large OBject (BLOB) data type to store data that varies in size or has a + varying structure. For example, you can use a BLOB column to store a + <a href="http://code.google.com/p/protobuf">protocol buffer</a> or + <a href="http://www.json.org">JSON structure</a>. + <p> + You can also use a BLOB to implement a <em>schema-independent</em> table. In + this type of table, you define a primary key column, a MIME type column, and one or + more generic columns as BLOB. The meaning of the data in the BLOB columns is indicated + by the value in the MIME type column. This allows you to store different row types in + the same table. The Contacts Provider's "data" table + {@link android.provider.ContactsContract.Data} is an example of a schema-independent + table. + </p> + </li> +</ul> +<!-- Designing Content URIs --> +<h2 id="ContentURI">Designing Content URIs</h2> +<p> + A <strong>content URI</strong> is a URI that identifies data in a provider. Content URIs include + the symbolic name of the entire provider (its <strong>authority</strong>) and a + name that points to a table or file (a <strong>path</strong>). The optional id part points to + an individual row in a table. Every data access method of + {@link android.content.ContentProvider} has a content URI as an argument; this allows you to + determine the table, row, or file to access. +</p> +<p> + The basics of content URIs are described in the topic + <a href="{@docRoot}guide/topics/providers/content-provider-basics.html"> + Content Provider Basics</a>. +</p> +<h3>Designing an authority</h3> +<p> + A provider usually has a single authority, which serves as its Android-internal name. To + avoid conflicts with other providers, you should use Internet domain ownership (in reverse) + as the basis of your provider authority. Because this recommendation is also true for Android + package names, you can define your provider authority as an extension of the name + of the package containing the provider. For example, if your Android package name is + <code>com.example.<appname></code>, you should give your provider the + authority <code>com.example.<appname>.provider</code>. +</p> +<h3>Designing a path structure</h3> +<p> + Developers usually create content URIs from the authority by appending paths that point to + individual tables. For example, if you have two tables <em>table1</em> and + <em>table2</em>, you combine the authority from the previous example to yield the + content URIs + <code>com.example.<appname>.provider/table1</code> and + <code>com.example.<appname>.provider/table2</code>. Paths aren't + limited to a single segment, and there doesn't have to be a table for each level of the path. +</p> +<h3>Handling content URI IDs</h3> +<p> + By convention, providers offer access to a single row in a table by accepting a content URI + with an ID value for the row at the end of the URI. Also by convention, providers match the + ID value to the table's <code>_ID</code> column, and perform the requested access against the + row that matches. +</p> +<p> + This convention facilitates a common design pattern for apps accessing a provider. The app + does a query against the provider and displays the resulting {@link android.database.Cursor} + in a {@link android.widget.ListView} using a {@link android.widget.CursorAdapter}. + The definition of {@link android.widget.CursorAdapter} requires one of the columns in the + {@link android.database.Cursor} to be <code>_ID</code> +</p> +<p> + The user then picks one of the displayed rows from the UI in order to look at or modify the + data. The app gets the corresponding row from the {@link android.database.Cursor} backing the + {@link android.widget.ListView}, gets the <code>_ID</code> value for this row, appends it to + the content URI, and sends the access request to the provider. The provider can then do the + query or modification against the exact row the user picked. +</p> +<h3>Content URI patterns</h3> +<p> + To help you choose which action to take for an incoming content URI, the provider API includes + the convenience class {@link android.content.UriMatcher}, which maps content URI "patterns" to + integer values. You can use the integer values in a <code>switch</code> statement that + chooses the desired action for the content URI or URIs that match a particular pattern. +</p> +<p> + A content URI pattern matches content URIs using wildcard characters: +</p> + <ul> + <li> + <strong><code>*</code>:</strong> Matches a string of any valid characters of any length. + </li> + <li> + <strong><code>#</code>:</strong> Matches a string of numeric characters of any length. + </li> + </ul> +<p> + As an example of designing and coding content URI handling, consider a provider with the + authority <code>com.example.app.provider</code> that recognizes the following content URIs + pointing to tables: +</p> +<ul> + <li> + <code>content://com.example.app.provider/table1</code>: A table called <code>table1</code>. + </li> + <li> + <code>content://com.example.app.provider/table2/dataset1</code>: A table called + <code>dataset1</code>. + </li> + <li> + <code>content://com.example.app.provider/table2/dataset2</code>: A table called + <code>dataset2</code>. + </li> + <li> + <code>content://com.example.app.provider/table3</code>: A table called <code>table3</code>. + </li> +</ul> +<p> + The provider also recognizes these content URIs if they have a row ID appended to them, as + for example <code>content://com.example.app.provider/table3/1</code> for the row identified by + <code>1</code> in <code>table3</code>. +</p> +<p> + The following content URI patterns would be possible: +</p> +<dl> + <dt> + <code>content://com.example.app.provider/*</code> + </dt> + <dd> + Matches any content URI in the provider. + </dd> + <dt> + <code>content://com.example.app.provider/table2/*</code>: + </dt> + <dd> + Matches a content URI for the tables <code>dataset1</code> + and <code>dataset2</code>, but doesn't match content URIs for <code>table1</code> or + <code>table3</code>. + </dd> + <dt> + <code>content://com.example.app.provider/table3/#</code>: Matches a content URI + for single rows in <code>table3</code>, such as + <code>content://com.example.app.provider/table3/6</code> for the row identified by + <code>6</code>. + </dt> +</dl> +<p> + The following code snippet shows how the methods in {@link android.content.UriMatcher} work. + This code handles URIs for an entire table differently from URIs for a + single row, by using the content URI pattern + <code>content://<authority>/<path></code> for tables, and + <code>content://<authority>/<path>/<id></code> for single rows. +</p> +<p> + The method {@link android.content.UriMatcher#addURI(String, String, int) addURI()} maps an + authority and path to an integer value. The method android.content.UriMatcher#match(Uri) + match()} returns the integer value for a URI. A <code>switch</code> statement + chooses between querying the entire table, and querying for a single record: +</p> +<pre class="prettyprint"> +public class ExampleProvider extends ContentProvider { +... + // Creates a UriMatcher object. + private static final UriMatcher sUriMatcher; +... + /* + * The calls to addURI() go here, for all of the content URI patterns that the provider + * should recognize. For this snippet, only the calls for table 3 are shown. + */ +... + /* + * Sets the integer value for multiple rows in table 3 to 1. Notice that no wildcard is used + * in the path + */ + sUriMatcher.addURI("com.example.app.provider", "table3", 1); + + /* + * Sets the code for a single row to 2. In this case, the "#" wildcard is + * used. "content://com.example.app.provider/table3/3" matches, but + * "content://com.example.app.provider/table3 doesn't. + */ + sUriMatcher.addURI("com.example.app.provider", "table3/#", 2); +... + // Implements ContentProvider.query() + public Cursor query( + Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String sortOrder) { +... + /* + * Choose the table to query and a sort order based on the code returned for the incoming + * URI. Here, too, only the statements for table 3 are shown. + */ + switch (sUriMatcher.match(uri)) { + + + // If the incoming URI was for all of table3 + case 1: + + if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC"; + break; + + // If the incoming URI was for a single row + case 2: + + /* + * Because this URI was for a single row, the _ID value part is + * present. Get the last path segment from the URI; this is the _ID value. + * Then, append the value to the WHERE clause for the query + */ + selection = selection + "_ID = " uri.getLastPathSegment(); + break; + + default: + ... + // If the URI is not recognized, you should do some error handling here. + } + // call the code to actually do the query + } +</pre> +<p> + Another class, {@link android.content.ContentUris}, provides convenience methods for working + with the <code>id</code> part of content URIs. The classes {@link android.net.Uri} and + {@link android.net.Uri.Builder} include convenience methods for parsing existing + {@link android.net.Uri} objects and building new ones. +</p> + +<!-- Implementing the ContentProvider class --> +<h2 id="ContentProvider">Implementing the ContentProvider Class</h2> +<p> + The {@link android.content.ContentProvider} instance manages access + to a structured set of data by handling requests from other applications. All forms + of access eventually call {@link android.content.ContentResolver}, which then calls a concrete + method of {@link android.content.ContentProvider} to get access. +</p> +<h3 id="RequiredAccess">Required methods</h3> +<p> + The abstract class {@link android.content.ContentProvider} defines six abstract methods that + you must implement as part of your own concrete subclass. All of these methods except + {@link android.content.ContentProvider#onCreate() onCreate()} are called by a client application + that is attempting to access your content provider: +</p> +<dl> + <dt> + {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) + query()} + </dt> + <dd> + Retrieve data from your provider. Use the arguments to select the table to + query, the rows and columns to return, and the sort order of the result. + Return the data as a {@link android.database.Cursor} object. + </dd> + <dt> + {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} + </dt> + <dd> + Insert a new row into your provider. Use the arguments to select the + destination table and to get the column values to use. Return a content URI for the + newly-inserted row. + </dd> + <dt> + {@link android.content.ContentProvider#update(Uri, ContentValues, String, String[]) + update()} + </dt> + <dd> + Update existing rows in your provider. Use the arguments to select the table and rows + to update and to get the updated column values. Return the number of rows updated. + </dd> + <dt> + {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} + </dt> + <dd> + Delete rows from your provider. Use the arguments to select the table and the rows to + delete. Return the number of rows deleted. + </dd> + <dt> + {@link android.content.ContentProvider#getType(Uri) getType()} + </dt> + <dd> + Return the MIME type corresponding to a content URI. This method is described in more + detail in the section <a href="#MIMETypes">Implementing Content Provider MIME Types</a>. + </dd> + <dt> + {@link android.content.ContentProvider#onCreate() onCreate()} + </dt> + <dd> + Initialize your provider. The Android system calls this method immediately after it + creates your provider. Notice that your provider is not created until a + {@link android.content.ContentResolver} object tries to access it. + </dd> +</dl> +<p> + Notice that these methods have the same signature as the identically-named + {@link android.content.ContentResolver} methods. +</p> +<p> + Your implementation of these methods should account for the following: +</p> +<ul> + <li> + All of these methods except {@link android.content.ContentProvider#onCreate() onCreate()} + can be called by multiple threads at once, so they must be thread-safe. To learn + more about multiple threads, see the topic + <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html"> + Processes and Threads</a>. + </li> + <li> + Avoid doing lengthy operations in {@link android.content.ContentProvider#onCreate() + onCreate()}. Defer initialization tasks until they are actually needed. + The section <a href="#OnCreate">Implementing the onCreate() method</a> + discusses this in more detail. + </li> + <li> + Although you must implement these methods, your code does not have to do anything except + return the expected data type. For example, you may want to prevent other applications + from inserting data into some tables. To do this, you can ignore the call to + {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} and return + 0. + </li> +</ul> +<h3 id="Query">Implementing the query() method</h3> +<p> + The + {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) + ContentProvider.query()} method must return a {@link android.database.Cursor} object, or if it + fails, throw an {@link java.lang.Exception}. If you are using an SQLite database as your data + storage, you can simply return the {@link android.database.Cursor} returned by one of the + <code>query()</code> methods of the {@link android.database.sqlite.SQLiteDatabase} class. + If the query does not match any rows, you should return a {@link android.database.Cursor} + instance whose {@link android.database.Cursor#getCount()} method returns 0. + You should return <code>null</code> only if an internal error occurred during the query process. +</p> +<p> + If you aren't using an SQLite database as your data storage, use one of the concrete subclasses + of {@link android.database.Cursor}. For example, the {@link android.database.MatrixCursor} class + implements a cursor in which each row is an array of {@link java.lang.Object}. With this class, + use {@link android.database.MatrixCursor#addRow(Object[]) addRow()} to add a new row. +</p> +<p> + Remember that the Android system must be able to communicate the {@link java.lang.Exception} + across process boundaries. Android can do this for the following exceptions that may be useful + in handling query errors: +</p> +<ul> + <li> + {@link java.lang.IllegalArgumentException} (You may choose to throw this if your provider + receives an invalid content URI) + </li> + <li> + {@link java.lang.NullPointerException} + </li> +</ul> +<h3 id="Insert">Implementing the insert() method</h3> +<p> + The {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} method adds a + new row to the appropriate table, using the values in the {@link android.content.ContentValues} + argument. If a column name is not in the {@link android.content.ContentValues} argument, you + may want to provide a default value for it either in your provider code or in your database + schema. +</p> +<p> + This method should return the content URI for the new row. To construct this, append the new + row's <code>_ID</code> (or other primary key) value to the table's content URI, using + {@link android.content.ContentUris#withAppendedId(Uri, long) withAppendedId()}. +</p> +<h3 id="Delete">Implementing the delete() method</h3> +<p> + The {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} method + does not have to physically delete rows from your data storage. If you are using a sync adapter + with your provider, you should consider marking a deleted row + with a "delete" flag rather than removing the row entirely. The sync adapter can + check for deleted rows and remove them from the server before deleting them from the provider. +</p> +<h3 id="Update">Implementing the update() method</h3> +<p> + The {@link android.content.ContentProvider#update(Uri, ContentValues, String, String[]) + update()} method takes the same {@link android.content.ContentValues} argument used by + {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()}, and the + same <code>selection</code> and <code>selectionArgs</code> arguments used by + {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} and + {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) + ContentProvider.query()}. This may allow you to re-use code between these methods. +</p> +<h3 id="OnCreate">Implementing the onCreate() method</h3> +<p> + The Android system calls {@link android.content.ContentProvider#onCreate() + onCreate()} when it starts up the provider. You should perform only fast-running initialization + tasks in this method, and defer database creation and data loading until the provider actually + receives a request for the data. If you do lengthy tasks in + {@link android.content.ContentProvider#onCreate() onCreate()}, you will slow down your + provider's startup. In turn, this will slow down the response from the provider to other + applications. +</p> +<p> + For example, if you are using an SQLite database you can create + a new {@link android.database.sqlite.SQLiteOpenHelper} object in + {@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()}, + and then create the SQL tables the first time you open the database. To facilitate this, the + first time you call {@link android.database.sqlite.SQLiteOpenHelper#getWritableDatabase + getWritableDatabase()}, it automatically calls the + {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) + SQLiteOpenHelper.onCreate()} method. +</p> +<p> + The following two snippets demonstrate the interaction between + {@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()} and + {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) + SQLiteOpenHelper.onCreate()}. The first snippet is the implementation of + {@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()}: +</p> +<pre class="prettyprint"> +public class ExampleProvider extends ContentProvider + + /* + * Defines a handle to the database helper object. The MainDatabaseHelper class is defined + * in a following snippet. + */ + private MainDatabaseHelper mOpenHelper; + + // Defines the database name + private static final String DBNAME = "mydb"; + + // Holds the database object + private SQLiteDatabase db; + + public boolean onCreate() { + + /* + * Creates a new helper object. This method always returns quickly. + * Notice that the database itself isn't created or opened + * until SQLiteOpenHelper.getWritableDatabase is called + */ + mOpenHelper = new SQLiteOpenHelper( + getContext(), // the application context + DBNAME, // the name of the database) + null, // uses the default SQLite cursor + 1 // the version number + ); + + return true; + } + + ... + + // Implements the provider's insert method + public Cursor insert(Uri uri, ContentValues values) { + // Insert code here to determine which table to open, handle error-checking, and so forth + + ... + + /* + * Gets a writeable database. This will trigger its creation if it doesn't already exist. + * + */ + db = mOpenHelper.getWritableDatabase(); + } +} +</pre> +<p> + The next snippet is the implementation of + {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) + SQLiteOpenHelper.onCreate()}, including a helper class: +</p> +<pre class="prettyprint"> +... +// A string that defines the SQL statement for creating a table +private static final String SQL_CREATE_MAIN = "CREATE TABLE " + + "main " + // Table's name + "(" + // The columns in the table + " _ID INTEGER PRIMARY KEY, " + + " WORD TEXT" + " FREQUENCY INTEGER " + + " LOCALE TEXT )"; +... +/** + * Helper class that actually creates and manages the provider's underlying data repository. + */ +protected static final class MainDatabaseHelper extends SQLiteOpenHelper { + + /* + * Instantiates an open helper for the provider's SQLite data repository + * Do not do database creation and upgrade here. + */ + MainDatabaseHelper(Context context) { + super(context, DBNAME, null, 1); + } + + /* + * Creates the data repository. This is called when the provider attempts to open the + * repository and SQLite reports that it doesn't exist. + */ + public void onCreate(SQLiteDatabase db) { + + // Creates the main table + db.execSQL(SQL_CREATE_MAIN); + } +} +</pre> + + +<!-- Implementing ContentProvider MIME Types --> +<h2 id="MIMETypes">Implementing ContentProvider MIME Types</h2> +<p> + The {@link android.content.ContentProvider} class has two methods for returning MIME types: +</p> +<dl> + <dt> + {@link android.content.ContentProvider#getType(Uri) getType()} + </dt> + <dd> + One of the required methods that you must implement for any provider. + </dd> + <dt> + {@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()} + </dt> + <dd> + A method that you're expected to implement if your provider offers files. + </dd> +</dl> +<h3 id="TableMIMETypes">MIME types for tables</h3> +<p> + The {@link android.content.ContentProvider#getType(Uri) getType()} method returns a + {@link java.lang.String} in MIME format that describes the type of data returned by the content + URI argument. The {@link android.net.Uri} argument can be a pattern rather than a specific URI; + in this case, you should return the type of data associated with content URIs that match the + pattern. +</p> +<p> + For common types of data such as as text, HTML, or JPEG, + {@link android.content.ContentProvider#getType(Uri) getType()} should return the standard + MIME type for that data. A full list of these standard types is available on the + <a href="http://www.iana.org/assignments/media-types/index.htm">IANA MIME Media Types</a> + website. +</p> +<p> + For content URIs that point to a row or rows of table data, + {@link android.content.ContentProvider#getType(Uri) getType()} should return + a MIME type in Android's vendor-specific MIME format: +</p> +<ul> + <li> + Type part: <code>vnd</code> + </li> + <li> + Subtype part: + <ul> + <li> + If the URI pattern is for a single row: <code>android.cursor.<strong>item</strong>/</code> + </li> + <li> + If the URI pattern is for more than one row: <code>android.cursor.<strong>dir</strong>/</code> + </li> + </ul> + </li> + <li> + Provider-specific part: <code>vnd.<name></code>.<code><type></code> + <p> + You supply the <code><name></code> and <code><type></code>. + The <code><name></code> value should be globally unique, + and the <code><type></code> value should be unique to the corresponding URI + pattern. A good choice for <code><name></code> is your company's name or + some part of your application's Android package name. A good choice for the + <code><type></code> is a string that identifies the table associated with the + URI. + </p> + + </li> +</ul> +<p> + For example, if a provider's authority is + <code>com.example.app.provider</code>, and it exposes a table named + <code>table1</code>, the MIME type for multiple rows in <code>table1</code> is: +</p> +<pre> +vnd.android.cursor.<strong>dir</strong>/vnd.com.example.provider.table1 +</pre> +<p> + For a single row of <code>table1</code>, the MIME type is: +</p> +<pre> +vnd.android.cursor.<strong>item</strong>/vnd.com.example.provider.table1 +</pre> +<h3 id="FileMIMETypes">MIME types for files</h3> +<p> + If your provider offers files, implement + {@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()}. + The method returns a {@link java.lang.String} array of MIME types for the files your provider + can return for a given content URI. You should filter the MIME types you offer by the MIME type + filter argument, so that you return only those MIME types that the client wants to handle. +</p> +<p> + For example, consider a provider that offers photo images as files in <code>.jpg</code>, + <code>.png</code>, and <code>.gif</code> format. + If an application calls {@link android.content.ContentResolver#getStreamTypes(Uri, String) + ContentResolver.getStreamTypes()} with the filter string <code>image/*</code> (something that + is an "image"), + then the {@link android.content.ContentProvider#getStreamTypes(Uri, String) + ContentProvider.getStreamTypes()} method should return the array: +</p> +<pre> +{ "image/jpeg", "image/png", "image/gif"} +</pre> +<p> + If the app is only interested in <code>.jpg</code> files, then it can call + {@link android.content.ContentResolver#getStreamTypes(Uri, String) + ContentResolver.getStreamTypes()} with the filter string <code>*\/jpeg</code>, and + {@link android.content.ContentProvider#getStreamTypes(Uri, String) + ContentProvider.getStreamTypes()} should return: +<pre> +{"image/jpeg"} +</pre> +<p> + If your provider doesn't offer any of the MIME types requested in the filter string, + {@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()} + should return <code>null</code>. +</p> + + +<!-- Implementing a Contract Class --> +<h2 id="ContractClass">Implementing a Contract Class</h2> +<p> + A contract class is a <code>public final</code> class that contains constant definitions for the + URIs, column names, MIME types, and other meta-data that pertain to the provider. The class + establishes a contract between the provider and other applications by ensuring that the provider + can be correctly accessed even if there are changes to the actual values of URIs, column names, + and so forth. +</p> +<p> + A contract class also helps developers because it usually has mnemonic names for its constants, + so developers are less likely to use incorrect values for column names or URIs. Since it's a + class, it can contain Javadoc documentation. Integrated development environments such as + Eclipse can auto-complete constant names from the contract class and display Javadoc for the + constants. +</p> +<p> + Developers can't access the contract class's class file from your application, but they can + statically compile it into their application from a <code>.jar</code> file you provide. +</p> +<p> + The {@link android.provider.ContactsContract} class and its nested classes are examples of + contract classes. +</p> +<h2 id="Permissions">Implementing Content Provider Permissions</h2> +<p> + Permissions and access for all aspects of the Android system are described in detail in the + topic <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a>. + The topic <a href="{@docRoot}guide/topics/data/data-storage.html">Data Storage</a> also + described the security and permissions in effect for various types of storage. + In brief, the important points are: +</p> +<ul> + <li> + By default, data files stored on the device's internal storage are private to your + application and provider. + </li> + <li> + {@link android.database.sqlite.SQLiteDatabase} databases you create are private to your + application and provider. + </li> + <li> + By default, data files that you save to external storage are <em>public</em> and + <em>world-readable</em>. You can't use a content provider to restrict access to files in + external storage, because other applications can use other API calls to read and write them. + </li> + <li> + The method calls for opening or creating files or SQLite databases on your device's internal + storage can potentially give both read and write access to all other applications. If you + use an internal file or database as your provider's repository, and you give it + "world-readable" or "world-writeable" access, the permissions you set for your provider in + its manifest won't protect your data. The default access for files and databases in + internal storage is "private", and for your provider's repository you shouldn't change this. + </li> +</ul> +<p> + If you want to use content provider permissions to control access to your data, then you should + store your data in internal files, SQLite databases, or the "cloud" (for example, + on a remote server), and you should keep files and databases private to your application. +</p> +<h3>Implementing permissions</h3> +<p> + All applications can read from or write to your provider, even if the underlying data is + private, because by default your provider does not have permissions set. To change this, + set permissions for your provider in your manifest file, using attributes or child + elements of the <code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code> element. You can set permissions that apply to the entire provider, + or to certain tables, or even to certain records, or all three. +</p> +<p> + You define permissions for your provider with one or more + <code><a href="{@docRoot}guide/topics/manifest/permission-element.html"> + <permission></a></code> elements in your manifest file. To make the + permission unique to your provider, use Java-style scoping for the + <code><a href="{@docRoot}guide/topics/manifest/permission-element.html#nm"> + android:name</a></code> attribute. For example, name the read permission + <code>com.example.app.provider.permission.READ_PROVIDER</code>. + +</p> +<p> + The following list describes the scope of provider permissions, starting with the + permissions that apply to the entire provider and then becoming more fine-grained. + More fine-grained permissions take precedence over ones with larger scope: +</p> +<dl> + <dt> + Single read-write provider-level permission + </dt> + <dd> + One permission that controls both read and write access to the entire provider, specified + with the <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#prmsn"> + android:permission</a></code> attribute of the + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code> element. + </dd> + <dt> + Separate read and write provider-level permission + </dt> + <dd> + A read permission and a write permission for the entire provider. You specify them + with the <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#rprmsn"> + android:readPermission</a></code> and + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#wprmsn"> + android:writePermission</a></code> attributes of the + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code> element. They take precedence over the permission required by + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#prmsn"> + android:permission</a></code>. + </dd> + <dt> + Path-level permission + </dt> + <dd> + Read, write, or read/write permission for a content URI in your provider. You specify + each URI you want to control with a + <code><a href="{@docRoot}guide/topics/manifest/path-permission-element.html"> + <path-permission></a></code> child element of the + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code> element. For each content URI you specify, you can specify a + read/write permission, a read permission, or a write permission, or all three. The read and + write permissions take precedence over the read/write permission. Also, path-level + permission takes precedence over provider-level permissions. + </dd> + <dt> + Temporary permission + </dt> + <dd> + A permission level that grants temporary access to an application, even if the application + doesn't have the permissions that are normally required. The temporary + access feature reduces the number of permissions an application has to request in + its manifest. When you turn on temporary permissions, the only applications that need + "permanent" permissions for your provider are ones that continually access all + your data. + <p> + Consider the permissions you need to implement an email provider and app, when you + want to allow an outside image viewer application to display photo attachments from your + provider. To give the image viewer the necessary access without requiring permissions, + set up temporary permissions for content URIs for photos. Design your email app so + that when the user wants to display a photo, the app sends an intent containing the + photo's content URI and permission flags to the image viewer. The image viewer can + then query your email provider to retrieve the photo, even though the viewer doesn't + have the normal read permission for your provider. + </p> + <p> + To turn on temporary permissions, either set the + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#gprmsn"> + android:grantUriPermissions</a></code> attribute of the + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code> element, or add one or more + <code><a href="{@docRoot}guide/topics/manifest/grant-uri-permission-element.html"> + <grant-uri-permission></a></code> child elements to your + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code> element. If you use temporary permissions, you have to call + {@link android.content.Context#revokeUriPermission(Uri, int) + Context.revokeUriPermission()} whenever you remove support for a content URI from your + provider, and the content URI is associated with a temporary permission. + </p> + <p> + The attribute's value determines how much of your provider is made accessible. + If the attribute is set to <code>true</code>, then the system will grant temporary + permission to your entire provider, overriding any other permissions that are required + by your provider-level or path-level permissions. + </p> + <p> + If this flag is set to <code>false</code>, then you must add + <code><a href="{@docRoot}guide/topics/manifest/grant-uri-permission-element.html"> + <grant-uri-permission></a></code> child elements to your + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code> element. Each child element specifies the content URI or + URIs for which temporary access is granted. + </p> + <p> + To delegate temporary access to an application, an intent must contain + the {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION} or the + {@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION} flags, or both. These + are set with the {@link android.content.Intent#setFlags(int) setFlags()} method. + </p> + <p> + If the <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#gprmsn"> + android:grantUriPermissions</a></code> attribute is not present, it's assumed to be + <code>false</code>. + </p> + </dd> +</dl> + + + +<!-- The Provider Element --> +<h2 id="ProviderElement">The <provider> Element</h2> +<p> + Like {@link android.app.Activity} and {@link android.app.Service} components, + a subclass of {@link android.content.ContentProvider} + must be defined in the manifest file for its application, using the + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code> element. The Android system gets the following information from + the element: +<dl> + <dt> + Authority + (<a href="{@docRoot}guide/topics/manifest/provider-element.html#auth">{@code + android:authorities}</a>) + </dt> + <dd> + Symbolic names that identify the entire provider within the system. This + attribute is described in more detail in the section + <a href="#ContentURI">Designing Content URIs</a>. + </dd> + <dt> + Provider class name + (<code> +<a href="{@docRoot}guide/topics/manifest/provider-element.html#nm">android:name</a> + </code>) + </dt> + <dd> + The class that implements {@link android.content.ContentProvider}. This class is + described in more detail in the section + <a href="#ContentProvider">Implementing the ContentProvider Class</a>. + </dd> + <dt> + Permissions + </dt> + <dd> + Attributes that specify the permissions that other applications must have in order to access + the provider's data: + <ul> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#gprmsn"> + android:grantUriPermssions</a></code>: Temporary permission flag. + </li> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#prmsn"> + android:permission</a></code>: Single provider-wide read/write permission. + </li> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#rprmsn"> + android:readPermission</a></code>: Provider-wide read permission. + </li> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#wprmsn"> + android:writePermission</a></code>: Provider-wide write permission. + </li> + </ul> + <p> + Permissions and their corresponding attributes are described in more + detail in the section + <a href="#Permissions">Implementing Content Provider Permissions</a>. + </p> + </dd> + <dt> + Startup and control attributes + </dt> + <dd> + These attributes determine how and when the Android system starts the provider, the + process characteristics of the provider, and other run-time settings: + <ul> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#enabled"> + android:enabled</a></code>: Flag allowing the system to start the provider. + </li> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#exported"> + android:exported</a></code>: Flag allowing other applications to use this provider. + </li> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#init"> + android:initOrder</a></code>: The order in which this provider should be started, + relative to other providers in the same process. + </li> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#multi"> + android:multiProcess</a></code>: Flag allowing the system to start the provider + in the same process as the calling client. + </li> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#proc"> + android:process</a></code>: The name of the process in which the provider should + run. + </li> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#sync"> + android:syncable</a></code>: Flag indicating that the provider's data is to be + sync'ed with data on a server. + </li> + </ul> + <p> + The attributes are fully documented in the dev guide topic for the + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code> + element. + </p> + </dd> + <dt> + Informational attributes + </dt> + <dd> + An optional icon and label for the provider: + <ul> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#icon"> + android:icon</a></code>: A drawable resource containing an icon for the provider. + The icon appears next to the provider's label in the list of apps in + <em>Settings</em> > <em>Apps</em> > <em>All</em>. + </li> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#label"> + android:label</a></code>: An informational label describing the provider or its + data, or both. The label appears in the list of apps in + <em>Settings</em> > <em>Apps</em> > <em>All</em>. + </li> + </ul> + <p> + The attributes are fully documented in the dev guide topic for the + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code> element. + </p> + </dd> +</dl> + +<!-- Intent Access --> +<h2 id="Intents">Intents and Data Access</h2> +<p> + Applications can access a content provider indirectly with an {@link android.content.Intent}. + The application does not call any of the methods of {@link android.content.ContentResolver} or + {@link android.content.ContentProvider}. Instead, it sends an intent that starts an activity, + which is often part of the provider's own application. The destination activity is in charge of + retrieving and displaying the data in its UI. Depending on the action in the intent, the + destination activity may also prompt the user to make modifications to the provider's data. + An intent may also contain "extras" data that the destination activity displays + in the UI; the user then has the option of changing this data before using it to modify the + data in the provider. +</p> +<p> + +</p> +<p> + You may want to use intent access to help ensure data integrity. Your provider may depend + on having data inserted, updated, and deleted according to strictly defined business logic. If + this is the case, allowing other applications to directly modify your data may lead to + invalid data. If you want developers to use intent access, be sure to document it thoroughly. + Explain to them why intent access using your own application's UI is better than trying to + modify the data with their code. +</p> +<p> + Handling an incoming intent that wishes to modify your provider's data is no different from + handling other intents. You can learn more about using intents by reading the topic + <a href="{@docRoot}guide/topics/intents/intents-filters.html">Intents and Intent Filters</a>. +</p> diff --git a/docs/html/guide/topics/providers/content-providers.jd b/docs/html/guide/topics/providers/content-providers.jd index 95331ce38519..1707f038b3ce 100644 --- a/docs/html/guide/topics/providers/content-providers.jd +++ b/docs/html/guide/topics/providers/content-providers.jd @@ -1,922 +1,96 @@ page.title=Content Providers @jd:body - <div id="qv-wrapper"> <div id="qv"> -<h2>In this document</h2> -<ol> -<li><a href="#basics">Content provider basics</a></li> -<li><a href="#querying">Querying a content provider</a></li> -<li><a href="#modifying">Modifying data in a provider</a></li> -<li><a href="#creating">Creating a content provider</a></li> -<li><a href="#urisum">Content URI summary</a></li> -</ol> -<h2>Key classes</h2> +<!-- In this document --> +<h2>Topics</h2> <ol> -<li>{@link android.content.ContentProvider}</li> -<li>{@link android.content.ContentResolver}</li> -<li>{@link android.database.Cursor}</li> + <li> + <a href="{@docRoot}guide/topics/providers/content-provider-basics.html"> + Content Provider Basics</a> + </li> + <li> + <a href="{@docRoot}guide/topics/providers/content-provider-creating.html"> + Creating a Content Provider</a> + </li> + <li> + <a href="{@docRoot}guide/topics/providers/calendar-provider.html">Calendar Provider</a> + </li> </ol> -<h2>See also</h2> -<ol> - <li><a href="{@docRoot}guide/topics/providers/calendar-provider.html">Calendar Provider</a></li> -</ol> + <!-- Related Samples --> +<h2>Related Samples</h2> + <ol> + <li> + <a href="{@docRoot}resources/samples/ContactManager/index.html"> + Contact Manager</a> application + </li> + <li> + <a + href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/view/List2.html"> + "Cursor (People)" + </a> + </li> + <li> + <a + href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/view/List7.html"> + "Cursor (Phones)"</a> + </li> + </ol> </div> </div> - -<p> -Content providers store and retrieve data and make it accessible to all -applications. They're the only way to share data across applications; there's -no common storage area that all Android packages can access. -</p> - -<p> -Android ships with a number of content providers for common data types -(audio, video, images, personal contact information, and so on). You can -see some of them listed in the {@link android.provider android.provider} -package. You can query these providers for the data they contain (although, -for some, you must acquire the proper permission to read the data). -</p> - -<p class="note"><strong>Note:</strong> Android 4.0 introduces the Calendar -Provider. For more information, see <a -href="{@docRoot}guide/topics/providers/calendar-provider.html">Calendar -Provider</a>.</p> -<p> -If you want to make your own data public, you have two options: You can -create your own content provider (a {@link android.content.ContentProvider} -subclass) or you can add the data to an existing provider — if there's -one that controls the same type of data and you have permission to write to it. -</p> - -<p> -This document is an introduction to using content providers. After a -brief discussion of the fundamentals, it explores how to query a content -provider, how to modify data controlled by a provider, and how to create -a content provider of your own. -</p> - - -<h2><a name="basics"></a>Content Provider Basics</h2> - -<p> -How a content provider actually stores its data under the covers is -up to its designer. But all content providers implement a common interface -for querying the provider and returning results — as well as for -adding, altering, and deleting data. -</p> - -<p> -It's an interface that clients use indirectly, most generally through -{@link android.content.ContentResolver} objects. You get a ContentResolver -by calling <code>{@link android.content.Context#getContentResolver -getContentResolver()}</code> from within the implementation of an Activity -or other application component: -</p> - -<pre>ContentResolver cr = getContentResolver();</pre> - -<p> -You can then use the ContentResolver's methods to interact with whatever -content providers you're interested in. -</p> - -<p> -When a query is initiated, the Android system identifies the content provider -that's the target of the query and makes sure that it is up and running. -The system instantiates all ContentProvider objects; you never need to do it -on your own. In fact, you never deal directly with ContentProvider objects -at all. Typically, there's just a single instance of each type of -ContentProvider. But it can communicate with multiple ContentResolver objects -in different applications and processes. The interaction between processes is -handled by the ContentResolver and ContentProvider classes. -</p> - - -<h3>The data model</h3> - -<p> -Content providers expose their data as a simple table on a database model, -where each row is a record and each column is data of a particular type -and meaning. For example, information about people and their phone numbers -might be exposed as follows: -</p> - -<table> - <tr> - <th scope="col">_ID</th> - <th scope="col">NUMBER</th> - <th scope="col">NUMBER_KEY</th> - <th scope="col">LABEL</th> - <th scope="col">NAME</th> - <th scope="col">TYPE</th> - </tr> - <tr> - <td>13</td> - <td>(425) 555 6677</td> - <td>425 555 6677</td> - <td>Kirkland office</td> - <td>Bully Pulpit</td> - <td>{@code TYPE_WORK}</td> - </tr> - <tr> - <td>44</td> - <td>(212) 555-1234</td> - <td>212 555 1234</td> - <td>NY apartment</td> - <td>Alan Vain</td> - <td>{@code TYPE_HOME}</td> - </tr> - <tr> - <td>45</td> - <td>(212) 555-6657</td> - <td>212 555 6657</td> - <td>Downtown office</td> - <td>Alan Vain</td> - <td>{@code TYPE_MOBILE}</td> - </tr> - <tr> - <td>53</td> - <td>201.555.4433</td> - <td>201 555 4433</td> - <td>Love Nest</td> - <td>Rex Cars</td> - <td>{@code TYPE_HOME}</td> - </tr> -</table> - -<p> -Every record includes a numeric {@code _ID} field that uniquely identifies -the record within the table. IDs can be used to match records in related -tables — for example, to find a person's phone number in one table -and pictures of that person in another. -</p> - -<p> -A query returns a {@link android.database.Cursor} object that can move from -record to record and column to column to read the contents of each field. -It has specialized methods for reading each type of data. So, to read a field, -you must know what type of data the field contains. (There's more on query -results and Cursor objects later.) -</p> - - -<h3><a name="uri"></a>URIs</h3> - -<p> -Each content provider exposes a public URI (wrapped as a {@link android.net.Uri} -object) that uniquely identifies its data set. A content provider that controls -multiple data sets (multiple tables) exposes a separate URI for each one. All -URIs for providers begin with the string "{@code content://}". The {@code content:} -scheme identifies the data as being controlled by a content provider. -</p> - -<p> -If you're defining a content provider, it's a good idea to also define a -constant for its URI, to simplify client code and make future updates cleaner. -Android defines {@code CONTENT_URI} constants for all the providers that come -with the platform. For example, the URI for the table that matches -phone numbers to people and the URI for the table that holds pictures of -people (both controlled by the Contacts content provider) are: -</p> - -<p> -<p style="margin-left: 2em">{@code android.provider.Contacts.Phones.CONTENT_URI} -<br/>{@code android.provider.Contacts.Photos.CONTENT_URI} -</p> - -<p> -The URI constant is used in all interactions with the content provider. -Every {@link android.content.ContentResolver} method takes the URI -as its first argument. It's what identifies which provider the ContentResolver -should talk to and which table of the provider is being targeted. -</p> - - -<h2><a name="querying"></a>Querying a Content Provider</h2> - -<p> -You need three pieces of information to query a content provider: -</p> - -<ul> -<li>The URI that identifies the provider</li> -<li>The names of the data fields you want to receive</li> -<li>The data types for those fields</li> -</ul> - -<p> -If you're querying a particular record, you also need the ID for that record. -</p> - - -<h3>Making the query</h3> - -<p> -To query a content provider, you can use either the -<code>{@link android.content.ContentResolver#query ContentResolver.query()}</code> -method or the <code>{@link android.app.Activity#managedQuery -Activity.managedQuery()}</code> method. -Both methods take the same set of arguments, and both return a -Cursor object. However, {@code managedQuery()} -causes the activity to manage the life cycle of the Cursor. A managed Cursor -handles all of the niceties, such as unloading itself when the activity pauses, -and requerying itself when the activity restarts. You can ask an Activity to -begin managing an unmanaged Cursor object for you by calling -<code>{@link android.app.Activity#startManagingCursor -Activity.startManagingCursor()}</code>. -</p> - -<p> -The first argument to either <code>{@link android.content.ContentResolver#query query()}</code> -or <code>{@link android.app.Activity#managedQuery managedQuery()}</code> is the provider URI -— the {@code CONTENT_URI} constant that identifies a particular -ContentProvider and data set (see <a href="#uri">URIs</a> earlier). -</p> - -<p> -To restrict a query to just one record, you can append the {@code _ID} value for -that record to the URI — that is, place a string matching the ID as the -last segment of the path part of the URI. For example, if the ID is 23, -the URI would be: -</p> - -<p style="margin-left: 2em">{@code content://. . . ./23}</p> - -<p> -There are some helper methods, particularly -<code>{@link android.content.ContentUris#withAppendedId -ContentUris.withAppendedId()}</code> and <code>{@link -android.net.Uri#withAppendedPath Uri.withAppendedPath()}</code>, -that make it easy to append an ID to a URI. Both are static methods that return -a Uri object with the ID added. So, for example, if you were looking for record -23 in the database of people contacts, you might construct a query as follows: -</p> - -<pre> -import android.provider.Contacts.People; -import android.content.ContentUris; -import android.net.Uri; -import android.database.Cursor; - -// Use the ContentUris method to produce the base URI for the contact with _ID == 23. -Uri myPerson = ContentUris.withAppendedId(People.CONTENT_URI, 23); - -// Alternatively, use the Uri method to produce the base URI. -// It takes a string rather than an integer. -Uri myPerson = Uri.withAppendedPath(People.CONTENT_URI, "23"); - -// Then query for this specific record: -Cursor cur = managedQuery(myPerson, null, null, null, null); -</pre> - -<p> -The other arguments to the <code>{@link android.content.ContentResolver#query query()}</code> -and <code>{@link android.app.Activity#managedQuery managedQuery()}</code> methods delimit -the query in more detail. They are: -</p> - -<ul> -<li>The names of the data columns that should be returned. A {@code null} -value returns all columns. Otherwise, only columns that are listed by name -are returned. All the content providers that come with the platform define -constants for their columns. For example, the -{@link android.provider.Contacts.Phones android.provider.Contacts.Phones} class -defines constants for the names of the columns in the phone table illustrated -earlier — {@code _ID}, {@code NUMBER}, {@code NUMBER_KEY}, {@code NAME}, -and so on.</li> - -<li><p>A filter detailing which rows to return, formatted as an SQL {@code WHERE} -clause (excluding the {@code WHERE} itself). A {@code null} value returns -all rows (unless the URI limits the query to a single record).</p></li> - -<li><p>Selection arguments.</p></li> - -<li><p>A sorting order for the rows that are returned, formatted as an SQL -{@code ORDER BY} clause (excluding the {@code ORDER BY} itself). A {@code null} -value returns the records in the default order for the table, which may be -unordered.</p></li> -</ul> - -<p> -Let's look at an example query to retrieve a list of contact names and their -primary phone numbers: -</p> - -<pre> -import android.provider.Contacts.People; -import android.database.Cursor; - -// Form an array specifying which columns to return. -String[] projection = new String[] { - People._ID, - People._COUNT, - People.NAME, - People.NUMBER - }; - -// Get the base URI for the People table in the Contacts content provider. -Uri contacts = People.CONTENT_URI; - -// Make the query. -Cursor managedCursor = managedQuery(contacts, - projection, // Which columns to return - null, // Which rows to return (all rows) - null, // Selection arguments (none) - // Put the results in ascending order by name - People.NAME + " ASC"); -</pre> - -<p> -This query retrieves data from the People table of the Contacts content -provider. It gets the name, primary phone number, and unique record ID for -each contact. It also reports the number of records that are returned as -the {@code _COUNT} field of each record. -</p> - -<p> -The constants for the names of the columns are defined in various interfaces -— {@code _ID} and {@code _COUNT} in -{@link android.provider.BaseColumns BaseColumns}, {@code NAME} in {@link android.provider.Contacts.PeopleColumns PeopleColumns}, and {@code NUMBER} -in {@link android.provider.Contacts.PhonesColumns PhoneColumns}. The -{@link android.provider.Contacts.People Contacts.People} class implements -each of these interfaces, which is why the code example above could refer -to them using just the class name. -</p> - - -<h3>What a query returns</h3> - -<p> -A query returns a set of zero or more database records. The names of the -columns, their default order, and their data types are specific to each -content provider. -But every provider has an {@code _ID} column, which holds a unique numeric -ID for each record. Every provider can also report the number -of records returned as the {@code _COUNT} column; its value -is the same for all rows. -</p> - -<p> -Here is an example result set for the query in the previous section: -</p> - -<table border="1"> - <tbody> - <tr> - <th scope="col">_ID</th> - <th scope="col">_COUNT</th> - <th scope="col">NAME</th> - <th scope="col">NUMBER</th> - </tr> - <tr> - <td>44</td> - <td>3</td> - <td>Alan Vain</td> - <td>212 555 1234</td> - </tr> - <tr> - <td>13</td> - <td>3</td> - <td>Bully Pulpit</td> - <td>425 555 6677</td> - </tr> - <tr> - <td>53</td> - <td>3</td> - <td>Rex Cars</td> - <td>201 555 4433</td> - </tr> - </tbody> -</table> - -<p> -The retrieved data is exposed by a {@link android.database.Cursor Cursor} -object that can be used to iterate backward or forward through the result -set. You can use this object only to read the data. To add, modify, or -delete data, you must use a ContentResolver object. -</p> - - -<h3>Reading retrieved data</h3> - -<p> -The Cursor object returned by a query provides access to a recordset of -results. If you have queried for a specific record by ID, this set will -contain only one value. Otherwise, it can contain multiple values. -(If there are no matches, it can also be empty.) You -can read data from specific fields in the record, but you must know the -data type of the field, because the Cursor object has a separate method -for reading each type of data — such as <code>{@link -android.database.Cursor#getString getString()}</code>, <code>{@link -android.database.Cursor#getInt getInt()}</code>, and <code>{@link -android.database.Cursor#getFloat getFloat()}</code>. -(However, for most types, if you call the method for reading strings, -the Cursor object will give you the String representation of the data.) -The Cursor lets you request the column name from the index of the column, -or the index number from the column name. -</p> - -<p> -The following snippet demonstrates reading names and phone numbers from -the query illustrated earlier: -</p> - -<pre> -import android.provider.Contacts.People; - -private void getColumnData(Cursor cur){ - if (cur.moveToFirst()) { - - String name; - String phoneNumber; - int nameColumn = cur.getColumnIndex(People.NAME); - int phoneColumn = cur.getColumnIndex(People.NUMBER); - String imagePath; - - do { - // Get the field values - name = cur.getString(nameColumn); - phoneNumber = cur.getString(phoneColumn); - - // Do something with the values. - ... - - } while (cur.moveToNext()); - - } -} -</pre> - -<p> -If a query can return binary data, such as an image or sound, the data -may be directly entered in the table or the table entry for that data may be -a string specifying a {@code content:} URI that you can use to get the data. -In general, smaller amounts of data (say, from 20 to 50K or less) are most often -directly entered in the table and can be read by calling -<code>{@link android.database.Cursor#getBlob Cursor.getBlob()}</code>. -It returns a byte array. -</p> - -<p> -If the table entry is a {@code content:} URI, you should never try to open -and read the file directly (for one thing, permissions problems can make this -fail). Instead, you should call -<code>{@link android.content.ContentResolver#openInputStream -ContentResolver.openInputStream()}</code> to get an -{@link java.io.InputStream} object that you can use to read the data. -</p> - - -<h2><a name="modifying"></a>Modifying Data</h2> - -<p> -Data kept by a content provider can be modified by: -</p> - -<ul> -<p><li>Adding new records</li> -<li>Adding new values to existing records</li> -<li>Batch updating existing records</li> -<li>Deleting records</li> -</ul> - -<p> -All data modification is accomplished using {@link android.content.ContentResolver} -methods. Some content providers require a more restrictive permission for writing -data than they do for reading it. If you don't have permission to write to a -content provider, the ContentResolver methods will fail. -</p> - - -<h3>Adding records</h3> - -<p> -To add a new record to a content provider, first set up a map of key-value pairs -in a {@link android.content.ContentValues} object, where each key matches -the name of a column in the content provider and the value is the desired -value for the new record in that column. Then call <code>{@link -android.content.ContentResolver#insert ContentResolver.insert()}</code> and pass -it the URI of the provider and the ContentValues map. This method returns -the full URI of the new record — that is, the provider's URI with -the appended ID for the new record. You can then use this URI to query and -get a Cursor over the new record, and to further modify the record. -Here's an example: -</p> - -<pre> -import android.provider.Contacts.People; -import android.content.ContentResolver; -import android.content.ContentValues; - -ContentValues values = new ContentValues(); - -// Add Abraham Lincoln to contacts and make him a favorite. -values.put(People.NAME, "Abraham Lincoln"); -// 1 = the new contact is added to favorites -// 0 = the new contact is not added to favorites -values.put(People.STARRED, 1); - -Uri uri = getContentResolver().insert(People.CONTENT_URI, values); -</pre> - - -<h3>Adding new values</h3> - -<p> -Once a record exists, you can add new information to it or modify -existing information. For example, the next step in the example above would -be to add contact information — like a phone number or an IM or e-mail -address — to the new entry. -</p> - -<p> -The best way to add to a record in the Contacts database is to append -the name of the table where the new data goes to the URI for the -record, then use the amended URI to add the new data values. Each -Contacts table exposes a name for this purpose as a {@code -CONTENT_DIRECTORY} constant. The following code continues the previous -example by adding a phone number and e-mail address for the record -just created: -</p> - -<pre> -Uri phoneUri = null; -Uri emailUri = null; - -// Add a phone number for Abraham Lincoln. Begin with the URI for -// the new record just returned by insert(); it ends with the _ID -// of the new record, so we don't have to add the ID ourselves. -// Then append the designation for the phone table to this URI, -// and use the resulting URI to insert the phone number. -phoneUri = Uri.withAppendedPath(uri, People.Phones.CONTENT_DIRECTORY); - -values.clear(); -values.put(People.Phones.TYPE, People.Phones.TYPE_MOBILE); -values.put(People.Phones.NUMBER, "1233214567"); -getContentResolver().insert(phoneUri, values); - -// Now add an email address in the same way. -emailUri = Uri.withAppendedPath(uri, People.ContactMethods.CONTENT_DIRECTORY); - -values.clear(); -// ContactMethods.KIND is used to distinguish different kinds of -// contact methods, such as email, IM, etc. -values.put(People.ContactMethods.KIND, Contacts.KIND_EMAIL); -values.put(People.ContactMethods.DATA, "test@example.com"); -values.put(People.ContactMethods.TYPE, People.ContactMethods.TYPE_HOME); -getContentResolver().insert(emailUri, values); -</pre> - -<p> -You can place small amounts of binary data into a table by calling -the version of <code>{@link android.content.ContentValues#put -ContentValues.put()}</code> that takes a byte array. -That would work for a small icon-like image or a short audio clip, for example. -However, if you have a large amount of binary data to add, such as a photograph -or a complete song, put a {@code content:} URI for the data in the table and call -<code>{@link android.content.ContentResolver#openOutputStream -ContentResolver.openOutputStream()}</code> -with the file's URI. (That causes the content provider to store the data -in a file and record the file path in a hidden field of the record.) -</p> - -<p> -In this regard, the {@link android.provider.MediaStore} content -provider, the main provider that dispenses image, audio, and video -data, employs a special convention: The same URI that is used with -{@code query()} or {@code managedQuery()} to get meta-information -about the binary data (such as, the caption of a photograph or the -date it was taken) is used with {@code openInputStream()} -to get the data itself. Similarly, the same URI that is used with -{@code insert()} to put meta-information into a MediaStore record -is used with {@code openOutputStream()} to place the binary data there. -The following code snippet illustrates this convention: -</p> - -<pre> -import android.provider.MediaStore.Images.Media; -import android.content.ContentValues; -import java.io.OutputStream; - -// Save the name and description of an image in a ContentValues map. -ContentValues values = new ContentValues(3); -values.put(Media.DISPLAY_NAME, "road_trip_1"); -values.put(Media.DESCRIPTION, "Day 1, trip to Los Angeles"); -values.put(Media.MIME_TYPE, "image/jpeg"); - -// Add a new record without the bitmap, but with the values just set. -// insert() returns the URI of the new record. -Uri uri = getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, values); - -// Now get a handle to the file for that record, and save the data into it. -// Here, sourceBitmap is a Bitmap object representing the file to save to the database. -try { - OutputStream outStream = getContentResolver().openOutputStream(uri); - sourceBitmap.compress(Bitmap.CompressFormat.JPEG, 50, outStream); - outStream.close(); -} catch (Exception e) { - Log.e(TAG, "exception while writing image", e); -} -</pre> - - -<h3>Batch updating records</h3> - -<p> -To batch update a group of records (for example, to change "NY" to "New York" -in all fields), call the <code>{@link -android.content.ContentResolver#update ContentResolver.update()}</code> -method with the columns and values to change. -</p> - - -<h3><a name="deletingrecord"></a>Deleting a record</h3> - -<p> -To delete a single record, call {<code>{@link -android.content.ContentResolver#delete ContentResolver.delete()}</code> -with the URI of a specific row. -</p> - <p> -To delete multiple rows, call <code>{@link -android.content.ContentResolver#delete ContentResolver.delete()}</code> -with the URI of the type of record to delete (for example, {@code android.provider.Contacts.People.CONTENT_URI}) and an SQL {@code WHERE} -clause defining which rows to delete. (<i><b>Caution</b>: -Be sure to include a valid {@code WHERE} clause if you're deleting a general -type, or you risk deleting more records than you intended!</i>). -</p> - - -<h2><a name="creating"></a>Creating a Content Provider</h2> - -<p> -To create a content provider, you must: -</p> - -<ul> -<li>Set up a system for storing the data. Most content providers -store their data using Android's file storage methods or SQLite databases, -but you can store your data any way you want. Android provides the -{@link android.database.sqlite.SQLiteOpenHelper SQLiteOpenHelper} -class to help you create a database and {@link -android.database.sqlite.SQLiteDatabase SQLiteDatabase} to manage it.</li> - -<li><p>Extend the {@link android.content.ContentProvider} class to provide -access to the data.</p></li> - -<li><p>Declare the content provider in the manifest file for your -application (AndroidManifest.xml).</p></li> -</ul> - -<p> -The following sections have notes on the last two of these tasks. -</p> - - -<h3>Extending the ContentProvider class</h3> - -<p> -You define a {@link android.content.ContentProvider} subclass to -expose your data to others using the conventions expected by -ContentResolver and Cursor objects. Principally, this means -implementing six abstract methods declared in the ContentProvider class: -</p> - -<p style="margin-left: 2em">{@code query()} -<br/>{@code insert()} -<br/>{@code update()} -<br/>{@code delete()} -<br/>{@code getType()} -<br/>{@code onCreate()}</p> - -<p> -The {@code query()} method must return a {@link android.database.Cursor} object -that can iterate over the requested data. Cursor itself is an interface, but -Android provides some ready-made Cursor objects that you can use. For example, -{@link android.database.sqlite.SQLiteCursor} can iterate over data stored in -an SQLite database. You get the Cursor object by calling any of the {@link -android.database.sqlite.SQLiteDatabase SQLiteDatabase} class's {@code query()} -methods. There are other Cursor implementations — such as {@link -android.database.MatrixCursor} — for data not stored in a database. -</p> - -<p> -Because these ContentProvider methods can be called from -various ContentResolver objects in different processes and threads, -they must be implemented in a thread-safe manner. -</p> - -<p> -As a courtesy, you might also want to call <code>{@link android.content.ContentResolver#notifyChange(android.net.Uri,android.database.ContentObserver) -ContentResolver.notifyChange()}</code> to notify listeners when there are -modifications to the data. -</p> - -<p> -Beyond defining the subclass itself, there are other steps you should take -to simplify the work of clients and make the class more accessible: -</p> - -<ul> -<li>Define a {@code public static final} {@link android.net.Uri} -named {@code CONTENT_URI}. This is the string that represents the full -{@code content:} URI that your content provider handles. You must define a -unique string for this value. The best solution is to use the fully-qualified -class name of the content provider (made lowercase). So, for example, the -URI for a TransportationProvider class could be defined as follows: - -<pre>public static final Uri CONTENT_URI = - Uri.parse("content://com.example.codelab.transportationprovider");</pre> - -<p> -If the provider has subtables, also define {@code CONTENT_URI} constants for -each of the subtables. These URIs should all have the same authority (since -that identifies the content provider), and be distinguished only by their paths. -For example: -</p> - -<p style="margin-left: 2em">{@code content://com.example.codelab.transportationprovider/train} -<br/>{@code content://com.example.codelab.transportationprovider/air/domestic} -<br/>{@code content://com.example.codelab.transportationprovider/air/international}</p> - -<p> -For an overview of {@code content:} URIs, see the <a href="#urisum">Content URI -Summary</a> at the end of this document. -</p></li> - -<li><p>Define the column names that the content provider will return to clients. -If you are using an underlying database, these column names are typically -identical to the SQL database column names they represent. Also define -{@code public static} String constants that clients can use to specify -the columns in queries and other instructions. -</p> - -<p> -Be sure to include an integer column named "{@code _id}" -(with the constant {@code _ID}) for -the IDs of the records. You should have this field whether or not you have -another field (such as a URL) that is also unique among all records. If -you're using the SQLite database, the {@code _ID} field should be the -following type: -</p> - -<p style="margin-left: 2em">{@code INTEGER PRIMARY KEY AUTOINCREMENT}</p> - -<p> -The {@code AUTOINCREMENT} descriptor is optional. But without it, SQLite -increments an ID counter field to the next number above the largest -existing number in the column. If you delete the last row, the next row added -will have the same ID as the deleted row. {@code AUTOINCREMENT} avoids this -by having SQLite increment to the next largest value whether deleted or not. -</p> -</li> - -<li><p>Carefully document the data type of each column. Clients need this -information to read the data.</p></li> - -<li><p>If you are handling a new data type, you must define a new MIME type -to return in your implementation of <code>{@link -android.content.ContentProvider#getType ContentProvider.getType()}</code>. -The type depends in part on whether or not the {@code content:} URI submitted -to {@code getType()} limits the request to a specific record. There's one -form of the MIME type for a single record and another for multiple records. -Use the {@link android.net.Uri Uri} methods to help determine what is being -requested. Here is the general format for each type:</p></li> - -<ul> -<li><p>For a single record: {@code vnd.android.cursor.item/vnd.<em>yourcompanyname.contenttype</em>}</p> - -<p>For example, a request for train record 122, like this URI,</p> -<p style="margin-left: 2em">{@code content://com.example.transportationprovider/trains/122}</p> - -<p>might return this MIME type:</p> -<p style="margin-left: 2em">{@code vnd.android.cursor.item/vnd.example.rail}</p> -</li> - -<li><p>For multiple records: {@code vnd.android.cursor.dir/vnd.<em>yourcompanyname.contenttype</em>}</p> - -<p>For example, a request for all train records, like the following URI,</p> -<p style="margin-left: 2em">{@code content://com.example.transportationprovider/trains}</p> - -<p>might return this MIME type:</p> -<p style="margin-left: 2em">{@code vnd.android.cursor.dir/vnd.example.rail}</p> -</li> -</ul> - -<li><p>If you are exposing byte data that's too big to put in the table itself -— such as a large bitmap file — the field that exposes the -data to clients should actually contain a {@code content:} URI string. -This is the field that gives clients access to the data file. The record -should also have another field, named "{@code _data}" that lists the exact file -path on the device for that file. This field is not intended to be read by -the client, but by the ContentResolver. The client will call <code>{@link -android.content.ContentResolver#openInputStream ContentResolver.openInputStream()}</code> -on the user-facing field holding the URI for the item. The ContentResolver -will request the "{@code _data}" field for that record, and because -it has higher permissions than a client, it should be able to access -that file directly and return a read wrapper for the file to the client.</p></li> - -</ul> - -<p> -For an example of a private content provider implementation, see the -NodePadProvider class in the Notepad sample application that ships with the SDK. -</p> - - -<h3>Declaring the content provider</h3> - -<p> -To let the Android system know about the content provider you've developed, -declare it with a {@code <provider>} element in the application's -AndroidManifest.xml file. Content providers that are not declared in the -manifest are not visible to the Android system -</p> - -<p> -The {@code name} attribute is the fully qualified name of the ContentProvider -subclass. The {@code authorities} attribute is the authority part of the -{@code content:} URI that identifies the provider. -For example if the ContentProvider subclass is AutoInfoProvider, the -{@code <provider>} element might look like this: -</p> - -<pre> -<provider android:name="com.example.autos.AutoInfoProvider" - android:authorities="com.example.autos.autoinfoprovider" - . . . /> -</provider> -</pre> - -<p> -Note that the {@code authorities} attribute omits the path part of a -{@code content:} URI. For example, if AutoInfoProvider controlled subtables -for different types of autos or different manufacturers, -</p> - -<p style="margin-left: 2em">{@code content://com.example.autos.autoinfoprovider/honda} -<br/>{@code content://com.example.autos.autoinfoprovider/gm/compact} -<br/>{@code content://com.example.autos.autoinfoprovider/gm/suv}</p> - -<p> -those paths would not be declared in the manifest. The authority is what -identifies the provider, not the path; your provider can interpret the path -part of the URI in any way you choose. -</p> - -<p> -Other {@code <provider>} attributes can set permissions to read and -write data, provide for an icon and text that can be displayed to users, -enable and disable the provider, and so on. Set the {@code multiprocess} -attribute to "{@code true}" if data does not need to be synchronized between -multiple running versions of the content provider. This permits an instance -of the provider to be created in each client process, eliminating the need -to perform IPC. -</p> - - -<h2><a name="urisum"></a>Content URI Summary</h2> - -<p> -Here is a recap of the important parts of a content URI: -</p> - -<p> -<img src="{@docRoot}images/content_uri.png" alt="Elements of a content URI" -height="80" width="528"> -</p> - -<ol type="A"> -<li>Standard prefix indicating that the data is controlled by a -content provider. It's never modified.</li> - -<li><p>The authority part of the URI; it identifies the content provider. -For third-party applications, this should be a fully-qualified class name -(reduced to lowercase) to ensure uniqueness. The authority is declared in -the {@code <provider>} element's {@code authorities} attribute:</p> - -<pre><provider android:name=".TransportationProvider" - android:authorities="com.example.transportationprovider" - . . . ></pre></li> - -<li><p>The path that the content provider uses to determine what kind of data is -being requested. This can be zero or more segments long. If the content provider -exposes only one type of data (only trains, for example), it can be absent. -If the provider exposes several types, including subtypes, it can be several -segments long — for example, "{@code land/bus}", "{@code land/train}", -"{@code sea/ship}", and "{@code sea/submarine}" to give four possibilities.</p></li> - -<li><p>The ID of the specific record being requested, if any. This is the -{@code _ID} value of the requested record. If the request is not limited to -a single record, this segment and the trailing slash are omitted:</p> - -<p style="margin-left: 2em">{@code content://com.example.transportationprovider/trains}</p> -</li> -</ol> - - + Content providers manage access to a structured set of data. They encapsulate the + data, and provide mechanisms for defining data security. Content providers are the standard + interface that connects data in one process with code running in another process. +</p> +<p> + When you want to access data in a content provider, you use the + {@link android.content.ContentResolver} object in your + application's {@link android.content.Context} to communicate with the provider as a client. + The {@link android.content.ContentResolver} object communicates with the provider object, an + instance of a class that implements {@link android.content.ContentProvider}. The provider + object receives data requests from clients, performs the requested action, and + returns the results. +</p> +<p> + You don't need to develop your own provider if you don't intend to share your data with + other applications. However, you do need your own provider to provide custom search + suggestions in your own application. You also need your own provider if you want to copy and + paste complex data or files from your application to other applications. +</p> +<p> + Android itself includes content providers that manage data such as audio, video, images, and + personal contact information. You can see some of them listed in the reference + documentation for the + <code><a href="{@docRoot}reference/android/provider/package-summary.html">android.provider</a> + </code> package. With some restrictions, these providers are accessible to any Android + application. +</p><p> + The following topics describe content providers in more detail: +</p> +<dl> + <dt> + <strong><a href="{@docRoot}guide/topics/providers/content-provider-basics.html"> + Content Provider Basics</a></strong> + </dt> + <dd> + How to access data in a content provider when the data is organized in tables. + </dd> + <dt> + <strong><a href="{@docRoot}guide/topics/providers/content-provider-creating.html"> + Creating a Content Provider</a></strong> + </dt> + <dd> + How to create your own content provider. + </dd> + <dt> + <strong><a href="{@docRoot}guide/topics/providers/calendar-provider.html"> + Calendar Provider</a></strong> + </dt> + <dd> + How to access the Calendar Provider that is part of the Android platform. + </dd> +</dl> diff --git a/docs/html/shareables/adl/2010Q2_Business_Overview.pdf b/docs/html/shareables/adl/2010Q2_Business_Overview.pdf Binary files differdeleted file mode 100644 index c34ea1566736..000000000000 --- a/docs/html/shareables/adl/2010Q2_Business_Overview.pdf +++ /dev/null diff --git a/docs/html/shareables/adl/2010Q2_Market_Overview.pdf b/docs/html/shareables/adl/2010Q2_Market_Overview.pdf Binary files differdeleted file mode 100644 index 3752258d2d35..000000000000 --- a/docs/html/shareables/adl/2010Q2_Market_Overview.pdf +++ /dev/null diff --git a/docs/html/shareables/adl/2010Q2_SDK_Overview.pdf b/docs/html/shareables/adl/2010Q2_SDK_Overview.pdf Binary files differdeleted file mode 100644 index 8796584d2ded..000000000000 --- a/docs/html/shareables/adl/2010Q2_SDK_Overview.pdf +++ /dev/null diff --git a/docs/html/shareables/adl/2011Q3_Android_Market_for_Developers.pdf b/docs/html/shareables/adl/2011Q3_Android_Market_for_Developers.pdf Binary files differdeleted file mode 100644 index 598a27e7c4e7..000000000000 --- a/docs/html/shareables/adl/2011Q3_Android_Market_for_Developers.pdf +++ /dev/null diff --git a/docs/html/shareables/adl/2011Q3_Designing_UIs_for_Phones_and_Tablets.pdf b/docs/html/shareables/adl/2011Q3_Designing_UIs_for_Phones_and_Tablets.pdf Binary files differdeleted file mode 100644 index 6ef41dd8271b..000000000000 --- a/docs/html/shareables/adl/2011Q3_Designing_UIs_for_Phones_and_Tablets.pdf +++ /dev/null diff --git a/docs/html/shareables/adl/2011Q3_Introduction_to_Honeycomb_APIs.pdf b/docs/html/shareables/adl/2011Q3_Introduction_to_Honeycomb_APIs.pdf Binary files differdeleted file mode 100755 index e56d23779dff..000000000000 --- a/docs/html/shareables/adl/2011Q3_Introduction_to_Honeycomb_APIs.pdf +++ /dev/null diff --git a/docs/html/shareables/icon_templates-v1.0.zip b/docs/html/shareables/icon_templates-v1.0.zip Binary files differdeleted file mode 100644 index 94fbcdcea6cc..000000000000 --- a/docs/html/shareables/icon_templates-v1.0.zip +++ /dev/null diff --git a/include/media/stagefright/OMXCodec.h b/include/media/stagefright/OMXCodec.h index 84f8282f64b7..4c30e0437bc7 100644 --- a/include/media/stagefright/OMXCodec.h +++ b/include/media/stagefright/OMXCodec.h @@ -172,6 +172,7 @@ private: uint32_t mFlags; bool mIsEncoder; + bool mIsVideo; char *mMIME; char *mComponentName; sp<MetaData> mOutputFormat; diff --git a/include/utils/ResourceTypes.h b/include/utils/ResourceTypes.h index 46420c1316bf..b741ed6142bd 100644 --- a/include/utils/ResourceTypes.h +++ b/include/utils/ResourceTypes.h @@ -843,6 +843,8 @@ struct ResTable_config DENSITY_MEDIUM = ACONFIGURATION_DENSITY_MEDIUM, DENSITY_TV = ACONFIGURATION_DENSITY_TV, DENSITY_HIGH = ACONFIGURATION_DENSITY_HIGH, + DENSITY_XHIGH = ACONFIGURATION_DENSITY_XHIGH, + DENSITY_XXHIGH = ACONFIGURATION_DENSITY_XXHIGH, DENSITY_NONE = ACONFIGURATION_DENSITY_NONE }; diff --git a/libs/rs/driver/rsdRuntimeMath.cpp b/libs/rs/driver/rsdRuntimeMath.cpp index e31553969329..753ef7356ab7 100644 --- a/libs/rs/driver/rsdRuntimeMath.cpp +++ b/libs/rs/driver/rsdRuntimeMath.cpp @@ -329,6 +329,16 @@ static int32_t SC_AtomicXor(volatile int32_t *ptr, int32_t value) { return prev; } +static uint32_t SC_AtomicUMin(volatile uint32_t *ptr, uint32_t value) { + uint32_t prev, status; + do { + prev = *ptr; + uint32_t n = rsMin(value, prev); + status = android_atomic_release_cas((int32_t) prev, (int32_t)n, (volatile int32_t*) ptr); + } while (CC_UNLIKELY(status != 0)); + return prev; +} + static int32_t SC_AtomicMin(volatile int32_t *ptr, int32_t value) { int32_t prev, status; do { @@ -339,6 +349,16 @@ static int32_t SC_AtomicMin(volatile int32_t *ptr, int32_t value) { return prev; } +static uint32_t SC_AtomicUMax(volatile uint32_t *ptr, uint32_t value) { + uint32_t prev, status; + do { + prev = *ptr; + uint32_t n = rsMax(value, prev); + status = android_atomic_release_cas((int32_t) prev, (int32_t) n, (volatile int32_t*) ptr); + } while (CC_UNLIKELY(status != 0)); + return prev; +} + static int32_t SC_AtomicMax(volatile int32_t *ptr, int32_t value) { int32_t prev, status; do { @@ -524,9 +544,9 @@ static RsdSymbolTable gSyms[] = { { "_Z11rsAtomicXorPVii", (void *)&SC_AtomicXor, true }, { "_Z11rsAtomicXorPVjj", (void *)&SC_AtomicXor, true }, { "_Z11rsAtomicMinPVii", (void *)&SC_AtomicMin, true }, - { "_Z11rsAtomicMinPVjj", (void *)&SC_AtomicMin, true }, + { "_Z11rsAtomicMinPVjj", (void *)&SC_AtomicUMin, true }, { "_Z11rsAtomicMaxPVii", (void *)&SC_AtomicMax, true }, - { "_Z11rsAtomicMaxPVjj", (void *)&SC_AtomicMax, true }, + { "_Z11rsAtomicMaxPVjj", (void *)&SC_AtomicUMax, true }, { "_Z11rsAtomicCasPViii", (void *)&SC_AtomicCas, true }, { "_Z11rsAtomicCasPVjjj", (void *)&SC_AtomicCas, true }, diff --git a/libs/rs/scriptc/rs_atomic.rsh b/libs/rs/scriptc/rs_atomic.rsh index 87c6c021b491..a455edd4ae63 100644 --- a/libs/rs/scriptc/rs_atomic.rsh +++ b/libs/rs/scriptc/rs_atomic.rsh @@ -242,7 +242,7 @@ extern int32_t __attribute__((overloadable)) * @return old value */ extern uint32_t __attribute__((overloadable)) - rsAtomicCas(volatile uint32_t* addr, int32_t compareValue, int32_t newValue); + rsAtomicCas(volatile uint32_t* addr, uint32_t compareValue, uint32_t newValue); #endif //defined(RS_VERSION) && (RS_VERSION >= 14) diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index 7b2f1b7dca59..7d4c28285851 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -840,7 +840,10 @@ public class AudioTrack /** * Stops playing the audio data. - * + * When used on an instance created in {@link #MODE_STREAM} mode, audio will stop playing + * after the last buffer that was written has been played. For an immediate stop, use + * {@link #pause()}, followed by {@link #flush()} to discard audio data that hasn't been played + * back yet. * @throws IllegalStateException */ public void stop() @@ -859,7 +862,7 @@ public class AudioTrack /** * Pauses the playback of the audio data. Data that has not been played * back will not be discarded. Subsequent calls to {@link #play} will play - * this data back. + * this data back. See {@link #flush()} to discard this data. * * @throws IllegalStateException */ diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk index 0aeb515d0181..4d61067508c8 100644 --- a/media/libstagefright/Android.mk +++ b/media/libstagefright/Android.mk @@ -77,7 +77,6 @@ LOCAL_SHARED_LIBRARIES := \ LOCAL_STATIC_LIBRARIES := \ libstagefright_color_conversion \ - libstagefright_aacenc \ libstagefright_amrnbenc \ libstagefright_amrwbenc \ libstagefright_avcenc \ diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp index 60d9bb727800..7597f642ac50 100755 --- a/media/libstagefright/OMXCodec.cpp +++ b/media/libstagefright/OMXCodec.cpp @@ -18,7 +18,6 @@ #define LOG_TAG "OMXCodec" #include <utils/Log.h> -#include "include/AACEncoder.h" #include "include/AMRNBEncoder.h" #include "include/AMRWBEncoder.h" #include "include/AVCEncoder.h" @@ -73,7 +72,6 @@ static sp<MediaSource> Make##name(const sp<MediaSource> &source, const sp<MetaDa FACTORY_CREATE_ENCODER(AMRNBEncoder) FACTORY_CREATE_ENCODER(AMRWBEncoder) -FACTORY_CREATE_ENCODER(AACEncoder) FACTORY_CREATE_ENCODER(AVCEncoder) FACTORY_CREATE_ENCODER(M4vH263Encoder) @@ -88,7 +86,6 @@ static sp<MediaSource> InstantiateSoftwareEncoder( static const FactoryInfo kFactoryInfo[] = { FACTORY_REF(AMRNBEncoder) FACTORY_REF(AMRWBEncoder) - FACTORY_REF(AACEncoder) FACTORY_REF(AVCEncoder) FACTORY_REF(M4vH263Encoder) }; @@ -153,7 +150,7 @@ static const CodecInfo kEncoderInfo[] = { { MEDIA_MIMETYPE_AUDIO_AMR_WB, "OMX.TI.WBAMR.encode" }, { MEDIA_MIMETYPE_AUDIO_AMR_WB, "AMRWBEncoder" }, { MEDIA_MIMETYPE_AUDIO_AAC, "OMX.TI.AAC.encode" }, - { MEDIA_MIMETYPE_AUDIO_AAC, "AACEncoder" }, + { MEDIA_MIMETYPE_AUDIO_AAC, "OMX.google.aac.encoder" }, { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.TI.DUCATI1.VIDEO.MPEG4E" }, { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.qcom.7x30.video.encoder.mpeg4" }, { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.qcom.video.encoder.mpeg4" }, @@ -1487,6 +1484,7 @@ OMXCodec::OMXCodec( mQuirks(quirks), mFlags(flags), mIsEncoder(isEncoder), + mIsVideo(!strncasecmp("video/", mime, 6)), mMIME(strdup(mime)), mComponentName(strdup(componentName)), mSource(source), @@ -2192,7 +2190,7 @@ error: } int64_t OMXCodec::retrieveDecodingTimeUs(bool isCodecSpecific) { - CHECK(mIsEncoder); + CHECK(mIsEncoder && mIsVideo); if (mDecodingTimeList.empty()) { CHECK(mSignalledEOS || mNoMoreOutputData); @@ -2387,7 +2385,7 @@ void OMXCodec::on_message(const omx_message &msg) { mNoMoreOutputData = true; } - if (mIsEncoder) { + if (mIsEncoder && mIsVideo) { int64_t decodingTimeUs = retrieveDecodingTimeUs(isCodecSpecific); buffer->meta_data()->setInt64(kKeyDecodingTime, decodingTimeUs); } @@ -3249,7 +3247,7 @@ bool OMXCodec::drainInputBuffer(BufferInfo *info) { int64_t lastBufferTimeUs; CHECK(srcBuffer->meta_data()->findInt64(kKeyTime, &lastBufferTimeUs)); CHECK(lastBufferTimeUs >= 0); - if (mIsEncoder) { + if (mIsEncoder && mIsVideo) { mDecodingTimeList.push_back(lastBufferTimeUs); } diff --git a/media/libstagefright/codecs/aacenc/Android.mk b/media/libstagefright/codecs/aacenc/Android.mk index 8318ba4479fb..34a27967ec87 100644 --- a/media/libstagefright/codecs/aacenc/Android.mk +++ b/media/libstagefright/codecs/aacenc/Android.mk @@ -85,3 +85,29 @@ LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/asm/ARMV7 endif include $(BUILD_STATIC_LIBRARY) + +################################################################################ + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + SoftAACEncoder.cpp + +LOCAL_C_INCLUDES := \ + frameworks/base/media/libstagefright/include \ + frameworks/base/include/media/stagefright/openmax \ + frameworks/base/media/libstagefright/codecs/common/include \ + +LOCAL_CFLAGS := -DOSCL_IMPORT_REF= + +LOCAL_STATIC_LIBRARIES := \ + libstagefright_aacenc + +LOCAL_SHARED_LIBRARIES := \ + libstagefright_omx libstagefright_foundation libutils \ + libstagefright_enc_common + +LOCAL_MODULE := libstagefright_soft_aacenc +LOCAL_MODULE_TAGS := optional + +include $(BUILD_SHARED_LIBRARY) diff --git a/media/libstagefright/codecs/aacenc/SoftAACEncoder.cpp b/media/libstagefright/codecs/aacenc/SoftAACEncoder.cpp new file mode 100644 index 000000000000..c6724c260d56 --- /dev/null +++ b/media/libstagefright/codecs/aacenc/SoftAACEncoder.cpp @@ -0,0 +1,560 @@ +/* + * Copyright (C) 2012 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "SoftAACEncoder" +#include <utils/Log.h> + +#include "SoftAACEncoder.h" + +#include "voAAC.h" +#include "cmnMemory.h" + +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/hexdump.h> + +namespace android { + +template<class T> +static void InitOMXParams(T *params) { + params->nSize = sizeof(T); + params->nVersion.s.nVersionMajor = 1; + params->nVersion.s.nVersionMinor = 0; + params->nVersion.s.nRevision = 0; + params->nVersion.s.nStep = 0; +} + +SoftAACEncoder::SoftAACEncoder( + const char *name, + const OMX_CALLBACKTYPE *callbacks, + OMX_PTR appData, + OMX_COMPONENTTYPE **component) + : SimpleSoftOMXComponent(name, callbacks, appData, component), + mEncoderHandle(NULL), + mApiHandle(NULL), + mMemOperator(NULL), + mNumChannels(1), + mSampleRate(44100), + mBitRate(0), + mSentCodecSpecificData(false), + mInputSize(0), + mInputFrame(NULL), + mInputTimeUs(-1ll), + mSawInputEOS(false), + mSignalledError(false) { + initPorts(); + CHECK_EQ(initEncoder(), (status_t)OK); + + setAudioParams(); +} + +SoftAACEncoder::~SoftAACEncoder() { + delete[] mInputFrame; + mInputFrame = NULL; + + if (mEncoderHandle) { + CHECK_EQ(VO_ERR_NONE, mApiHandle->Uninit(mEncoderHandle)); + mEncoderHandle = NULL; + } + + delete mApiHandle; + mApiHandle = NULL; + + delete mMemOperator; + mMemOperator = NULL; +} + +void SoftAACEncoder::initPorts() { + OMX_PARAM_PORTDEFINITIONTYPE def; + InitOMXParams(&def); + + def.nPortIndex = 0; + def.eDir = OMX_DirInput; + def.nBufferCountMin = kNumBuffers; + def.nBufferCountActual = def.nBufferCountMin; + def.nBufferSize = kNumSamplesPerFrame * sizeof(int16_t) * 2; + def.bEnabled = OMX_TRUE; + def.bPopulated = OMX_FALSE; + def.eDomain = OMX_PortDomainAudio; + def.bBuffersContiguous = OMX_FALSE; + def.nBufferAlignment = 1; + + def.format.audio.cMIMEType = const_cast<char *>("audio/raw"); + def.format.audio.pNativeRender = NULL; + def.format.audio.bFlagErrorConcealment = OMX_FALSE; + def.format.audio.eEncoding = OMX_AUDIO_CodingPCM; + + addPort(def); + + def.nPortIndex = 1; + def.eDir = OMX_DirOutput; + def.nBufferCountMin = kNumBuffers; + def.nBufferCountActual = def.nBufferCountMin; + def.nBufferSize = 8192; + def.bEnabled = OMX_TRUE; + def.bPopulated = OMX_FALSE; + def.eDomain = OMX_PortDomainAudio; + def.bBuffersContiguous = OMX_FALSE; + def.nBufferAlignment = 2; + + def.format.audio.cMIMEType = const_cast<char *>("audio/aac"); + def.format.audio.pNativeRender = NULL; + def.format.audio.bFlagErrorConcealment = OMX_FALSE; + def.format.audio.eEncoding = OMX_AUDIO_CodingAAC; + + addPort(def); +} + +status_t SoftAACEncoder::initEncoder() { + mApiHandle = new VO_AUDIO_CODECAPI; + + if (VO_ERR_NONE != voGetAACEncAPI(mApiHandle)) { + ALOGE("Failed to get api handle"); + return UNKNOWN_ERROR; + } + + mMemOperator = new VO_MEM_OPERATOR; + mMemOperator->Alloc = cmnMemAlloc; + mMemOperator->Copy = cmnMemCopy; + mMemOperator->Free = cmnMemFree; + mMemOperator->Set = cmnMemSet; + mMemOperator->Check = cmnMemCheck; + + VO_CODEC_INIT_USERDATA userData; + memset(&userData, 0, sizeof(userData)); + userData.memflag = VO_IMF_USERMEMOPERATOR; + userData.memData = (VO_PTR) mMemOperator; + if (VO_ERR_NONE != + mApiHandle->Init(&mEncoderHandle, VO_AUDIO_CodingAAC, &userData)) { + ALOGE("Failed to init AAC encoder"); + return UNKNOWN_ERROR; + } + + return OK; +} + +OMX_ERRORTYPE SoftAACEncoder::internalGetParameter( + OMX_INDEXTYPE index, OMX_PTR params) { + switch (index) { + case OMX_IndexParamAudioPortFormat: + { + OMX_AUDIO_PARAM_PORTFORMATTYPE *formatParams = + (OMX_AUDIO_PARAM_PORTFORMATTYPE *)params; + + if (formatParams->nPortIndex > 1) { + return OMX_ErrorUndefined; + } + + if (formatParams->nIndex > 0) { + return OMX_ErrorNoMore; + } + + formatParams->eEncoding = + (formatParams->nPortIndex == 0) + ? OMX_AUDIO_CodingPCM : OMX_AUDIO_CodingAAC; + + return OMX_ErrorNone; + } + + case OMX_IndexParamAudioAac: + { + OMX_AUDIO_PARAM_AACPROFILETYPE *aacParams = + (OMX_AUDIO_PARAM_AACPROFILETYPE *)params; + + if (aacParams->nPortIndex != 1) { + return OMX_ErrorUndefined; + } + + aacParams->nBitRate = mBitRate; + aacParams->nAudioBandWidth = 0; + aacParams->nAACtools = 0; + aacParams->nAACERtools = 0; + aacParams->eAACProfile = OMX_AUDIO_AACObjectMain; + aacParams->eAACStreamFormat = OMX_AUDIO_AACStreamFormatMP4FF; + aacParams->eChannelMode = OMX_AUDIO_ChannelModeStereo; + + aacParams->nChannels = mNumChannels; + aacParams->nSampleRate = mSampleRate; + aacParams->nFrameLength = 0; + + return OMX_ErrorNone; + } + + case OMX_IndexParamAudioPcm: + { + OMX_AUDIO_PARAM_PCMMODETYPE *pcmParams = + (OMX_AUDIO_PARAM_PCMMODETYPE *)params; + + if (pcmParams->nPortIndex != 0) { + return OMX_ErrorUndefined; + } + + pcmParams->eNumData = OMX_NumericalDataSigned; + pcmParams->eEndian = OMX_EndianBig; + pcmParams->bInterleaved = OMX_TRUE; + pcmParams->nBitPerSample = 16; + pcmParams->ePCMMode = OMX_AUDIO_PCMModeLinear; + pcmParams->eChannelMapping[0] = OMX_AUDIO_ChannelLF; + pcmParams->eChannelMapping[1] = OMX_AUDIO_ChannelRF; + + pcmParams->nChannels = mNumChannels; + pcmParams->nSamplingRate = mSampleRate; + + return OMX_ErrorNone; + } + + default: + return SimpleSoftOMXComponent::internalGetParameter(index, params); + } +} + +OMX_ERRORTYPE SoftAACEncoder::internalSetParameter( + OMX_INDEXTYPE index, const OMX_PTR params) { + switch (index) { + case OMX_IndexParamStandardComponentRole: + { + const OMX_PARAM_COMPONENTROLETYPE *roleParams = + (const OMX_PARAM_COMPONENTROLETYPE *)params; + + if (strncmp((const char *)roleParams->cRole, + "audio_encoder.aac", + OMX_MAX_STRINGNAME_SIZE - 1)) { + return OMX_ErrorUndefined; + } + + return OMX_ErrorNone; + } + + case OMX_IndexParamAudioPortFormat: + { + const OMX_AUDIO_PARAM_PORTFORMATTYPE *formatParams = + (const OMX_AUDIO_PARAM_PORTFORMATTYPE *)params; + + if (formatParams->nPortIndex > 1) { + return OMX_ErrorUndefined; + } + + if (formatParams->nIndex > 0) { + return OMX_ErrorNoMore; + } + + if ((formatParams->nPortIndex == 0 + && formatParams->eEncoding != OMX_AUDIO_CodingPCM) + || (formatParams->nPortIndex == 1 + && formatParams->eEncoding != OMX_AUDIO_CodingAAC)) { + return OMX_ErrorUndefined; + } + + return OMX_ErrorNone; + } + + case OMX_IndexParamAudioAac: + { + OMX_AUDIO_PARAM_AACPROFILETYPE *aacParams = + (OMX_AUDIO_PARAM_AACPROFILETYPE *)params; + + if (aacParams->nPortIndex != 1) { + return OMX_ErrorUndefined; + } + + mBitRate = aacParams->nBitRate; + mNumChannels = aacParams->nChannels; + mSampleRate = aacParams->nSampleRate; + + if (setAudioParams() != OK) { + return OMX_ErrorUndefined; + } + + return OMX_ErrorNone; + } + + case OMX_IndexParamAudioPcm: + { + OMX_AUDIO_PARAM_PCMMODETYPE *pcmParams = + (OMX_AUDIO_PARAM_PCMMODETYPE *)params; + + if (pcmParams->nPortIndex != 0) { + return OMX_ErrorUndefined; + } + + mNumChannels = pcmParams->nChannels; + mSampleRate = pcmParams->nSamplingRate; + + if (setAudioParams() != OK) { + return OMX_ErrorUndefined; + } + + return OMX_ErrorNone; + } + + + default: + return SimpleSoftOMXComponent::internalSetParameter(index, params); + } +} + +status_t SoftAACEncoder::setAudioParams() { + // We call this whenever sample rate, number of channels or bitrate change + // in reponse to setParameter calls. + + ALOGV("setAudioParams: %lu Hz, %lu channels, %lu bps", + mSampleRate, mNumChannels, mBitRate); + + status_t err = setAudioSpecificConfigData(); + + if (err != OK) { + return err; + } + + AACENC_PARAM params; + memset(¶ms, 0, sizeof(params)); + params.sampleRate = mSampleRate; + params.bitRate = mBitRate; + params.nChannels = mNumChannels; + params.adtsUsed = 0; // We add adts header in the file writer if needed. + if (VO_ERR_NONE != mApiHandle->SetParam( + mEncoderHandle, VO_PID_AAC_ENCPARAM, ¶ms)) { + ALOGE("Failed to set AAC encoder parameters"); + return UNKNOWN_ERROR; + } + + return OK; +} + +static status_t getSampleRateTableIndex(int32_t sampleRate, int32_t &index) { + static const int32_t kSampleRateTable[] = { + 96000, 88200, 64000, 48000, 44100, 32000, + 24000, 22050, 16000, 12000, 11025, 8000 + }; + const int32_t tableSize = + sizeof(kSampleRateTable) / sizeof(kSampleRateTable[0]); + + for (int32_t i = 0; i < tableSize; ++i) { + if (sampleRate == kSampleRateTable[i]) { + index = i; + return OK; + } + } + + return UNKNOWN_ERROR; +} + +status_t SoftAACEncoder::setAudioSpecificConfigData() { + // The AAC encoder's audio specific config really only encodes + // number of channels and the sample rate (mapped to an index into + // a fixed sample rate table). + + int32_t index; + status_t err = getSampleRateTableIndex(mSampleRate, index); + if (err != OK) { + ALOGE("Unsupported sample rate (%lu Hz)", mSampleRate); + return err; + } + + if (mNumChannels > 2 || mNumChannels <= 0) { + ALOGE("Unsupported number of channels(%lu)", mNumChannels); + return UNKNOWN_ERROR; + } + + // OMX_AUDIO_AACObjectLC + mAudioSpecificConfigData[0] = ((0x02 << 3) | (index >> 1)); + mAudioSpecificConfigData[1] = ((index & 0x01) << 7) | (mNumChannels << 3); + + return OK; +} + +void SoftAACEncoder::onQueueFilled(OMX_U32 portIndex) { + if (mSignalledError) { + return; + } + + List<BufferInfo *> &inQueue = getPortQueue(0); + List<BufferInfo *> &outQueue = getPortQueue(1); + + if (!mSentCodecSpecificData) { + // The very first thing we want to output is the codec specific + // data. It does not require any input data but we will need an + // output buffer to store it in. + + if (outQueue.empty()) { + return; + } + + BufferInfo *outInfo = *outQueue.begin(); + OMX_BUFFERHEADERTYPE *outHeader = outInfo->mHeader; + outHeader->nFilledLen = sizeof(mAudioSpecificConfigData); + outHeader->nFlags = OMX_BUFFERFLAG_CODECCONFIG; + + uint8_t *out = outHeader->pBuffer + outHeader->nOffset; + memcpy(out, mAudioSpecificConfigData, sizeof(mAudioSpecificConfigData)); + +#if 0 + ALOGI("sending codec specific data."); + hexdump(out, sizeof(mAudioSpecificConfigData)); +#endif + + outQueue.erase(outQueue.begin()); + outInfo->mOwnedByUs = false; + notifyFillBufferDone(outHeader); + + mSentCodecSpecificData = true; + } + + size_t numBytesPerInputFrame = + mNumChannels * kNumSamplesPerFrame * sizeof(int16_t); + + for (;;) { + // We do the following until we run out of buffers. + + while (mInputSize < numBytesPerInputFrame) { + // As long as there's still input data to be read we + // will drain "kNumSamplesPerFrame * mNumChannels" samples + // into the "mInputFrame" buffer and then encode those + // as a unit into an output buffer. + + if (mSawInputEOS || inQueue.empty()) { + return; + } + + BufferInfo *inInfo = *inQueue.begin(); + OMX_BUFFERHEADERTYPE *inHeader = inInfo->mHeader; + + const void *inData = inHeader->pBuffer + inHeader->nOffset; + + size_t copy = numBytesPerInputFrame - mInputSize; + if (copy > inHeader->nFilledLen) { + copy = inHeader->nFilledLen; + } + + if (mInputFrame == NULL) { + mInputFrame = new int16_t[kNumSamplesPerFrame * mNumChannels]; + } + + if (mInputSize == 0) { + mInputTimeUs = inHeader->nTimeStamp; + } + + memcpy((uint8_t *)mInputFrame + mInputSize, inData, copy); + mInputSize += copy; + + inHeader->nOffset += copy; + inHeader->nFilledLen -= copy; + + // "Time" on the input buffer has in effect advanced by the + // number of audio frames we just advanced nOffset by. + inHeader->nTimeStamp += + (copy * 1000000ll / mSampleRate) + / (mNumChannels * sizeof(int16_t)); + + if (inHeader->nFilledLen == 0) { + if (inHeader->nFlags & OMX_BUFFERFLAG_EOS) { + ALOGV("saw input EOS"); + mSawInputEOS = true; + + // Pad any remaining data with zeroes. + memset((uint8_t *)mInputFrame + mInputSize, + 0, + numBytesPerInputFrame - mInputSize); + + mInputSize = numBytesPerInputFrame; + } + + inQueue.erase(inQueue.begin()); + inInfo->mOwnedByUs = false; + notifyEmptyBufferDone(inHeader); + + inData = NULL; + inHeader = NULL; + inInfo = NULL; + } + } + + // At this point we have all the input data necessary to encode + // a single frame, all we need is an output buffer to store the result + // in. + + if (outQueue.empty()) { + return; + } + + BufferInfo *outInfo = *outQueue.begin(); + OMX_BUFFERHEADERTYPE *outHeader = outInfo->mHeader; + + VO_CODECBUFFER inputData; + memset(&inputData, 0, sizeof(inputData)); + inputData.Buffer = (unsigned char *)mInputFrame; + inputData.Length = numBytesPerInputFrame; + CHECK(VO_ERR_NONE == + mApiHandle->SetInputData(mEncoderHandle, &inputData)); + + VO_CODECBUFFER outputData; + memset(&outputData, 0, sizeof(outputData)); + VO_AUDIO_OUTPUTINFO outputInfo; + memset(&outputInfo, 0, sizeof(outputInfo)); + + uint8_t *outPtr = (uint8_t *)outHeader->pBuffer + outHeader->nOffset; + size_t outAvailable = outHeader->nAllocLen - outHeader->nOffset; + + VO_U32 ret = VO_ERR_NONE; + size_t nOutputBytes = 0; + do { + outputData.Buffer = outPtr; + outputData.Length = outAvailable - nOutputBytes; + ret = mApiHandle->GetOutputData( + mEncoderHandle, &outputData, &outputInfo); + if (ret == VO_ERR_NONE) { + outPtr += outputData.Length; + nOutputBytes += outputData.Length; + } + } while (ret != VO_ERR_INPUT_BUFFER_SMALL); + + outHeader->nFilledLen = nOutputBytes; + + outHeader->nFlags = OMX_BUFFERFLAG_ENDOFFRAME; + + if (mSawInputEOS) { + // We also tag this output buffer with EOS if it corresponds + // to the final input buffer. + outHeader->nFlags = OMX_BUFFERFLAG_EOS; + } + + outHeader->nTimeStamp = mInputTimeUs; + +#if 0 + ALOGI("sending %d bytes of data (time = %lld us, flags = 0x%08lx)", + nOutputBytes, mInputTimeUs, outHeader->nFlags); + + hexdump(outHeader->pBuffer + outHeader->nOffset, outHeader->nFilledLen); +#endif + + outQueue.erase(outQueue.begin()); + outInfo->mOwnedByUs = false; + notifyFillBufferDone(outHeader); + + outHeader = NULL; + outInfo = NULL; + + mInputSize = 0; + } +} + +} // namespace android + +android::SoftOMXComponent *createSoftOMXComponent( + const char *name, const OMX_CALLBACKTYPE *callbacks, + OMX_PTR appData, OMX_COMPONENTTYPE **component) { + return new android::SoftAACEncoder(name, callbacks, appData, component); +} diff --git a/media/libstagefright/codecs/aacenc/SoftAACEncoder.h b/media/libstagefright/codecs/aacenc/SoftAACEncoder.h new file mode 100644 index 000000000000..d148eb7630f2 --- /dev/null +++ b/media/libstagefright/codecs/aacenc/SoftAACEncoder.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2012 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. + */ + +#ifndef SOFT_AAC_ENCODER_H_ + +#define SOFT_AAC_ENCODER_H_ + +#include "SimpleSoftOMXComponent.h" + +struct VO_AUDIO_CODECAPI; +struct VO_MEM_OPERATOR; + +namespace android { + +struct SoftAACEncoder : public SimpleSoftOMXComponent { + SoftAACEncoder( + const char *name, + const OMX_CALLBACKTYPE *callbacks, + OMX_PTR appData, + OMX_COMPONENTTYPE **component); + +protected: + virtual ~SoftAACEncoder(); + + virtual OMX_ERRORTYPE internalGetParameter( + OMX_INDEXTYPE index, OMX_PTR params); + + virtual OMX_ERRORTYPE internalSetParameter( + OMX_INDEXTYPE index, const OMX_PTR params); + + virtual void onQueueFilled(OMX_U32 portIndex); + +private: + enum { + kNumBuffers = 4, + kNumSamplesPerFrame = 1024, + }; + + void *mEncoderHandle; + VO_AUDIO_CODECAPI *mApiHandle; + VO_MEM_OPERATOR *mMemOperator; + + OMX_U32 mNumChannels; + OMX_U32 mSampleRate; + OMX_U32 mBitRate; + + bool mSentCodecSpecificData; + size_t mInputSize; + int16_t *mInputFrame; + int64_t mInputTimeUs; + + bool mSawInputEOS; + + uint8_t mAudioSpecificConfigData[2]; + + bool mSignalledError; + + void initPorts(); + status_t initEncoder(); + + status_t setAudioSpecificConfigData(); + status_t setAudioParams(); + + DISALLOW_EVIL_CONSTRUCTORS(SoftAACEncoder); +}; + +} // namespace android + +#endif // SOFT_AAC_ENCODER_H_ diff --git a/media/libstagefright/codecs/aacenc/basic_op/oper_32b.c b/media/libstagefright/codecs/aacenc/basic_op/oper_32b.c index e48af9d51f77..982f4fd44e6d 100644 --- a/media/libstagefright/codecs/aacenc/basic_op/oper_32b.c +++ b/media/libstagefright/codecs/aacenc/basic_op/oper_32b.c @@ -358,4 +358,4 @@ Word32 pow2_xy(Word32 x, Word32 y) res = pow2Table[(POW2_TABLE_SIZE*fPart)/y] >> iPart; return(res); -}
\ No newline at end of file +} diff --git a/media/libstagefright/codecs/aacenc/basic_op/typedefs.h b/media/libstagefright/codecs/aacenc/basic_op/typedefs.h index c924e2c2b165..2d5d9567f2db 100644 --- a/media/libstagefright/codecs/aacenc/basic_op/typedefs.h +++ b/media/libstagefright/codecs/aacenc/basic_op/typedefs.h @@ -77,12 +77,12 @@ typedef unsigned short UWord16; /* ********* define 32 bit signed/unsigned types & constants */ -typedef long Word32; -typedef unsigned long UWord32; +typedef int Word32; +typedef unsigned int UWord32; -#ifdef LINUX +#ifndef _MSC_VER typedef long long Word64; typedef unsigned long long UWord64; #else diff --git a/media/libstagefright/codecs/aacenc/inc/config.h b/media/libstagefright/codecs/aacenc/inc/config.h index 3b29cefd8c65..b0b4c26f8aea 100644 --- a/media/libstagefright/codecs/aacenc/inc/config.h +++ b/media/libstagefright/codecs/aacenc/inc/config.h @@ -33,4 +33,4 @@ #define MINBITS_COEF 744 -#endif
\ No newline at end of file +#endif diff --git a/media/libstagefright/codecs/aacenc/inc/sf_estim.h b/media/libstagefright/codecs/aacenc/inc/sf_estim.h index 9039f25481e0..997eba5b828b 100644 --- a/media/libstagefright/codecs/aacenc/inc/sf_estim.h +++ b/media/libstagefright/codecs/aacenc/inc/sf_estim.h @@ -43,4 +43,4 @@ EstimateScaleFactors(PSY_OUT_CHANNEL psyOutChannel[MAX_CHANNELS], Word16 logSfbFormFactor[MAX_CHANNELS][MAX_GROUPED_SFB], Word16 sfbNRelevantLines[MAX_CHANNELS][MAX_GROUPED_SFB], const Word16 nChannels); -#endif
\ No newline at end of file +#endif diff --git a/media/libstagefright/codecs/aacenc/inc/transform.h b/media/libstagefright/codecs/aacenc/inc/transform.h index fbac7aa68796..311cef5e1da0 100644 --- a/media/libstagefright/codecs/aacenc/inc/transform.h +++ b/media/libstagefright/codecs/aacenc/inc/transform.h @@ -33,4 +33,4 @@ void Transform_Real(Word16 *mdctDelayBuffer, Word16 windowSequence ); -#endif
\ No newline at end of file +#endif diff --git a/media/libstagefright/codecs/aacenc/src/aac_rom.c b/media/libstagefright/codecs/aacenc/src/aac_rom.c index 08792e842b56..127322d2ad1a 100644 --- a/media/libstagefright/codecs/aacenc/src/aac_rom.c +++ b/media/libstagefright/codecs/aacenc/src/aac_rom.c @@ -2360,4 +2360,4 @@ const unsigned char bitrevTab[17 + 129] = 0x4d, 0x59, 0x4f, 0x79, 0x53, 0x65, 0x57, 0x75, 0x5b, 0x6d, 0x5f, 0x7d, 0x67, 0x73, 0x6f, 0x7b, 0x00, 0x08, 0x14, 0x1c, 0x22, 0x2a, 0x36, 0x3e, 0x41, 0x49, 0x55, 0x5d, 0x63, 0x6b, 0x77, 0x7f, 0x00, -};
\ No newline at end of file +}; diff --git a/media/libstagefright/codecs/aacenc/src/aacenc.c b/media/libstagefright/codecs/aacenc/src/aacenc.c index b5e8a9c5ba8a..ad2f29a55a1f 100644 --- a/media/libstagefright/codecs/aacenc/src/aacenc.c +++ b/media/libstagefright/codecs/aacenc/src/aacenc.c @@ -492,4 +492,4 @@ VO_S32 VO_API voGetAACEncAPI(VO_AUDIO_CODECAPI * pDecHandle) pDecHandle->Uninit = voAACEncUninit; return VO_ERR_NONE; -}
\ No newline at end of file +} diff --git a/media/libstagefright/codecs/aacenc/src/asm/ARMV5E/PrePostMDCT_v5.s b/media/libstagefright/codecs/aacenc/src/asm/ARMV5E/PrePostMDCT_v5.s index 103cc91814ad..da21d5faf0c2 100644 --- a/media/libstagefright/codecs/aacenc/src/asm/ARMV5E/PrePostMDCT_v5.s +++ b/media/libstagefright/codecs/aacenc/src/asm/ARMV5E/PrePostMDCT_v5.s @@ -128,4 +128,4 @@ PostMDCT_LOOP: PostMDCT_END: ldmia sp!, {r4 - r11, pc} @ENDP @ |PostMDCT| - .end
\ No newline at end of file + .end diff --git a/media/libstagefright/codecs/aacenc/src/asm/ARMV5E/R4R8First_v5.s b/media/libstagefright/codecs/aacenc/src/asm/ARMV5E/R4R8First_v5.s index 72cb9a3ca907..4ca4f31108cf 100644 --- a/media/libstagefright/codecs/aacenc/src/asm/ARMV5E/R4R8First_v5.s +++ b/media/libstagefright/codecs/aacenc/src/asm/ARMV5E/R4R8First_v5.s @@ -249,4 +249,4 @@ DATATab: .word 0x5a82799a @ENDP @ |Radix8First| - .end
\ No newline at end of file + .end diff --git a/media/libstagefright/codecs/aacenc/src/asm/ARMV5E/Radix4FFT_v5.s b/media/libstagefright/codecs/aacenc/src/asm/ARMV5E/Radix4FFT_v5.s index e81c82eaf790..b59b967ad9fa 100644 --- a/media/libstagefright/codecs/aacenc/src/asm/ARMV5E/Radix4FFT_v5.s +++ b/media/libstagefright/codecs/aacenc/src/asm/ARMV5E/Radix4FFT_v5.s @@ -166,4 +166,4 @@ Radix4FFT_END: ldmia sp!, {r4 - r11, pc} @ENDP @ |Radix4FFT| - .end
\ No newline at end of file + .end diff --git a/media/libstagefright/codecs/aacenc/src/asm/ARMV7/PrePostMDCT_v7.s b/media/libstagefright/codecs/aacenc/src/asm/ARMV7/PrePostMDCT_v7.s index 64d767a5c3e6..7f6b8813456e 100644 --- a/media/libstagefright/codecs/aacenc/src/asm/ARMV7/PrePostMDCT_v7.s +++ b/media/libstagefright/codecs/aacenc/src/asm/ARMV7/PrePostMDCT_v7.s @@ -23,9 +23,13 @@ .section .text .global PreMDCT + .fnstart PreMDCT: stmdb sp!, {r4 - r11, lr} + .save {r4 - r11, lr} + fstmfdd sp!, {d8 - d15} + .vsave {d8 - d15} add r9, r0, r1, lsl #2 sub r3, r9, #32 @@ -74,14 +78,20 @@ PreMDCT_LOOP: bne PreMDCT_LOOP PreMDCT_END: + fldmfdd sp!, {d8 - d15} ldmia sp!, {r4 - r11, pc} @ENDP @ |PreMDCT| + .fnend .section .text .global PostMDCT + .fnstart PostMDCT: stmdb sp!, {r4 - r11, lr} + .save {r4 - r11, lr} + fstmfdd sp!, {d8 - d15} + .vsave {d8 - d15} add r9, r0, r1, lsl #2 sub r3, r9, #32 @@ -129,7 +139,8 @@ PostMDCT_LOOP: bne PostMDCT_LOOP PostMDCT_END: + fldmfdd sp!, {d8 - d15} ldmia sp!, {r4 - r11, pc} @ENDP @ |PostMDCT| - .end
\ No newline at end of file + .fnend diff --git a/media/libstagefright/codecs/aacenc/src/asm/ARMV7/R4R8First_v7.s b/media/libstagefright/codecs/aacenc/src/asm/ARMV7/R4R8First_v7.s index 7fc55203978d..03fa6a987f6c 100644 --- a/media/libstagefright/codecs/aacenc/src/asm/ARMV7/R4R8First_v7.s +++ b/media/libstagefright/codecs/aacenc/src/asm/ARMV7/R4R8First_v7.s @@ -23,9 +23,13 @@ .section .text .global Radix8First + .fnstart Radix8First: stmdb sp!, {r4 - r11, lr} + .save {r4 - r11, lr} + fstmfdd sp!, {d8 - d15} + .vsave {d8 - d15} ldr r3, SQRT1_2 cmp r1, #0 @@ -103,17 +107,23 @@ Radix8First_LOOP: bne Radix8First_LOOP Radix8First_END: + fldmfdd sp!, {d8 - d15} ldmia sp!, {r4 - r11, pc} SQRT1_2: .word 0x2d413ccd @ENDP @ |Radix8First| + .fnend .section .text .global Radix4First + .fnstart Radix4First: stmdb sp!, {r4 - r11, lr} + .save {r4 - r11, lr} + fstmfdd sp!, {d8 - d15} + .vsave {d8 - d15} cmp r1, #0 beq Radix4First_END @@ -140,7 +150,8 @@ Radix4First_LOOP: bne Radix4First_LOOP Radix4First_END: + fldmfdd sp!, {d8 - d15} ldmia sp!, {r4 - r11, pc} @ENDP @ |Radix4First| - .end
\ No newline at end of file + .fnend diff --git a/media/libstagefright/codecs/aacenc/src/asm/ARMV7/Radix4FFT_v7.s b/media/libstagefright/codecs/aacenc/src/asm/ARMV7/Radix4FFT_v7.s index b8655aee2096..431bc30c9312 100644 --- a/media/libstagefright/codecs/aacenc/src/asm/ARMV7/Radix4FFT_v7.s +++ b/media/libstagefright/codecs/aacenc/src/asm/ARMV7/Radix4FFT_v7.s @@ -23,9 +23,13 @@ .section .text .global Radix4FFT + .fnstart Radix4FFT: stmdb sp!, {r4 - r11, lr} + .save {r4 - r11, lr} + fstmfdd sp!, {d8 - d15} + .vsave {d8 - d15} mov r1, r1, asr #2 cmp r1, #0 @@ -137,7 +141,8 @@ Radix4FFT_LOOP1_END: bne Radix4FFT_LOOP1 Radix4FFT_END: + fldmfdd sp!, {d8 - d15} ldmia sp!, {r4 - r11, pc} @ENDP @ |Radix4FFT| - .end
\ No newline at end of file + .fnend diff --git a/media/libstagefright/codecs/aacenc/src/band_nrg.c b/media/libstagefright/codecs/aacenc/src/band_nrg.c index 7501af121927..e4034b8a4efd 100644 --- a/media/libstagefright/codecs/aacenc/src/band_nrg.c +++ b/media/libstagefright/codecs/aacenc/src/band_nrg.c @@ -99,4 +99,4 @@ void CalcBandEnergyMS(const Word32 *mdctSpectrumLeft, *bandEnergySideSum = accuSideSum; } -#endif
\ No newline at end of file +#endif diff --git a/media/libstagefright/codecs/amrwbenc/src/mem_align.c b/media/libstagefright/codecs/amrwbenc/src/mem_align.c index a29baf3830b3..3b7853f19854 100644 --- a/media/libstagefright/codecs/amrwbenc/src/mem_align.c +++ b/media/libstagefright/codecs/amrwbenc/src/mem_align.c @@ -23,6 +23,11 @@ #include "mem_align.h" +#ifdef _MSC_VER +#include <stddef.h> +#else +#include <stdint.h> +#endif /***************************************************************************** * @@ -66,8 +71,8 @@ mem_malloc(VO_MEM_OPERATOR *pMemop, unsigned int size, unsigned char alignment, pMemop->Set(CodecID, tmp, 0, size + alignment); mem_ptr = - (unsigned char *) ((unsigned int) (tmp + alignment - 1) & - (~((unsigned int) (alignment - 1)))); + (unsigned char *) ((intptr_t) (tmp + alignment - 1) & + (~((intptr_t) (alignment - 1)))); if (mem_ptr == tmp) mem_ptr += alignment; diff --git a/media/libstagefright/omx/SoftOMXPlugin.cpp b/media/libstagefright/omx/SoftOMXPlugin.cpp index da3ae42426db..cf9e8c91f651 100644 --- a/media/libstagefright/omx/SoftOMXPlugin.cpp +++ b/media/libstagefright/omx/SoftOMXPlugin.cpp @@ -35,6 +35,7 @@ static const struct { } kComponents[] = { { "OMX.google.aac.decoder", "aacdec", "audio_decoder.aac" }, + { "OMX.google.aac.encoder", "aacenc", "audio_encoder.aac" }, { "OMX.google.amrnb.decoder", "amrdec", "audio_decoder.amrnb" }, { "OMX.google.amrwb.decoder", "amrdec", "audio_decoder.amrwb" }, { "OMX.google.h264.decoder", "h264dec", "video_decoder.avc" }, diff --git a/native/include/android/configuration.h b/native/include/android/configuration.h index 4d683fbe1db6..06cd3da1a685 100644 --- a/native/include/android/configuration.h +++ b/native/include/android/configuration.h @@ -42,6 +42,8 @@ enum { ACONFIGURATION_DENSITY_MEDIUM = 160, ACONFIGURATION_DENSITY_TV = 213, ACONFIGURATION_DENSITY_HIGH = 240, + ACONFIGURATION_DENSITY_XHIGH = 320, + ACONFIGURATION_DENSITY_XXHIGH = 480, ACONFIGURATION_DENSITY_NONE = 0xffff, ACONFIGURATION_KEYBOARD_ANY = 0x0000, diff --git a/opengl/libs/EGL/eglApi.cpp b/opengl/libs/EGL/eglApi.cpp index 664f2582d255..8b37da56c9e7 100644 --- a/opengl/libs/EGL/eglApi.cpp +++ b/opengl/libs/EGL/eglApi.cpp @@ -477,6 +477,26 @@ EGLBoolean eglQuerySurface( EGLDisplay dpy, EGLSurface surface, return result; } +void EGLAPI eglBeginFrame(EGLDisplay dpy, EGLSurface surface) { + clearError(); + + egl_display_t const * const dp = validate_display(dpy); + if (!dp) { + return; + } + + SurfaceRef _s(dp, surface); + if (!_s.get()) { + setError(EGL_BAD_SURFACE, EGL_FALSE); + return; + } + + int64_t timestamp = systemTime(SYSTEM_TIME_MONOTONIC); + + egl_surface_t const * const s = get_surface(surface); + native_window_set_buffers_timestamp(s->win.get(), timestamp); +} + // ---------------------------------------------------------------------------- // Contexts // ---------------------------------------------------------------------------- diff --git a/packages/SystemUI/res/layout-sw600dp/status_bar_notification_panel_title.xml b/packages/SystemUI/res/layout-sw600dp/status_bar_notification_panel_title.xml index ef959360130d..b985aaf81ba1 100644 --- a/packages/SystemUI/res/layout-sw600dp/status_bar_notification_panel_title.xml +++ b/packages/SystemUI/res/layout-sw600dp/status_bar_notification_panel_title.xml @@ -63,6 +63,7 @@ android:layout_height="wrap_content" android:layout_width="wrap_content" android:layout_gravity="center_vertical" + android:paddingRight="6dp" > <ImageView @@ -87,7 +88,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingRight="12dp" - android:paddingLeft="6dp" android:singleLine="true" android:ellipsize="end" android:text="@string/status_bar_settings_settings_button" @@ -99,6 +99,7 @@ android:layout_height="wrap_content" android:layout_width="wrap_content" android:layout_gravity="center_vertical" + android:paddingRight="6dp" > <ImageView @@ -122,7 +123,6 @@ android:layout_gravity="left|center_vertical" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:paddingLeft="6dp" android:paddingRight="12dp" android:singleLine="true" android:ellipsize="end" diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index a6c64ade3e80..d083467e90ad 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -101,7 +101,12 @@ <string name="accessibility_wifi_one_bar" msgid="6854947280074467207">"Wi-Fi, адзiн слупок."</string> <string name="accessibility_wifi_two_bars" msgid="3344340012058984348">"Wi-Fi, два слупкi."</string> <string name="accessibility_wifi_three_bars" msgid="928322805193265041">"Wi-Fi, тры слупкi."</string> - <string name="accessibility_wifi_signal_full" msgid="4826278754383492058">"Моцны сiгнал Wi-Fi."</string> + <string name="accessibility_wifi_signal_full" msgid="1275764416228473932">"Поўны сігнал Wi-Fi."</string> + <string name="accessibility_no_wimax" msgid="4329180129727630368">"Няма сiгналу WiMAX."</string> + <string name="accessibility_wimax_one_bar" msgid="4170994299011863648">"Адзiн слупок сiгналу WiMAX."</string> + <string name="accessibility_wimax_two_bars" msgid="9176236858336502288">"Два слупкi сiгналу WiMAX."</string> + <string name="accessibility_wimax_three_bars" msgid="6116551636752103927">"Тры слупкi сiгналу WiMAX."</string> + <string name="accessibility_wimax_signal_full" msgid="2768089986795579558">"Моцны сiгнал WiMAX."</string> <string name="accessibility_data_connection_gprs" msgid="1606477224486747751">"GPRS"</string> <string name="accessibility_data_connection_3g" msgid="8628562305003568260">"3G"</string> <string name="accessibility_data_connection_3.5g" msgid="8664845609981692001">"3.5G"</string> @@ -135,4 +140,5 @@ <string name="gps_notification_searching_text" msgid="8574247005642736060">"Пошук GPS"</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"Месца задана праз GPS"</string> <string name="accessibility_clear_all" msgid="5235938559247164925">"Выдалiць усе апавяшчэннi."</string> + <string name="dreams_dock_launcher" msgid="3541196417659166245">"Актывацыя экраннай застаўкі"</string> </resources> diff --git a/packages/SystemUI/res/values-es-rUS-large/strings.xml b/packages/SystemUI/res/values-es-rUS-large/strings.xml index 3f96e87fce1d..dd44b28fb214 100644 --- a/packages/SystemUI/res/values-es-rUS-large/strings.xml +++ b/packages/SystemUI/res/values-es-rUS-large/strings.xml @@ -19,7 +19,7 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="status_bar_clear_all_button" msgid="4661583896803349732">"Borrar todas"</string> + <string name="status_bar_clear_all_button" msgid="4661583896803349732">"Eliminar todas"</string> <string name="notifications_off_title" msgid="1860117696034775851">"Notificaciones desactivadas"</string> <string name="notifications_off_text" msgid="1439152806320786912">"Toca aquí para volver a activar las notificaciones."</string> </resources> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index 85818ae6ee3b..a70090847bc3 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -20,7 +20,7 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="7164937344850004466">"IU del sistema"</string> - <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Borrar"</string> + <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Eliminar"</string> <string name="status_bar_do_not_disturb_button" msgid="5812628897510997853">"No molestar"</string> <string name="status_bar_please_disturb_button" msgid="3345398298841572813">"Mostrar notificaciones"</string> <string name="status_bar_recent_remove_item_title" msgid="6026395868129852968">"Eliminar de la lista"</string> @@ -122,7 +122,7 @@ <skip /> <string name="accessibility_settings_button" msgid="799583911231893380">"Configuración del sistema"</string> <string name="accessibility_notifications_button" msgid="4498000369779421892">"Notificaciones"</string> - <string name="accessibility_remove_notification" msgid="3603099514902182350">"Borrar notificación"</string> + <string name="accessibility_remove_notification" msgid="3603099514902182350">"Eliminar notificación"</string> <string name="accessibility_gps_enabled" msgid="3511469499240123019">"GPS habilitado"</string> <string name="accessibility_gps_acquiring" msgid="8959333351058967158">"Adquisición de GPS"</string> <string name="accessibility_tty_enabled" msgid="4613200365379426561">"TeleTypewriter habilitado"</string> @@ -133,12 +133,12 @@ <string name="data_usage_disabled_dialog_4g_title" msgid="4789143363492682629">"Datos de 4G inhabilitados"</string> <string name="data_usage_disabled_dialog_mobile_title" msgid="1046047248844821202">"Se inhabilitaron los datos móviles"</string> <string name="data_usage_disabled_dialog_title" msgid="2086815304858964954">"Datos inhabilitados"</string> - <string name="data_usage_disabled_dialog" msgid="3853117269051806280">"Alcanzaste el límite de uso de datos especificado."\n\n"Puede que tu operador te cobre por volver a habilitar datos."</string> - <string name="data_usage_disabled_dialog_enable" msgid="7729772039208664606">"Volver a habilitar datos"</string> + <string name="data_usage_disabled_dialog" msgid="3853117269051806280">"Alcanzaste el límite de uso de datos especificado."\n\n"Puede que tu operador te cobre por volver a activar datos."</string> + <string name="data_usage_disabled_dialog_enable" msgid="7729772039208664606">"Volver a activar datos"</string> <string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Sin conexión a Internet"</string> <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi conectado"</string> <string name="gps_notification_searching_text" msgid="8574247005642736060">"Buscando GPS"</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"La ubicación se estableció por GPS"</string> - <string name="accessibility_clear_all" msgid="5235938559247164925">"Borrar todas las notificaciones"</string> + <string name="accessibility_clear_all" msgid="5235938559247164925">"Eliminar todas las notificaciones"</string> <string name="dreams_dock_launcher" msgid="3541196417659166245">"Activar el protector de pantalla"</string> </resources> diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index 943b8ca4484c..a5bfb4a25359 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -101,7 +101,12 @@ <string name="accessibility_wifi_one_bar" msgid="6854947280074467207">"WiFi signaal: üks post."</string> <string name="accessibility_wifi_two_bars" msgid="3344340012058984348">"WiFi signaal: kaks posti."</string> <string name="accessibility_wifi_three_bars" msgid="928322805193265041">"WiFi signaal: kolm posti."</string> - <string name="accessibility_wifi_signal_full" msgid="4826278754383492058">"WiFi signaal on täis."</string> + <string name="accessibility_wifi_signal_full" msgid="1275764416228473932">"WiFi-signaal on tugev."</string> + <string name="accessibility_no_wimax" msgid="4329180129727630368">"WiMAX-i pole."</string> + <string name="accessibility_wimax_one_bar" msgid="4170994299011863648">"WiMAX-i on üks riba."</string> + <string name="accessibility_wimax_two_bars" msgid="9176236858336502288">"WiMAX-i on kaks riba."</string> + <string name="accessibility_wimax_three_bars" msgid="6116551636752103927">"WiMAX-i on kolm riba."</string> + <string name="accessibility_wimax_signal_full" msgid="2768089986795579558">"WiMAX-i signaal on tugev."</string> <string name="accessibility_data_connection_gprs" msgid="1606477224486747751">"GPRS"</string> <string name="accessibility_data_connection_3g" msgid="8628562305003568260">"3G"</string> <string name="accessibility_data_connection_3.5g" msgid="8664845609981692001">"3,5G"</string> @@ -133,4 +138,5 @@ <string name="gps_notification_searching_text" msgid="8574247005642736060">"GPS-i otsimine"</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"GPS-i määratud asukoht"</string> <string name="accessibility_clear_all" msgid="5235938559247164925">"Kustuta kõik teatised."</string> + <string name="dreams_dock_launcher" msgid="3541196417659166245">"Aktiveeri ekraanisäästja"</string> </resources> diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index 545d54eab8c8..dec4def6fbb5 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -115,7 +115,7 @@ <string name="accessibility_data_connection_edge" msgid="4477457051631979278">"Edge"</string> <string name="accessibility_data_connection_wifi" msgid="2324496756590645221">"Wi-Fi"</string> <string name="accessibility_no_sim" msgid="8274017118472455155">"Sem SIM."</string> - <string name="accessibility_bluetooth_tether" msgid="4102784498140271969">"Vínculo Bluetooth."</string> + <string name="accessibility_bluetooth_tether" msgid="4102784498140271969">"Tethering Bluetooth."</string> <string name="accessibility_airplane_mode" msgid="834748999790763092">"Modo de avião."</string> <!-- String.format failed for translation --> <!-- no translation found for accessibility_battery_level (7451474187113371965) --> diff --git a/packages/SystemUI/res/values-rm/strings.xml b/packages/SystemUI/res/values-rm/strings.xml index 2106b892da7c..983df47e88ea 100644 --- a/packages/SystemUI/res/values-rm/strings.xml +++ b/packages/SystemUI/res/values-rm/strings.xml @@ -39,8 +39,10 @@ <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Nagins avis"</string> <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Actual"</string> <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Avis"</string> - <!-- outdated translation 7923774589611311406 --> <string name="battery_low_title" msgid="2783104807551211639">"Connectar il chargiabattarias"</string> - <!-- outdated translation 7388781709819722764 --> <string name="battery_low_subtitle" msgid="1752040062087829196">"L\'accu è prest vid."</string> + <!-- no translation found for battery_low_title (2783104807551211639) --> + <skip /> + <!-- no translation found for battery_low_subtitle (1752040062087829196) --> + <skip /> <!-- no translation found for battery_low_percent_format (1077244949318261761) --> <skip /> <!-- no translation found for invalid_charger (4549105996740522523) --> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index 17330b1e9a7b..963976dcc185 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -36,7 +36,7 @@ <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Уведомления"</string> <string name="battery_low_title" msgid="2783104807551211639">"Подключите зарядное устройство"</string> <string name="battery_low_subtitle" msgid="1752040062087829196">"Батарея разряжена."</string> - <string name="battery_low_percent_format" msgid="1077244949318261761">"Осталось: <xliff:g id="NUMBER">%d%%</xliff:g>"</string> + <string name="battery_low_percent_format" msgid="1077244949318261761">"Осталось <xliff:g id="NUMBER">%d%%</xliff:g>"</string> <string name="invalid_charger" msgid="4549105996740522523">"Зарядка через порт USB не поддерживается."\n"Используйте только зарядное устройство из комплекта поставки."</string> <string name="battery_low_why" msgid="7279169609518386372">"Расход заряда батареи"</string> <string name="status_bar_settings_settings_button" msgid="3023889916699270224">"Настройки"</string> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index 3843ea96a8ec..70e29ebe28a0 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -26,7 +26,7 @@ <string name="status_bar_recent_remove_item_title" msgid="6026395868129852968">"Ondoa kwenye orodha"</string> <string name="status_bar_recent_inspect_item_title" msgid="7793624864528818569">"Taarifa za programu-matumizi"</string> <string name="status_bar_no_recent_apps" msgid="6576392951053994640">"Hakuna programu za sasa"</string> - <string name="status_bar_accessibility_dismiss_recents" msgid="4576076075226540105">"Ondosha programu za hivi karibuni"</string> + <string name="status_bar_accessibility_dismiss_recents" msgid="4576076075226540105">"Ondosha prog za hivi karibuni"</string> <!-- String.format failed for translation --> <!-- no translation found for status_bar_accessibility_recent_apps:other (1040784359794890744) --> <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Hakuna arifa"</string> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java index d09e68050402..d46ab6ca41a2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java @@ -890,34 +890,51 @@ public class NetworkController extends BroadcastReceiver { if (!mHasMobileDataFeature) { mDataSignalIconId = mPhoneSignalIconId = 0; - } else if (mDataConnected) { - mobileLabel = mNetworkName; - if (DEBUG) { - mobileLabel += "yyyyYYYYyyyyYYYY"; - } - combinedSignalIconId = mDataSignalIconId; - switch (mDataActivity) { - case TelephonyManager.DATA_ACTIVITY_IN: - mMobileActivityIconId = R.drawable.stat_sys_signal_in; - break; - case TelephonyManager.DATA_ACTIVITY_OUT: - mMobileActivityIconId = R.drawable.stat_sys_signal_out; - break; - case TelephonyManager.DATA_ACTIVITY_INOUT: - mMobileActivityIconId = R.drawable.stat_sys_signal_inout; - break; - default: - mMobileActivityIconId = 0; - break; + mobileLabel = ""; + } else { + // We want to show the carrier name if in service and either: + // - We are connected to mobile data, or + // - We are not connected to mobile data, as long as the *reason* packets are not + // being routed over that link is that we have better connectivity via wifi. + // If data is disconnected for some other reason but wifi is connected, we show nothing. + // Otherwise (nothing connected) we show "No internet connection". + + if (mDataConnected) { + mobileLabel = mNetworkName; + } else if (mWifiConnected) { + if (hasService()) { + mobileLabel = mNetworkName; + } else { + mobileLabel = ""; + } + } else { + mobileLabel + = context.getString(R.string.status_bar_settings_signal_meter_disconnected); } - combinedLabel = mobileLabel; - combinedActivityIconId = mMobileActivityIconId; - combinedSignalIconId = mDataSignalIconId; // set by updateDataIcon() - mContentDescriptionCombinedSignal = mContentDescriptionDataType; - } else { - mobileLabel = mHasMobileDataFeature ? - context.getString(R.string.status_bar_settings_signal_meter_disconnected) : ""; + // Now for things that should only be shown when actually using mobile data. + if (mDataConnected) { + combinedSignalIconId = mDataSignalIconId; + switch (mDataActivity) { + case TelephonyManager.DATA_ACTIVITY_IN: + mMobileActivityIconId = R.drawable.stat_sys_signal_in; + break; + case TelephonyManager.DATA_ACTIVITY_OUT: + mMobileActivityIconId = R.drawable.stat_sys_signal_out; + break; + case TelephonyManager.DATA_ACTIVITY_INOUT: + mMobileActivityIconId = R.drawable.stat_sys_signal_inout; + break; + default: + mMobileActivityIconId = 0; + break; + } + + combinedLabel = mobileLabel; + combinedActivityIconId = mMobileActivityIconId; + combinedSignalIconId = mDataSignalIconId; // set by updateDataIcon() + mContentDescriptionCombinedSignal = mContentDescriptionDataType; + } } if (mWifiConnected) { @@ -949,6 +966,12 @@ public class NetworkController extends BroadcastReceiver { combinedLabel = wifiLabel; combinedSignalIconId = mWifiIconId; // set by updateWifiIcons() mContentDescriptionCombinedSignal = mContentDescriptionWifi; + } else { + if (mHasMobileDataFeature) { + wifiLabel = ""; + } else { + wifiLabel = context.getString(R.string.status_bar_settings_signal_meter_disconnected); + } } if (mBluetoothTethered) { @@ -969,9 +992,17 @@ public class NetworkController extends BroadcastReceiver { mDataTypeIconId = 0; // combined values from connected wifi take precedence over airplane mode - if (!mWifiConnected) { - wifiLabel = context.getString(R.string.status_bar_settings_signal_meter_disconnected); - combinedLabel = wifiLabel; + if (mWifiConnected) { + // Suppress "No internet connection." from mobile if wifi connected. + mobileLabel = ""; + } else { + if (mHasMobileDataFeature) { + // let the mobile icon show "No internet connection." + wifiLabel = ""; + } else { + wifiLabel = context.getString(R.string.status_bar_settings_signal_meter_disconnected); + combinedLabel = wifiLabel; + } mContentDescriptionCombinedSignal = mContentDescriptionPhoneSignal; combinedSignalIconId = mDataSignalIconId; } diff --git a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java index 0d0461baed7b..1e9784c5cc79 100644 --- a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java +++ b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java @@ -855,6 +855,9 @@ public class LockPatternKeyguardView extends KeyguardViewBase implements Handler case Password: secure = mLockPatternUtils.isLockPasswordEnabled(); break; + case Unknown: + // This means no security is set up + break; default: throw new IllegalStateException("unknown unlock mode " + unlockMode); } @@ -877,8 +880,8 @@ public class LockPatternKeyguardView extends KeyguardViewBase implements Handler // Re-create the unlock screen if necessary. This is primarily required to properly handle // SIM state changes. This typically happens when this method is called by reset() - if (mode == Mode.UnlockScreen) { - final UnlockMode unlockMode = getUnlockMode(); + final UnlockMode unlockMode = getUnlockMode(); + if (mode == Mode.UnlockScreen && unlockMode != UnlockMode.Unknown) { if (force || mUnlockScreen == null || unlockMode != mUnlockScreenMode) { boolean restartFaceLock = stopFaceLockIfRunning(); recreateUnlockScreen(unlockMode); @@ -1052,11 +1055,15 @@ public class LockPatternKeyguardView extends KeyguardViewBase implements Handler break; case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: case DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED: - // "forgot pattern" button is only available in the pattern mode... - if (mForgotPattern || mLockPatternUtils.isPermanentlyLocked()) { - currentMode = UnlockMode.Account; + if (mLockPatternUtils.isLockPatternEnabled()) { + // "forgot pattern" button is only available in the pattern mode... + if (mForgotPattern || mLockPatternUtils.isPermanentlyLocked()) { + currentMode = UnlockMode.Account; + } else { + currentMode = UnlockMode.Pattern; + } } else { - currentMode = UnlockMode.Pattern; + currentMode = UnlockMode.Unknown; } break; default: diff --git a/policy/src/com/android/internal/policy/impl/LockScreen.java b/policy/src/com/android/internal/policy/impl/LockScreen.java index 24a2420428d3..33846619857e 100644 --- a/policy/src/com/android/internal/policy/impl/LockScreen.java +++ b/policy/src/com/android/internal/policy/impl/LockScreen.java @@ -23,6 +23,8 @@ import com.android.internal.widget.WaveView; import com.android.internal.widget.multiwaveview.MultiWaveView; import android.app.ActivityManager; +import android.app.ActivityManagerNative; +import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; @@ -34,6 +36,7 @@ import android.view.ViewGroup; import android.widget.*; import android.util.Log; import android.media.AudioManager; +import android.os.RemoteException; import android.provider.MediaStore; import android.provider.Settings; @@ -229,8 +232,16 @@ class LockScreen extends LinearLayout implements KeyguardScreen { // Start the Camera Intent intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mContext.startActivity(intent); - mCallback.goToUnlockScreen(); + try { + ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); + } catch (RemoteException e) { + Log.w(TAG, "can't dismiss keyguard on launch"); + } + try { + mContext.startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.w(TAG, "Camera application not found"); + } } else { toggleRingMode(); mUnlockWidgetMethods.updateResources(); diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp index f5892d4a0e3d..6701059c227a 100644 --- a/services/audioflinger/AudioFlinger.cpp +++ b/services/audioflinger/AudioFlinger.cpp @@ -1988,11 +1988,14 @@ bool AudioFlinger::MixerThread::threadLoop() if (CC_LIKELY(mixerStatus == MIXER_TRACKS_READY)) { // mix buffers... mAudioMixer->process(); - sleepTime = 0; - // increase sleep time progressively when application underrun condition clears - if (sleepTimeShift > 0) { + // increase sleep time progressively when application underrun condition clears. + // Only increase sleep time if the mixer is ready for two consecutive times to avoid + // that a steady state of alternating ready/not ready conditions keeps the sleep time + // such that we would underrun the audio HAL. + if ((sleepTime == 0) && (sleepTimeShift > 0)) { sleepTimeShift--; } + sleepTime = 0; standbyTime = systemTime() + kStandbyTimeInNsecs; //TODO: delay standby when effects have a tail } else { diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index b7dc4a29d4d7..a372fb8e06cf 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -992,11 +992,15 @@ private NetworkStateTracker makeWimaxStateTracker() { NetworkInfo ni = network.getNetworkInfo(); if (ni.isAvailable() == false) { - if (DBG) log("special network not available"); if (!TextUtils.equals(feature,Phone.FEATURE_ENABLE_DUN_ALWAYS)) { + if (DBG) log("special network not available ni=" + ni.getTypeName()); return Phone.APN_TYPE_NOT_AVAILABLE; } else { // else make the attempt anyway - probably giving REQUEST_STARTED below + if (DBG) { + log("special network not available, but try anyway ni=" + + ni.getTypeName()); + } } } diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp index 438a6daf37be..986aec576a0f 100644 --- a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp +++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp @@ -350,15 +350,28 @@ uint32_t DisplayHardware::getPageFlipCount() const { } // this needs to be thread safe -nsecs_t DisplayHardware::waitForVSync() const { +nsecs_t DisplayHardware::waitForRefresh() const { nsecs_t timestamp; if (mVSync.wait(×tamp) < 0) { // vsync not supported! usleep( getDelayToNextVSyncUs(×tamp) ); } + mLastHwVSync = timestamp; // FIXME: Not thread safe return timestamp; } +nsecs_t DisplayHardware::getRefreshTimestamp() const { + // this returns the last refresh timestamp. + // if the last one is not available, we estimate it based on + // the refresh period and whatever closest timestamp we have. + nsecs_t now = systemTime(); + return now - ((now - mLastHwVSync) % mRefreshPeriod); +} + +nsecs_t DisplayHardware::getRefreshPeriod() const { + return mRefreshPeriod; +} + int32_t DisplayHardware::getDelayToNextVSyncUs(nsecs_t* timestamp) const { Mutex::Autolock _l(mFakeVSyncMutex); const nsecs_t period = mRefreshPeriod; diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.h b/services/surfaceflinger/DisplayHardware/DisplayHardware.h index 77da272bbe8b..02be4dcba7fd 100644 --- a/services/surfaceflinger/DisplayHardware/DisplayHardware.h +++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.h @@ -76,7 +76,9 @@ public: uint32_t getMaxViewportDims() const; // waits for the next vsync and returns the timestamp of when it happened - nsecs_t waitForVSync() const; + nsecs_t waitForRefresh() const; + nsecs_t getRefreshPeriod() const; + nsecs_t getRefreshTimestamp() const; uint32_t getPageFlipCount() const; EGLDisplay getEGLDisplay() const { return mDisplay; } @@ -119,6 +121,7 @@ private: mutable Mutex mFakeVSyncMutex; mutable nsecs_t mNextFakeVSync; nsecs_t mRefreshPeriod; + mutable nsecs_t mLastHwVSync; HWComposer* mHwc; diff --git a/services/surfaceflinger/EventThread.cpp b/services/surfaceflinger/EventThread.cpp index 80ab5195cec5..6796d7dee2a7 100644 --- a/services/surfaceflinger/EventThread.cpp +++ b/services/surfaceflinger/EventThread.cpp @@ -129,7 +129,7 @@ bool EventThread::threadLoop() { // at least one listener requested VSYNC mLock.unlock(); - timestamp = mHw.waitForVSync(); + timestamp = mHw.waitForRefresh(); mLock.lock(); mDeliveredEvents++; diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index d4c4b1f93652..a294281428aa 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -38,6 +38,7 @@ #include "Layer.h" #include "SurfaceFlinger.h" #include "SurfaceTextureLayer.h" +#include <math.h> #define DEBUG_RESIZE 0 @@ -54,6 +55,8 @@ Layer::Layer(SurfaceFlinger* flinger, mCurrentTransform(0), mCurrentScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE), mCurrentOpacity(true), + mFrameLatencyNeeded(false), + mFrameLatencyOffset(0), mFormat(PIXEL_FORMAT_NONE), mGLExtensions(GLExtensions::getInstance()), mOpaqueLayer(true), @@ -65,6 +68,17 @@ Layer::Layer(SurfaceFlinger* flinger, glGenTextures(1, &mTextureName); } +void Layer::onLayerDisplayed() { + if (mFrameLatencyNeeded) { + const DisplayHardware& hw(graphicPlane(0).displayHardware()); + mFrameStats[mFrameLatencyOffset].timestamp = mSurfaceTexture->getTimestamp(); + mFrameStats[mFrameLatencyOffset].set = systemTime(); + mFrameStats[mFrameLatencyOffset].vsync = hw.getRefreshTimestamp(); + mFrameLatencyOffset = (mFrameLatencyOffset + 1) % 128; + mFrameLatencyNeeded = false; + } +} + void Layer::onFirstRef() { LayerBaseClient::onFirstRef(); @@ -408,6 +422,7 @@ void Layer::lockPageFlip(bool& recomputeVisibleRegions) // update the active buffer mActiveBuffer = mSurfaceTexture->getCurrentBuffer(); + mFrameLatencyNeeded = true; const Rect crop(mSurfaceTexture->getCurrentCrop()); const uint32_t transform(mSurfaceTexture->getCurrentTransform()); @@ -538,11 +553,33 @@ void Layer::dump(String8& result, char* buffer, size_t SIZE) const result.append(buffer); + LayerBase::dumpStats(result, buffer, SIZE); + if (mSurfaceTexture != 0) { mSurfaceTexture->dump(result, " ", buffer, SIZE); } } +void Layer::dumpStats(String8& result, char* buffer, size_t SIZE) const +{ + LayerBaseClient::dumpStats(result, buffer, SIZE); + const size_t o = mFrameLatencyOffset; + const DisplayHardware& hw(graphicPlane(0).displayHardware()); + const nsecs_t period = hw.getRefreshPeriod(); + result.appendFormat("%lld\n", period); + for (size_t i=0 ; i<128 ; i++) { + const size_t index = (o+i) % 128; + const nsecs_t time_app = mFrameStats[index].timestamp; + const nsecs_t time_set = mFrameStats[index].set; + const nsecs_t time_vsync = mFrameStats[index].vsync; + result.appendFormat("%lld\t%lld\t%lld\n", + time_app, + time_vsync, + time_set); + } + result.append("\n"); +} + uint32_t Layer::getEffectiveUsage(uint32_t usage) const { // TODO: should we do something special if mSecure is set? diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 2b9471b011b6..b3fa5e739273 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -34,6 +34,7 @@ #include "LayerBase.h" #include "SurfaceTextureLayer.h" #include "Transform.h" +#include <utils/Timers.h> namespace android { @@ -78,12 +79,15 @@ public: // LayerBaseClient interface virtual wp<IBinder> getSurfaceTextureBinder() const; + virtual void onLayerDisplayed(); + // only for debugging inline const sp<GraphicBuffer>& getActiveBuffer() const { return mActiveBuffer; } protected: virtual void onFirstRef(); virtual void dump(String8& result, char* scratch, size_t size) const; + virtual void dumpStats(String8& result, char* buffer, size_t SIZE) const; private: friend class SurfaceTextureLayer; @@ -110,6 +114,16 @@ private: uint32_t mCurrentTransform; uint32_t mCurrentScalingMode; bool mCurrentOpacity; + bool mFrameLatencyNeeded; + int mFrameLatencyOffset; + struct Statistics { + Statistics() : timestamp(0), set(0), vsync(0) { } + nsecs_t timestamp; // buffer timestamp + nsecs_t set; // buffer displayed timestamp + nsecs_t vsync; // vsync immediately before set + }; + // protected by mLock + Statistics mFrameStats[128]; // constants PixelFormat mFormat; @@ -121,9 +135,6 @@ private: bool mSecure; // no screenshots bool mProtectedByApp; // application requires protected path to external sink Region mPostedDirtyRegion; - - // binder thread, transaction thread - mutable Mutex mLock; }; // --------------------------------------------------------------------------- diff --git a/services/surfaceflinger/LayerBase.cpp b/services/surfaceflinger/LayerBase.cpp index 37879f1a47d0..1e2c4cb54747 100644 --- a/services/surfaceflinger/LayerBase.cpp +++ b/services/surfaceflinger/LayerBase.cpp @@ -471,6 +471,9 @@ void LayerBase::drawWithOpenGL(const Region& clip) const void LayerBase::dump(String8& result, char* buffer, size_t SIZE) const { const Layer::State& s(drawingState()); + s.transparentRegion.dump(result, "transparentRegion"); + transparentRegionScreen.dump(result, "transparentRegionScreen"); + visibleRegionScreen.dump(result, "visibleRegionScreen"); snprintf(buffer, SIZE, "+ %s %p (%s)\n" " " @@ -491,6 +494,9 @@ void LayerBase::shortDump(String8& result, char* scratch, size_t size) const LayerBase::dump(result, scratch, size); } +void LayerBase::dumpStats(String8& result, char* scratch, size_t SIZE) const +{ +} // --------------------------------------------------------------------------- diff --git a/services/surfaceflinger/LayerBase.h b/services/surfaceflinger/LayerBase.h index 7f6214510d33..03d2cc6a8b3e 100644 --- a/services/surfaceflinger/LayerBase.h +++ b/services/surfaceflinger/LayerBase.h @@ -205,10 +205,13 @@ public: /** called with the state lock when the surface is removed from the * current list */ virtual void onRemoved() { }; - + + virtual void onLayerDisplayed() { }; + /** always call base class first */ virtual void dump(String8& result, char* scratch, size_t size) const; virtual void shortDump(String8& result, char* scratch, size_t size) const; + virtual void dumpStats(String8& result, char* buffer, size_t SIZE) const; enum { // flags for doTransaction() diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index af474022dddf..883b642cac0e 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -431,7 +431,7 @@ bool SurfaceFlinger::threadLoop() } else { // pretend we did the post hw.compositionComplete(); - hw.waitForVSync(); + hw.waitForRefresh(); } return true; } @@ -445,6 +445,12 @@ void SurfaceFlinger::postFramebuffer() const nsecs_t now = systemTime(); mDebugInSwapBuffers = now; hw.flip(mSwapRegion); + + size_t numLayers = mVisibleLayersSortedByZ.size(); + for (size_t i = 0; i < numLayers; i++) { + mVisibleLayersSortedByZ[i]->onLayerDisplayed(); + } + mLastSwapBufferTime = systemTime() - now; mDebugInSwapBuffers = 0; mSwapRegion.clear(); @@ -1463,14 +1469,6 @@ status_t SurfaceFlinger::dump(int fd, const Vector<String16>& args) IPCThreadState::self()->getCallingUid()); result.append(buffer); } else { - - // figure out if we're stuck somewhere - const nsecs_t now = systemTime(); - const nsecs_t inSwapBuffers(mDebugInSwapBuffers); - const nsecs_t inTransaction(mDebugInTransaction); - nsecs_t inSwapBuffersDuration = (inSwapBuffers) ? now-inSwapBuffers : 0; - nsecs_t inTransactionDuration = (inTransaction) ? now-inTransaction : 0; - // Try to get the main lock, but don't insist if we can't // (this would indicate SF is stuck, but we want to be able to // print something in dumpsys). @@ -1486,117 +1484,157 @@ status_t SurfaceFlinger::dump(int fd, const Vector<String16>& args) result.append(buffer); } - /* - * Dump the visible layer list - */ - const LayerVector& currentLayers = mCurrentState.layersSortedByZ; - const size_t count = currentLayers.size(); - snprintf(buffer, SIZE, "Visible layers (count = %d)\n", count); - result.append(buffer); - for (size_t i=0 ; i<count ; i++) { - const sp<LayerBase>& layer(currentLayers[i]); - layer->dump(result, buffer, SIZE); - const Layer::State& s(layer->drawingState()); - s.transparentRegion.dump(result, "transparentRegion"); - layer->transparentRegionScreen.dump(result, "transparentRegionScreen"); - layer->visibleRegionScreen.dump(result, "visibleRegionScreen"); + bool dumpAll = true; + size_t index = 0; + if (args.size()) { + dumpAll = false; + if (args[index] == String16("--latency")) { + index++; + dumpStatsLocked(args, index, result, buffer, SIZE); + } } - /* - * Dump the layers in the purgatory - */ + if (dumpAll) { + dumpAllLocked(result, buffer, SIZE); + } - const size_t purgatorySize = mLayerPurgatory.size(); - snprintf(buffer, SIZE, "Purgatory state (%d entries)\n", purgatorySize); - result.append(buffer); - for (size_t i=0 ; i<purgatorySize ; i++) { - const sp<LayerBase>& layer(mLayerPurgatory.itemAt(i)); - layer->shortDump(result, buffer, SIZE); + if (locked) { + mStateLock.unlock(); } + } + write(fd, result.string(), result.size()); + return NO_ERROR; +} - /* - * Dump SurfaceFlinger global state - */ +void SurfaceFlinger::dumpStatsLocked(const Vector<String16>& args, size_t& index, + String8& result, char* buffer, size_t SIZE) const +{ + String8 name; + if (index < args.size()) { + name = String8(args[index]); + index++; + } - snprintf(buffer, SIZE, "SurfaceFlinger global state:\n"); - result.append(buffer); + const LayerVector& currentLayers = mCurrentState.layersSortedByZ; + const size_t count = currentLayers.size(); + for (size_t i=0 ; i<count ; i++) { + const sp<LayerBase>& layer(currentLayers[i]); + if (name.isEmpty()) { + snprintf(buffer, SIZE, "%s\n", layer->getName().string()); + result.append(buffer); + } + if (name.isEmpty() || (name == layer->getName())) { + layer->dumpStats(result, buffer, SIZE); + } + } +} - const GLExtensions& extensions(GLExtensions::getInstance()); - snprintf(buffer, SIZE, "GLES: %s, %s, %s\n", - extensions.getVendor(), - extensions.getRenderer(), - extensions.getVersion()); - result.append(buffer); +void SurfaceFlinger::dumpAllLocked( + String8& result, char* buffer, size_t SIZE) const +{ + // figure out if we're stuck somewhere + const nsecs_t now = systemTime(); + const nsecs_t inSwapBuffers(mDebugInSwapBuffers); + const nsecs_t inTransaction(mDebugInTransaction); + nsecs_t inSwapBuffersDuration = (inSwapBuffers) ? now-inSwapBuffers : 0; + nsecs_t inTransactionDuration = (inTransaction) ? now-inTransaction : 0; - snprintf(buffer, SIZE, "EGL : %s\n", - eglQueryString(graphicPlane(0).getEGLDisplay(), - EGL_VERSION_HW_ANDROID)); - result.append(buffer); + /* + * Dump the visible layer list + */ + const LayerVector& currentLayers = mCurrentState.layersSortedByZ; + const size_t count = currentLayers.size(); + snprintf(buffer, SIZE, "Visible layers (count = %d)\n", count); + result.append(buffer); + for (size_t i=0 ; i<count ; i++) { + const sp<LayerBase>& layer(currentLayers[i]); + layer->dump(result, buffer, SIZE); + } - snprintf(buffer, SIZE, "EXTS: %s\n", extensions.getExtension()); - result.append(buffer); + /* + * Dump the layers in the purgatory + */ - mWormholeRegion.dump(result, "WormholeRegion"); - const DisplayHardware& hw(graphicPlane(0).displayHardware()); - snprintf(buffer, SIZE, - " orientation=%d, canDraw=%d\n", - mCurrentState.orientation, hw.canDraw()); - result.append(buffer); - snprintf(buffer, SIZE, - " last eglSwapBuffers() time: %f us\n" - " last transaction time : %f us\n" - " refresh-rate : %f fps\n" - " x-dpi : %f\n" - " y-dpi : %f\n", - mLastSwapBufferTime/1000.0, - mLastTransactionTime/1000.0, - hw.getRefreshRate(), - hw.getDpiX(), - hw.getDpiY()); - result.append(buffer); + const size_t purgatorySize = mLayerPurgatory.size(); + snprintf(buffer, SIZE, "Purgatory state (%d entries)\n", purgatorySize); + result.append(buffer); + for (size_t i=0 ; i<purgatorySize ; i++) { + const sp<LayerBase>& layer(mLayerPurgatory.itemAt(i)); + layer->shortDump(result, buffer, SIZE); + } - if (inSwapBuffersDuration || !locked) { - snprintf(buffer, SIZE, " eglSwapBuffers time: %f us\n", - inSwapBuffersDuration/1000.0); - result.append(buffer); - } + /* + * Dump SurfaceFlinger global state + */ - if (inTransactionDuration || !locked) { - snprintf(buffer, SIZE, " transaction time: %f us\n", - inTransactionDuration/1000.0); - result.append(buffer); - } + snprintf(buffer, SIZE, "SurfaceFlinger global state:\n"); + result.append(buffer); - /* - * VSYNC state - */ - mEventThread->dump(result, buffer, SIZE); + const GLExtensions& extensions(GLExtensions::getInstance()); + snprintf(buffer, SIZE, "GLES: %s, %s, %s\n", + extensions.getVendor(), + extensions.getRenderer(), + extensions.getVersion()); + result.append(buffer); - /* - * Dump HWComposer state - */ - HWComposer& hwc(hw.getHwComposer()); - snprintf(buffer, SIZE, "h/w composer state:\n"); - result.append(buffer); - snprintf(buffer, SIZE, " h/w composer %s and %s\n", - hwc.initCheck()==NO_ERROR ? "present" : "not present", - (mDebugDisableHWC || mDebugRegion) ? "disabled" : "enabled"); - result.append(buffer); - hwc.dump(result, buffer, SIZE, mVisibleLayersSortedByZ); + snprintf(buffer, SIZE, "EGL : %s\n", + eglQueryString(graphicPlane(0).getEGLDisplay(), + EGL_VERSION_HW_ANDROID)); + result.append(buffer); - /* - * Dump gralloc state - */ - const GraphicBufferAllocator& alloc(GraphicBufferAllocator::get()); - alloc.dump(result); - hw.dump(result); + snprintf(buffer, SIZE, "EXTS: %s\n", extensions.getExtension()); + result.append(buffer); - if (locked) { - mStateLock.unlock(); - } - } - write(fd, result.string(), result.size()); - return NO_ERROR; + mWormholeRegion.dump(result, "WormholeRegion"); + const DisplayHardware& hw(graphicPlane(0).displayHardware()); + snprintf(buffer, SIZE, + " orientation=%d, canDraw=%d\n", + mCurrentState.orientation, hw.canDraw()); + result.append(buffer); + snprintf(buffer, SIZE, + " last eglSwapBuffers() time: %f us\n" + " last transaction time : %f us\n" + " refresh-rate : %f fps\n" + " x-dpi : %f\n" + " y-dpi : %f\n", + mLastSwapBufferTime/1000.0, + mLastTransactionTime/1000.0, + hw.getRefreshRate(), + hw.getDpiX(), + hw.getDpiY()); + result.append(buffer); + + snprintf(buffer, SIZE, " eglSwapBuffers time: %f us\n", + inSwapBuffersDuration/1000.0); + result.append(buffer); + + snprintf(buffer, SIZE, " transaction time: %f us\n", + inTransactionDuration/1000.0); + result.append(buffer); + + /* + * VSYNC state + */ + mEventThread->dump(result, buffer, SIZE); + + /* + * Dump HWComposer state + */ + HWComposer& hwc(hw.getHwComposer()); + snprintf(buffer, SIZE, "h/w composer state:\n"); + result.append(buffer); + snprintf(buffer, SIZE, " h/w composer %s and %s\n", + hwc.initCheck()==NO_ERROR ? "present" : "not present", + (mDebugDisableHWC || mDebugRegion) ? "disabled" : "enabled"); + result.append(buffer); + hwc.dump(result, buffer, SIZE, mVisibleLayersSortedByZ); + + /* + * Dump gralloc state + */ + const GraphicBufferAllocator& alloc(GraphicBufferAllocator::get()); + alloc.dump(result); + hw.dump(result); } status_t SurfaceFlinger::onTransact( diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 7f6c90ca0685..c976e5a00ce3 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -337,6 +337,9 @@ private: void debugFlashRegions(); void drawWormhole() const; + void dumpStatsLocked(const Vector<String16>& args, size_t& index, + String8& result, char* buffer, size_t SIZE) const; + void dumpAllLocked(String8& result, char* buffer, size_t SIZE) const; mutable MessageQueue mEventQueue; diff --git a/telephony/java/com/android/internal/telephony/DataConnection.java b/telephony/java/com/android/internal/telephony/DataConnection.java index 1336818661a5..d0e304fc1a79 100644 --- a/telephony/java/com/android/internal/telephony/DataConnection.java +++ b/telephony/java/com/android/internal/telephony/DataConnection.java @@ -328,8 +328,11 @@ public abstract class DataConnection extends StateMachine { String reason = null; if (dp.onCompletedMsg != null) { + // Get ApnContext, but only valid on GSM devices this is a string on CDMA devices. Message msg = dp.onCompletedMsg; - alreadySent = (ApnContext)msg.obj; + if (msg.obj instanceof ApnContext) { + alreadySent = (ApnContext)msg.obj; + } reason = dp.reason; if (VDBG) { log(String.format("msg=%s msg.obj=%s", msg.toString(), diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java index c0384789ed05..5ab2c58a4959 100644 --- a/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java +++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2011 The Android Open Source Project + * Copyright (C) 2008-2012 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. @@ -72,6 +72,7 @@ public class RSTestCore { unitTests.add(new UT_alloc(this, mRes, mCtx)); unitTests.add(new UT_refcount(this, mRes, mCtx)); unitTests.add(new UT_foreach(this, mRes, mCtx)); + unitTests.add(new UT_atomic(this, mRes, mCtx)); unitTests.add(new UT_math(this, mRes, mCtx)); unitTests.add(new UT_fp_mad(this, mRes, mCtx)); /* diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_atomic.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_atomic.java new file mode 100644 index 000000000000..267c5b2cf649 --- /dev/null +++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_atomic.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2012 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.rs.test; + +import android.content.Context; +import android.content.res.Resources; +import android.renderscript.*; + +public class UT_atomic extends UnitTest { + private Resources mRes; + + protected UT_atomic(RSTestCore rstc, Resources res, Context ctx) { + super(rstc, "Atomics", ctx); + mRes = res; + } + + public void run() { + RenderScript pRS = RenderScript.create(mCtx); + ScriptC_atomic s = new ScriptC_atomic(pRS, mRes, R.raw.atomic); + pRS.setMessageHandler(mRsMessage); + s.invoke_atomic_test(); + pRS.finish(); + waitForMessage(); + pRS.destroy(); + } +} diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/atomic.rs b/tests/RenderScriptTests/tests/src/com/android/rs/test/atomic.rs new file mode 100644 index 000000000000..f0a504196adc --- /dev/null +++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/atomic.rs @@ -0,0 +1,77 @@ +#include "shared.rsh" + +// Testing atomic operations +static bool testUMax(uint32_t dst, uint32_t src) { + bool failed = false; + uint32_t old = dst; + uint32_t expect = (dst > src ? dst : src); + uint32_t ret = rsAtomicMax(&dst, src); + _RS_ASSERT(old == ret); + _RS_ASSERT(dst == expect); + return failed; +} + +static bool testUMin(uint32_t dst, uint32_t src) { + bool failed = false; + uint32_t old = dst; + uint32_t expect = (dst < src ? dst : src); + uint32_t ret = rsAtomicMin(&dst, src); + _RS_ASSERT(old == ret); + _RS_ASSERT(dst == expect); + return failed; +} + +static bool testUCas(uint32_t dst, uint32_t cmp, uint32_t swp) { + bool failed = false; + uint32_t old = dst; + uint32_t expect = (dst == cmp ? swp : dst); + uint32_t ret = rsAtomicCas(&dst, cmp, swp); + _RS_ASSERT(old == ret); + _RS_ASSERT(dst == expect); + return failed; +} + +static bool test_atomics() { + bool failed = false; + + failed |= testUMax(5, 6); + failed |= testUMax(6, 5); + failed |= testUMax(5, 0xf0000006); + failed |= testUMax(0xf0000006, 5); + + failed |= testUMin(5, 6); + failed |= testUMin(6, 5); + failed |= testUMin(5, 0xf0000006); + failed |= testUMin(0xf0000006, 5); + + failed |= testUCas(4, 4, 5); + failed |= testUCas(4, 5, 5); + failed |= testUCas(5, 5, 4); + failed |= testUCas(5, 4, 4); + failed |= testUCas(0xf0000004, 0xf0000004, 0xf0000005); + failed |= testUCas(0xf0000004, 0xf0000005, 0xf0000005); + failed |= testUCas(0xf0000005, 0xf0000005, 0xf0000004); + failed |= testUCas(0xf0000005, 0xf0000004, 0xf0000004); + + if (failed) { + rsDebug("test_atomics FAILED", 0); + } + else { + rsDebug("test_atomics PASSED", 0); + } + + return failed; +} + +void atomic_test() { + bool failed = false; + failed |= test_atomics(); + + if (failed) { + rsSendToClientBlocking(RS_MSG_TEST_FAILED); + } + else { + rsSendToClientBlocking(RS_MSG_TEST_PASSED); + } +} + diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp index f0c215eeffa5..6ce665b467f1 100644 --- a/tools/aapt/AaptAssets.cpp +++ b/tools/aapt/AaptAssets.cpp @@ -1084,12 +1084,17 @@ bool AaptGroupEntry::getDensityName(const char* name, if (out) out->density = ResTable_config::DENSITY_HIGH; return true; } - + if (strcmp(name, "xhdpi") == 0) { - if (out) out->density = ResTable_config::DENSITY_MEDIUM*2; + if (out) out->density = ResTable_config::DENSITY_XHIGH; return true; } - + + if (strcmp(name, "xxhdpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_XXHIGH; + return true; + } + char* c = (char*)name; while (*c >= '0' && *c <= '9') { c++; |