私有内容供应器是只由单一应用使用的内容提供者,它是最安全的内容供应器 [8]。
下面展示了如何实现私有内容供应器的示例代码。
要点(创建内容供应器):
将导出属性显式设置为false
。
即使数据来自相同应用,也应该小心并安全地处理收到的请求数据。
可以发送敏感信息,因为它在同一应用内发送和接收所有信息。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.jssec.android.provider.privateprovider">
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:name=".PrivateUserActivity"
android:label="@string/app_name"
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- *** POINT 1 *** Explicitly set the exported attribute to false. -->
<provider
android:name=".PrivateProvider"
android:authorities="org.jssec.android.provider.privateprovider"
android:exported="false" />
</application>
</manifest>
PrivateProvider.java
package org.jssec.android.provider.privateprovider;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
public class PrivateProvider extends ContentProvider {
public static final String AUTHORITY = "org.jssec.android.provider.privateprovider";
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.org.jssec.contenttype";
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.org.jssec.contenttype";
// Expose the interface that the Content Provider provides.
public interface Download {
public static final String PATH = "downloads";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH);
}
public interface Address {
public static final String PATH = "addresses";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH);
}
// UriMatcher
private static final int DOWNLOADS_CODE = 1;
private static final int DOWNLOADS_ID_CODE = 2;
private static final int ADDRESSES_CODE = 3;
private static final int ADDRESSES_ID_CODE = 4;
private static UriMatcher sUriMatcher;
static {
sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
sUriMatcher.addURI(AUTHORITY, Download.PATH, DOWNLOADS_CODE);
sUriMatcher.addURI(AUTHORITY, Download.PATH + "/#", DOWNLOADS_ID_CODE);
sUriMatcher.addURI(AUTHORITY, Address.PATH, ADDRESSES_CODE);
sUriMatcher.addURI(AUTHORITY, Address.PATH + "/#", ADDRESSES_ID_CODE);
}
// Since this is a sample program,
// query method returns the following fixed result always without using database.
private static MatrixCursor sAddressCursor = new MatrixCursor(new String[] { "_id", "city" });
static {
sAddressCursor.addRow(new String[] { "1", "New York" });
sAddressCursor.addRow(new String[] { "2", "Longon" });
sAddressCursor.addRow(new String[] { "3", "Paris" });
}
private static MatrixCursor sDownloadCursor = new MatrixCursor(new String[] { "_id", "path" });
static {
sDownloadCursor.addRow(new String[] { "1", "/sdcard/downloads/sample.jpg" });
sDownloadCursor.addRow(new String[] { "2", "/sdcard/downloads/sample.txt" });
}
@Override
public boolean onCreate() {
return true;
}
@Override
public String getType(Uri uri) {
// *** POINT 2 *** Handle the received request data carefully and securely,
// even though the data comes from the same application.
// Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
// Checking for other parameters are omitted here, due to sample.
// Please refer to "3.2 Handle Input Data Carefully and Securely."
// *** POINT 3 *** Sensitive information can be sent since it is sending and receiving all within the same application.
// However, the result of getType rarely has the sensitive meaning.
switch (sUriMatcher.match(uri)) {
case DOWNLOADS_CODE:
case ADDRESSES_CODE:
return CONTENT_TYPE;
case DOWNLOADS_ID_CODE:
case ADDRESSES_ID_CODE:
return CONTENT_ITEM_TYPE;
default:
throw new IllegalArgumentException("Invalid URI:" + uri);
}
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// *** POINT 2 *** Handle the received request data carefully and securely,
// even though the data comes from the same application.
// Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
// Checking for other parameters are omitted here, due to sample.
// Please refer to "3.2 Handle Input Data Carefully and Securely."
// *** POINT 3 *** Sensitive information can be sent since it is sending and receiving all within the same application.
// It depends on application whether the query result has sensitive meaning or not.
switch (sUriMatcher.match(uri)) {
case DOWNLOADS_CODE:
case DOWNLOADS_ID_CODE:
return sDownloadCursor;
case ADDRESSES_CODE:
case ADDRESSES_ID_CODE:
return sAddressCursor;
default:
throw new IllegalArgumentException("Invalid URI:" + uri);
}
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// *** POINT 2 *** Handle the received request data carefully and securely,
// even though the data comes from the same application.
// Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
// Checking for other parameters are omitted here, due to sample.
// Please refer to "3.2 Handle Input Data Carefully and Securely."
// *** POINT 3 *** Sensitive information can be sent since it is sending and receiving all within the same application.
// It depends on application whether the issued ID has sensitive meaning or not.
switch (sUriMatcher.match(uri)) {
case DOWNLOADS_CODE:
return ContentUris.withAppendedId(Download.CONTENT_URI, 3);
case ADDRESSES_CODE:
return ContentUris.withAppendedId(Address.CONTENT_URI, 4);
default:
throw new IllegalArgumentException("Invalid URI:" + uri);
}
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// *** POINT 2 *** Handle the received request data carefully and securely,
// even though the data comes from the same application.
// Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
// Checking for other parameters are omitted here, due to sample.
// Please refer to "3.2 Handle Input Data Carefully and Securely."
// *** POINT 3 *** Sensitive information can be sent since it is sending and receiving all within the same application.
// It depends on application whether the number of updated records has sensitive meaning or not.
switch (sUriMatcher.match(uri)) {
case DOWNLOADS_CODE:
return 5; // Return number of updated records
case DOWNLOADS_ID_CODE:
return 1;
case ADDRESSES_CODE:
return 15;
case ADDRESSES_ID_CODE:
return 1;
default:
throw new IllegalArgumentException("Invalid URI:" + uri);
}
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// *** POINT 2 *** Handle the received request data carefully and securely,
// even though the data comes from the same application.
// Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
// Checking for other parameters are omitted here, due to sample.
// Please refer to "3.2 Handle Input Data Carefully and Securely."
// *** POINT 3 *** Sensitive information can be sent since it is sending and receiving all within the same application.
// It depends on application whether the number of deleted records has sensitive meaning or not.
switch (sUriMatcher.match(uri)) {
case DOWNLOADS_CODE:
return 10; // Return number of deleted records
case DOWNLOADS_ID_CODE:
return 1;
case ADDRESSES_CODE:
return 20;
case ADDRESSES_ID_CODE:
return 1;
default:
throw new IllegalArgumentException("Invalid URI:" + uri);
}
}
}
下面是活动的示例,它使用私有内容供应器。
要点(使用内容供应器):
敏感信息可以发送,因为目标供应器在相同应用中。
小心和安全地处理收到的结果数据,即使数据来自相同应用。
PrivateUserActivity.java
package org.jssec.android.provider.privateprovider;
import android.app.Activity;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
public class PrivateUserActivity extends Activity {
public void onQueryClick(View view) {
logLine("[Query]");
Cursor cursor = null;
try {
// *** POINT 4 *** Sensitive information can be sent since the destination provider is in the same application.
cursor = getContentResolver().query(
PrivateProvider.Download.CONTENT_URI, null, null, null, null);
// *** POINT 5 *** Handle received result data carefully and securely,
// even though the data comes from the same application.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
if (cursor == null) {
logLine(" null cursor");
} else {
boolean moved = cursor.moveToFirst();
while (moved) {
logLine(String.format(" %d, %s", cursor.getInt(0), cursor.getString(1)));
moved = cursor.moveToNext();
}
}
}
finally {
if (cursor != null) cursor.close();
}
}
public void onInsertClick(View view) {
logLine("[Insert]");
// *** POINT 4 *** Sensitive information can be sent since the destination provider is in the same application.
Uri uri = getContentResolver().insert(PrivateProvider.Download.CONTENT_URI, null);
// *** POINT 5 *** Handle received result data carefully and securely,
// even though the data comes from the same application.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
logLine(" uri:" + uri);
}
public void onUpdateClick(View view) {
logLine("[Update]");
// *** POINT 4 *** Sensitive information can be sent since the destination provider is in the same application.
int count = getContentResolver().update(PrivateProvider.Download.CONTENT_URI, null, null, null);
// *** POINT 5 *** Handle received result data carefully and securely,
// even though the data comes from the same application.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
logLine(String.format(" %s records updated", count));
}
public void onDeleteClick(View view) {
logLine("[Delete]");
// *** POINT 4 *** Sensitive information can be sent since the destination provider is in the same application.
int count = getContentResolver().delete(
PrivateProvider.Download.CONTENT_URI, null, null);
// *** POINT 5 *** Handle received result data carefully and securely,
// even though the data comes from the same application.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
logLine(String.format(" %s records deleted", count));
}
private TextView mLogView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mLogView = (TextView)findViewById(R.id.logview);
}
private void logLine(String line) {
mLogView.append(line);
mLogView.append("¥n");
}
}