diff --git a/src/api-impl/android/content/ContentProvider.java b/src/api-impl/android/content/ContentProvider.java index 4d3b372b..05b85fab 100644 --- a/src/api-impl/android/content/ContentProvider.java +++ b/src/api-impl/android/content/ContentProvider.java @@ -1,8 +1,15 @@ package android.content; -import android.content.pm.PackageParser; +import java.util.HashMap; +import java.util.Map; -public class ContentProvider { +import android.content.pm.PackageParser; +import android.database.Cursor; +import android.net.Uri; + +public abstract class ContentProvider { + + static Map providers = new HashMap(); static void createContentProviders() throws Exception { for (PackageParser.Provider provider_parsed : Context.pkg.providers) { @@ -11,6 +18,7 @@ public class ContentProvider { Class providerCls = Class.forName(providerName).asSubclass(ContentProvider.class); ContentProvider provider = providerCls.getConstructor().newInstance(); provider.onCreate(); + providers.put(provider_parsed.info.authority, provider); } } @@ -20,4 +28,10 @@ public class ContentProvider { return new Context(); } + public abstract Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder); + + public abstract Uri insert(Uri uri, ContentValues values); + + public abstract int delete(Uri uri, String selection, String[] selectionArgs); + } diff --git a/src/api-impl/android/content/ContentResolver.java b/src/api-impl/android/content/ContentResolver.java index c5595a94..9fdc96dd 100644 --- a/src/api-impl/android/content/ContentResolver.java +++ b/src/api-impl/android/content/ContentResolver.java @@ -3,7 +3,6 @@ package android.content; import java.io.File; import java.io.FileNotFoundException; -import android.database.AbstractCursor; import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; @@ -28,17 +27,11 @@ public class ContentResolver { } public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { - return new AbstractCursor() { - public int getCount() { return 0; } - public String[] getColumnNames() { return new String[0]; } - public String getString(int column) { throw new IndexOutOfBoundsException(); } - public short getShort(int column) { throw new IndexOutOfBoundsException(); } - public int getInt(int column) { throw new IndexOutOfBoundsException(); } - public long getLong(int column) { throw new IndexOutOfBoundsException(); } - public float getFloat(int column) { throw new IndexOutOfBoundsException(); } - public double getDouble(int column) { throw new IndexOutOfBoundsException(); } - public boolean isNull(int column) { throw new IndexOutOfBoundsException(); } - }; + ContentProvider provider = ContentProvider.providers.get(uri.getAuthority()); + if (provider != null) + return provider.query(uri, projection, selection, selectionArgs, sortOrder); + else + return null; } public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) { @@ -46,6 +39,18 @@ public class ContentResolver { } public int delete(Uri uri, String selection, String[] selectionArgs) { - return 0; + ContentProvider provider = ContentProvider.providers.get(uri.getAuthority()); + if (provider != null) + return provider.delete(uri, selection, selectionArgs); + else + return 0; + } + + public Uri insert(Uri uri, ContentValues values) { + ContentProvider provider = ContentProvider.providers.get(uri.getAuthority()); + if (provider != null) + return provider.insert(uri, values); + else + return null; } } diff --git a/src/api-impl/android/content/ContentUris.java b/src/api-impl/android/content/ContentUris.java new file mode 100644 index 00000000..c10cd378 --- /dev/null +++ b/src/api-impl/android/content/ContentUris.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 android.content; + +import java.util.List; + +import android.net.Uri; + +/** +* Utility methods useful for working with {@link android.net.Uri} objects +* that use the "content" (content://) scheme. +* +*

+* Content URIs have the syntax +*

+*

+* content://authority/path/id +*

+*
+*
+* content: +*
+*
+* The scheme portion of the URI. This is always set to {@link +* android.content.ContentResolver#SCHEME_CONTENT ContentResolver.SCHEME_CONTENT} (value +* content://). +*
+*
+* authority +*
+*
+* A string that identifies the entire content provider. All the content URIs for the provider +* start with this string. To guarantee a unique authority, providers should consider +* using an authority that is the same as the provider class' package identifier. +*
+*
+* path +*
+*
+* Zero or more segments, separated by a forward slash (/), that identify +* some subset of the provider's data. Most providers use the path part to identify +* individual tables. Individual segments in the path are often called +* "directories" although they do not refer to file directories. The right-most +* segment in a path is often called a "twig" +*
+*
+* id +*
+*
+* A unique numeric identifier for a single row in the subset of data identified by the +* preceding path part. Most providers recognize content URIs that contain an id part +* and give them special handling. A table that contains a column named _ID +* often expects the id part to be a particular value for that column. +*
+*
+* +*/ +public class ContentUris { + + /** + * Converts the last path segment to a long. + * + *

This supports a common convention for content URIs where an ID is + * stored in the last segment. + * + * @throws UnsupportedOperationException if this isn't a hierarchical URI + * @throws NumberFormatException if the last segment isn't a number + * + * @return the long conversion of the last segment or -1 if the path is + * empty + */ + public static long parseId(Uri contentUri) { + String last = contentUri.getLastPathSegment(); + return last == null ? -1 : Long.parseLong(last); + } + + /** + * Appends the given ID to the end of the path. + * + * @param builder to append the ID to + * @param id to append + * + * @return the given builder + */ + public static Uri.Builder appendId(Uri.Builder builder, long id) { + return builder.appendEncodedPath(String.valueOf(id)); + } + + /** + * Appends the given ID to the end of the path. + * + * @param contentUri to start with + * @param id to append + * + * @return a new URI with the given ID appended to the end of the path + */ + public static Uri withAppendedId(Uri contentUri, long id) { + return appendId(contentUri.buildUpon(), id).build(); + } + + /** + * Removes any ID from the end of the path. + * + * @param contentUri that ends with an ID + * @return a new URI with the ID removed from the end of the path + * @throws IllegalArgumentException when the given URI has no ID to remove + * from the end of the path + */ + public static Uri removeId(Uri contentUri) { + // Verify that we have a valid ID to actually remove + final String last = contentUri.getLastPathSegment(); + if (last == null) { + throw new IllegalArgumentException("No path segments to remove"); + } else { + Long.parseLong(last); + } + + final List segments = contentUri.getPathSegments(); + final Uri.Builder builder = contentUri.buildUpon(); + builder.path(null); + for (int i = 0; i < segments.size() - 1; i++) { + builder.appendPath(segments.get(i)); + } + return builder.build(); + } +} diff --git a/src/api-impl/android/content/SearchRecentSuggestionsProvider.java b/src/api-impl/android/content/SearchRecentSuggestionsProvider.java index fb805d38..6bb77003 100644 --- a/src/api-impl/android/content/SearchRecentSuggestionsProvider.java +++ b/src/api-impl/android/content/SearchRecentSuggestionsProvider.java @@ -1,5 +1,23 @@ package android.content; +import android.database.Cursor; +import android.net.Uri; + public class SearchRecentSuggestionsProvider extends ContentProvider { public void setupSuggestions(String s, int i) {} + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + throw new UnsupportedOperationException("Unimplemented method 'query'"); + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + throw new UnsupportedOperationException("Unimplemented method 'insert'"); + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + throw new UnsupportedOperationException("Unimplemented method 'delete'"); + } } diff --git a/src/api-impl/android/content/UriMatcher.java b/src/api-impl/android/content/UriMatcher.java index 85174ef8..0629d833 100644 --- a/src/api-impl/android/content/UriMatcher.java +++ b/src/api-impl/android/content/UriMatcher.java @@ -1,8 +1,282 @@ +/* + * Copyright (C) 2006 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 android.content; -public class UriMatcher { +import java.util.ArrayList; +import java.util.List; - public UriMatcher(int code) {} +import android.net.Uri; - public void addURI(String authority, String path, int code) {} +/** +Utility class to aid in matching URIs in content providers. + +

To use this class, build up a tree of UriMatcher objects. +For example: +

+	private static final int PEOPLE = 1;
+	private static final int PEOPLE_ID = 2;
+	private static final int PEOPLE_PHONES = 3;
+	private static final int PEOPLE_PHONES_ID = 4;
+	private static final int PEOPLE_CONTACTMETHODS = 7;
+	private static final int PEOPLE_CONTACTMETHODS_ID = 8;
+
+	private static final int DELETED_PEOPLE = 20;
+
+	private static final int PHONES = 9;
+	private static final int PHONES_ID = 10;
+	private static final int PHONES_FILTER = 14;
+
+	private static final int CONTACTMETHODS = 18;
+	private static final int CONTACTMETHODS_ID = 19;
+
+	private static final int CALLS = 11;
+	private static final int CALLS_ID = 12;
+	private static final int CALLS_FILTER = 15;
+
+	private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+
+	static
+	{
+		sURIMatcher.addURI("contacts", "people", PEOPLE);
+		sURIMatcher.addURI("contacts", "people/#", PEOPLE_ID);
+		sURIMatcher.addURI("contacts", "people/#/phones", PEOPLE_PHONES);
+		sURIMatcher.addURI("contacts", "people/#/phones/#", PEOPLE_PHONES_ID);
+		sURIMatcher.addURI("contacts", "people/#/contact_methods", PEOPLE_CONTACTMETHODS);
+		sURIMatcher.addURI("contacts", "people/#/contact_methods/#", PEOPLE_CONTACTMETHODS_ID);
+		sURIMatcher.addURI("contacts", "deleted_people", DELETED_PEOPLE);
+		sURIMatcher.addURI("contacts", "phones", PHONES);
+		sURIMatcher.addURI("contacts", "phones/filter/*", PHONES_FILTER);
+		sURIMatcher.addURI("contacts", "phones/#", PHONES_ID);
+		sURIMatcher.addURI("contacts", "contact_methods", CONTACTMETHODS);
+		sURIMatcher.addURI("contacts", "contact_methods/#", CONTACTMETHODS_ID);
+		sURIMatcher.addURI("call_log", "calls", CALLS);
+		sURIMatcher.addURI("call_log", "calls/filter/*", CALLS_FILTER);
+		sURIMatcher.addURI("call_log", "calls/#", CALLS_ID);
+	}
+
+

Starting from API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, paths can start + with a leading slash. For example: +

+		sURIMatcher.addURI("contacts", "/people", PEOPLE);
+
+

Then when you need to match against a URI, call {@link #match}, providing +the URL that you have been given. You can use the result to build a query, +return a type, insert or delete a row, or whatever you need, without duplicating +all of the if-else logic that you would otherwise need. For example: +

+	public String getType(Uri url)
+	{
+		int match = sURIMatcher.match(url);
+		switch (match)
+		{
+			case PEOPLE:
+				return "vnd.android.cursor.dir/person";
+			case PEOPLE_ID:
+				return "vnd.android.cursor.item/person";
+... snip ...
+				return "vnd.android.cursor.dir/snail-mail";
+			case PEOPLE_ADDRESS_ID:
+				return "vnd.android.cursor.item/snail-mail";
+			default:
+				return null;
+		}
+	}
+
+instead of: +
+	public String getType(Uri url)
+	{
+		List pathSegments = url.getPathSegments();
+		if (pathSegments.size() >= 2) {
+			if ("people".equals(pathSegments.get(1))) {
+				if (pathSegments.size() == 2) {
+					return "vnd.android.cursor.dir/person";
+				} else if (pathSegments.size() == 3) {
+					return "vnd.android.cursor.item/person";
+... snip ...
+					return "vnd.android.cursor.dir/snail-mail";
+				} else if (pathSegments.size() == 3) {
+					return "vnd.android.cursor.item/snail-mail";
+				}
+			}
+		}
+		return null;
+	}
+
+*/ +public class UriMatcher +{ + public static final int NO_MATCH = -1; + /** + * Creates the root node of the URI tree. + * + * @param code the code to match for the root URI + */ + public UriMatcher(int code) + { + mCode = code; + mWhich = -1; + mChildren = new ArrayList(); + mText = null; + } + + private UriMatcher(int which, String text) + { + mCode = NO_MATCH; + mWhich = which; + mChildren = new ArrayList(); + mText = text; + } + + /** + * Add a URI to match, and the code to return when this URI is + * matched. URI nodes may be exact match string, the token "*" + * that matches any text, or the token "#" that matches only + * numbers. + *

+ * Starting from API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, + * this method will accept a leading slash in the path. + * + * @param authority the authority to match + * @param path the path to match. * may be used as a wild card for + * any text, and # may be used as a wild card for numbers. + * @param code the code that is returned when a URI is matched + * against the given components. Must be positive. + */ + public void addURI(String authority, String path, int code) + { + if (code < 0) { + throw new IllegalArgumentException("code " + code + " is invalid: it must be positive"); + } + + String[] tokens = null; + if (path != null) { + String newPath = path; + // Strip leading slash if present. + if (path.length() > 1 && path.charAt(0) == '/') { + newPath = path.substring(1); + } + tokens = newPath.split("/"); + } + + int numTokens = tokens != null ? tokens.length : 0; + UriMatcher node = this; + for (int i = -1; i < numTokens; i++) { + String token = i < 0 ? authority : tokens[i]; + ArrayList children = node.mChildren; + int numChildren = children.size(); + UriMatcher child; + int j; + for (j = 0; j < numChildren; j++) { + child = children.get(j); + if (token.equals(child.mText)) { + node = child; + break; + } + } + if (j == numChildren) { + // Child not found, create it + child = createChild(token); + node.mChildren.add(child); + node = child; + } + } + node.mCode = code; + } + + private static UriMatcher createChild(String token) { + switch (token) { + case "#": + return new UriMatcher(NUMBER, "#"); + case "*": + return new UriMatcher(TEXT, "*"); + default: + return new UriMatcher(EXACT, token); + } + } + + /** + * Try to match against the path in a url. + * + * @param uri The url whose path we will match against. + * + * @return The code for the matched node (added using addURI), + * or -1 if there is no matched node. + */ + public int match(Uri uri) + { + final List pathSegments = uri.getPathSegments(); + final int li = pathSegments.size(); + + UriMatcher node = this; + + if (li == 0 && uri.getAuthority() == null) { + return this.mCode; + } + + for (int i=-1; i list = node.mChildren; + if (list == null) { + break; + } + node = null; + int lj = list.size(); + for (int j=0; j '9') { + break which_switch; + } + } + node = n; + break; + case TEXT: + node = n; + break; + } + if (node != null) { + break; + } + } + if (node == null) { + return NO_MATCH; + } + } + + return node.mCode; + } + + private static final int EXACT = 0; + private static final int NUMBER = 1; + private static final int TEXT = 2; + + private int mCode; + private final int mWhich; + private final String mText; + private ArrayList mChildren; } diff --git a/src/api-impl/meson.build b/src/api-impl/meson.build index 23f87d60..2a809857 100644 --- a/src/api-impl/meson.build +++ b/src/api-impl/meson.build @@ -65,6 +65,7 @@ hax_jar = jar('hax', [ 'android/content/ComponentName.java', 'android/content/ContentProvider.java', 'android/content/ContentResolver.java', + 'android/content/ContentUris.java', 'android/content/ContentValues.java', 'android/content/Context.java', 'android/content/ContextWrapper.java',