4.3.1.5 创建/使用临时内容供应器

临时内容供应器基本上是一个私有内容供应器,但它允许特定的应用访问特定的 URI。通过向目标应用发送一个指定了特殊标志的意图,即可为这些应用提供临时访问权限。内容供应器方的应用可以将访问权限主动授予其他应用,并且还可以将访问权限被动授予索要临时访问权限的应用。

下面展示了实现临时内容供应器的示例代码。

要点(创建内容供应器):

  1. 将导出属性显式设置为false

  2. 使用grant-uri-permission指定路径来临时授予访问权。

  3. 即使数据来自临时访问应用,也应该消息并安全地处理收到的请求数据。

  4. 可以返回公开给临时访问应用的信息。

  5. 为意图指定 URI 来授予临时访问权。

  6. 为意图指定访问权限来授予临时访问权。

  7. 将显式意图发送给应用来授予临时访问权。

  8. 将意图返回给请求临时访问权的应用。

AndroidManifest.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="org.jssec.android.provider.temporaryprovider">
  4. <application
  5. android:icon="@drawable/ic_launcher"
  6. android:label="@string/app_name" >
  7. <activity
  8. android:name=".TemporaryActiveGrantActivity"
  9. android:label="@string/app_name"
  10. android:exported="true" >
  11. <intent-filter>
  12. <action android:name="android.intent.action.MAIN" />
  13. <category android:name="android.intent.category.LAUNCHER" />
  14. </intent-filter>
  15. </activity>
  16. <!-- Temporary Content Provider -->
  17. <!-- *** POINT 1 *** Explicitly set the exported attribute to false. -->
  18. <provider
  19. android:name=".TemporaryProvider"
  20. android:authorities="org.jssec.android.provider.temporaryprovider"
  21. android:exported="false" >
  22. <!-- *** POINT 2 *** Specify the path to grant access temporarily with the grant-uri-permissi
  23. on. -->
  24. <grant-uri-permission android:path="/addresses" />
  25. </provider>
  26. <activity
  27. android:name=".TemporaryPassiveGrantActivity"
  28. android:label="@string/app_name"
  29. android:exported="true" />
  30. </application>
  31. </manifest>

TemporaryProvider.java

  1. package org.jssec.android.provider.temporaryprovider;
  2. import android.content.ContentProvider;
  3. import android.content.ContentUris;
  4. import android.content.ContentValues;
  5. import android.content.UriMatcher;
  6. import android.database.Cursor;
  7. import android.database.MatrixCursor;
  8. import android.net.Uri;
  9. public class TemporaryProvider extends ContentProvider {
  10. public static final String AUTHORITIY = "org.jssec.android.provider.temporaryprovider";
  11. public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.org.jssec.contenttype";
  12. public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.org.jssec.contenttype";
  13. // Expose the interface that the Content Provider provides.
  14. public interface Download {
  15. public static final String PATH = "downloads";
  16. public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITIY + "/" + PATH);
  17. }
  18. public interface Address {
  19. public static final String PATH = "addresses";
  20. public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITIY + "/" + PATH);
  21. }
  22. // UriMatcher
  23. private static final int DOWNLOADS_CODE = 1;
  24. private static final int DOWNLOADS_ID_CODE = 2;
  25. private static final int ADDRESSES_CODE = 3;
  26. private static final int ADDRESSES_ID_CODE = 4;
  27. private static UriMatcher sUriMatcher;
  28. static {
  29. sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
  30. sUriMatcher.addURI(AUTHORITIY, Download.PATH, DOWNLOADS_CODE);
  31. sUriMatcher.addURI(AUTHORITIY, Download.PATH + "/#", DOWNLOADS_ID_CODE);
  32. sUriMatcher.addURI(AUTHORITIY, Address.PATH, ADDRESSES_CODE);
  33. sUriMatcher.addURI(AUTHORITIY, Address.PATH + "/#", ADDRESSES_ID_CODE);
  34. }
  35. // Since this is a sample program,
  36. // query method returns the following fixed result always without using database.
  37. private static MatrixCursor sAddressCursor = new MatrixCursor(new String[] { "_id", "city" });
  38. static {
  39. sAddressCursor.addRow(new String[] { "1", "New York" });
  40. sAddressCursor.addRow(new String[] { "2", "London" });
  41. sAddressCursor.addRow(new String[] { "3", "Paris" });
  42. }
  43. private static MatrixCursor sDownloadCursor = new MatrixCursor(new String[] { "_id", "path" });
  44. static {
  45. sDownloadCursor.addRow(new String[] { "1", "/sdcard/downloads/sample.jpg" });
  46. sDownloadCursor.addRow(new String[] { "2", "/sdcard/downloads/sample.txt" });
  47. }
  48. @Override
  49. public boolean onCreate() {
  50. return true;
  51. }
  52. @Override
  53. public String getType(Uri uri) {
  54. switch (sUriMatcher.match(uri)) {
  55. case DOWNLOADS_CODE:
  56. case ADDRESSES_CODE:
  57. return CONTENT_TYPE;
  58. case DOWNLOADS_ID_CODE:
  59. case ADDRESSES_ID_CODE:
  60. return CONTENT_ITEM_TYPE;
  61. default:
  62. throw new IllegalArgumentException("Invalid URI:" + uri);
  63. }
  64. }
  65. @Override
  66. public Cursor query(Uri uri, String[] projection, String selection,
  67. String[] selectionArgs, String sortOrder) {
  68. // *** POINT 3 *** Handle the received request data carefully and securely,
  69. // even though the data comes from the application granted access temporarily.
  70. // Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
  71. // Checking for other parameters are omitted here, due to sample.
  72. // Please refer to "3.2 Handle Input Data Carefully and Securely."
  73. // *** POINT 4 *** Information that is granted to disclose to the temporary access applications can be returned.
  74. // It depends on application whether the query result can be disclosed or not.
  75. switch (sUriMatcher.match(uri)) {
  76. case DOWNLOADS_CODE:
  77. case DOWNLOADS_ID_CODE:
  78. return sDownloadCursor;
  79. case ADDRESSES_CODE:
  80. case ADDRESSES_ID_CODE:
  81. return sAddressCursor;
  82. default:
  83. throw new IllegalArgumentException("Invalid URI:" + uri);
  84. }
  85. }
  86. @Override
  87. public Uri insert(Uri uri, ContentValues values) {
  88. // *** POINT 3 *** Handle the received request data carefully and securely,
  89. // even though the data comes from the application granted access temporarily.
  90. // Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
  91. // Checking for other parameters are omitted here, due to sample.
  92. // Please refer to "3.2 Handle Input Data Carefully and Securely."
  93. // *** POINT 4 *** Information that is granted to disclose to the temporary access applications c
  94. an be returned.
  95. // It depends on application whether the issued ID has sensitive meaning or not.
  96. switch (sUriMatcher.match(uri)) {
  97. case DOWNLOADS_CODE:
  98. return ContentUris.withAppendedId(Download.CONTENT_URI, 3);
  99. case ADDRESSES_CODE:
  100. return ContentUris.withAppendedId(Address.CONTENT_URI, 4);
  101. default:
  102. throw new IllegalArgumentException("Invalid URI:" + uri);
  103. }
  104. }
  105. @Override
  106. public int update(Uri uri, ContentValues values, String selection,
  107. String[] selectionArgs) {
  108. // *** POINT 3 *** Handle the received request data carefully and securely,
  109. // even though the data comes from the application granted access temporarily.
  110. // Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
  111. // Checking for other parameters are omitted here, due to sample.
  112. // Please refer to "3.2 Handle Input Data Carefully and Securely."
  113. // *** POINT 4 *** Information that is granted to disclose to the temporary access applications can be returned.
  114. // It depends on application whether the number of updated records has sensitive meaning or not.
  115. switch (sUriMatcher.match(uri)) {
  116. case DOWNLOADS_CODE:
  117. return 5; // Return number of updated records
  118. case DOWNLOADS_ID_CODE:
  119. return 1;
  120. case ADDRESSES_CODE:
  121. return 15;
  122. case ADDRESSES_ID_CODE:
  123. return 1;
  124. default:
  125. throw new IllegalArgumentException("Invalid URI:" + uri);
  126. }
  127. }
  128. @Override
  129. public int delete(Uri uri, String selection, String[] selectionArgs) {
  130. // *** POINT 3 *** Handle the received request data carefully and securely,
  131. // even though the data comes from the application granted access temporarily.
  132. // Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
  133. // Checking for other parameters are omitted here, due to sample.
  134. // Please refer to "3.2 Handle Input Data Carefully and Securely."
  135. // *** POINT 4 *** Information that is granted to disclose to the temporary access applications can be returned.
  136. // It depends on application whether the number of deleted records has sensitive meaning or not.
  137. switch (sUriMatcher.match(uri)) {
  138. case DOWNLOADS_CODE:
  139. return 10; // Return number of deleted records
  140. case DOWNLOADS_ID_CODE:
  141. return 1;
  142. case ADDRESSES_CODE:
  143. return 20;
  144. case ADDRESSES_ID_CODE:
  145. return 1;
  146. default:
  147. throw new IllegalArgumentException("Invalid URI:" + uri);
  148. }
  149. }
  150. }

TemporaryActiveGrantActivity.java

  1. package org.jssec.android.provider.temporaryprovider;
  2. import android.app.Activity;
  3. import android.content.ActivityNotFoundException;
  4. import android.content.Intent;
  5. import android.os.Bundle;
  6. import android.view.View;
  7. import android.widget.Toast;
  8. public class TemporaryActiveGrantActivity extends Activity {
  9. // User Activity Information
  10. private static final String TARGET_PACKAGE = "org.jssec.android.provider.temporaryuser";
  11. private static final String TARGET_ACTIVITY = "org.jssec.android.provider.temporaryuser.TemporaryUserActivity";
  12. @Override
  13. protected void onCreate(Bundle savedInstanceState) {
  14. super.onCreate(savedInstanceState);
  15. setContentView(R.layout.active_grant);
  16. }
  17. // In the case that Content Provider application grants access permission to other application actively.
  18. public void onSendClick(View view) {
  19. try {
  20. Intent intent = new Intent();
  21. // *** POINT 5 *** Specify URI for the intent to grant temporary access.
  22. intent.setData(TemporaryProvider.Address.CONTENT_URI);
  23. // *** POINT 6 *** Specify access rights for the intent to grant temporary access.
  24. intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
  25. // *** POINT 7 *** Send the explicit intent to an application to grant temporary access.
  26. intent.setClassName(TARGET_PACKAGE, TARGET_ACTIVITY);
  27. startActivity(intent);
  28. } catch (ActivityNotFoundException e) {
  29. Toast.makeText(this, "User Activity not found.", Toast.LENGTH_LONG).show();
  30. }
  31. }
  32. }

TemporaryPassiveGrantActivity.java

  1. package org.jssec.android.provider.temporaryprovider;
  2. import android.app.Activity;
  3. import android.content.Intent;
  4. import android.os.Bundle;
  5. import android.view.View;
  6. public class TemporaryPassiveGrantActivity extends Activity {
  7. @Override
  8. protected void onCreate(Bundle savedInstanceState) {
  9. super.onCreate(savedInstanceState);
  10. setContentView(R.layout.passive_grant);
  11. }
  12. // In the case that Content Provider application passively grants access permission
  13. // to the application that requested Content Provider access.
  14. public void onGrantClick(View view) {
  15. Intent intent = new Intent();
  16. // *** POINT 5 *** Specify URI for the intent to grant temporary access.
  17. intent.setData(TemporaryProvider.Address.CONTENT_URI);
  18. // *** POINT 6 *** Specify access rights for the intent to grant temporary access.
  19. intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
  20. // *** POINT 8 *** Return the intent to the application that requests temporary access.
  21. setResult(Activity.RESULT_OK, intent);
  22. finish();
  23. }
  24. public void onCloseClick(View view) {
  25. finish();
  26. }
  27. }

下面是临时内容供应器的示例。

要点(使用内容供应器):

  1. 不要发送敏感信息。

  2. 收到结果时,小心并安全地处理结果数据。

TemporaryUserActivity.java

  1. package org.jssec.android.provider.temporaryuser;
  2. import android.app.Activity;
  3. import android.content.ActivityNotFoundException;
  4. import android.content.Intent;
  5. import android.content.pm.ProviderInfo;
  6. import android.database.Cursor;
  7. import android.net.Uri;
  8. import android.os.Bundle;
  9. import android.view.View;
  10. import android.widget.TextView;
  11. public class TemporaryUserActivity extends Activity {
  12. // Information of the Content Provider's Activity to request temporary content provider access.
  13. private static final String TARGET_PACKAGE = "org.jssec.android.provider.temporaryprovider";
  14. private static final String TARGET_ACTIVITY = "org.jssec.android.provider.temporaryprovider.TemporaryPassiveGrantActivity";
  15. // Target Content Provider Information
  16. private static final String AUTHORITY = "org.jssec.android.provider.temporaryprovider";
  17. private interface Address {
  18. public static final String PATH = "addresses";
  19. public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH);
  20. }
  21. private static final int REQUEST_CODE = 1;
  22. public void onQueryClick(View view) {
  23. logLine("[Query]");
  24. Cursor cursor = null;
  25. try {
  26. if (!providerExists(Address.CONTENT_URI)) {
  27. logLine(" Content Provider doesn't exist.");
  28. return;
  29. }
  30. // *** POINT 9 *** Do not send sensitive information.
  31. // If no problem when the information is taken by malware, it can be included in the request.
  32. cursor = getContentResolver().query(Address.CONTENT_URI, null, null, null, null);
  33. // *** POINT 10 *** When receiving a result, handle the result data carefully and securely.
  34. // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
  35. if (cursor == null) {
  36. logLine(" null cursor");
  37. } else {
  38. boolean moved = cursor.moveToFirst();
  39. while (moved) {
  40. logLine(String.format(" %d, %s", cursor.getInt(0), cursor.getString(1)));
  41. moved = cursor.moveToNext();
  42. }
  43. }
  44. } catch (SecurityException ex) {
  45. logLine(" Exception:" + ex.getMessage());
  46. }
  47. finally {
  48. if (cursor != null) cursor.close();
  49. }
  50. }
  51. // In the case that this application requests temporary access to the Content Provider
  52. // and the Content Provider passively grants temporary access permission to this application.
  53. public void onGrantRequestClick(View view) {
  54. Intent intent = new Intent();
  55. intent.setClassName(TARGET_PACKAGE, TARGET_ACTIVITY);
  56. try {
  57. startActivityForResult(intent, REQUEST_CODE);
  58. } catch (ActivityNotFoundException e) {
  59. logLine("Content Provider's Activity not found.");
  60. }
  61. }
  62. private boolean providerExists(Uri uri) {
  63. ProviderInfo pi = getPackageManager().resolveContentProvider(uri.getAuthority(), 0);
  64. return (pi != null);
  65. }
  66. private TextView mLogView;
  67. // In the case that the Content Provider application grants temporary access
  68. // to this application actively.
  69. @Override
  70. public void onCreate(Bundle savedInstanceState) {
  71. super.onCreate(savedInstanceState);
  72. setContentView(R.layout.main);
  73. mLogView = (TextView)findViewById(R.id.logview);
  74. }
  75. private void logLine(String line) {
  76. mLogView.append(line);
  77. mLogView.append("¥n");
  78. }
  79. }