5.2.1 示例代码

5.2.1.1 如何使用 Android OS 的系统权限

Android 操作系统有一个称为“权限”的安全机制,可以保护其用户的资产(如联系人和 GPS 功能)免受恶意软件的侵害。 当应用请求访问受 Android OS 保护的信息或功能时,应用需要显式声明权限才能访问它们。 安装应用,它申请需要用户同意的权限时,会出现以下确认界面 [23]。

[23] 在 Android 6.0(API Level 23)及更高版本中,安装应用时不会发生用户的权限授予或拒绝,而是在应用请求权限时在运行时发生。 更多详细信息,请参见“5.2.1.4 在 Android 6.0 及更高版本中使用危险权限的方法”和“5.2.3.6 Android 6.0 和更高版本中的权限模型规范的修改”部分。

5.2.1.md - 图1

从该确认界面中,用户能够知道,应用试图访问哪些类型的特征或信息。 如果应用试图访问明显不需要的功能或信息,那么该应用很可能是恶意软件。 因此,为了使你的应用不被怀疑是恶意软件,因此需要尽量减少使用权限声明。

要点:

  1. 使用uses-permission声明应用中使用的权限。
  2. 不要用uses-permission声明任何不必要的权限。

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.permission.usespermission" >
  4. <!-- *** POINT 1 *** Declare a permission used in an application with uses-permission -->
  5. <!-- Permission to access Internet -->
  6. <uses-permission android:name="android.permission.INTERNET"/>
  7. <!-- *** POINT 2 *** Do not declare any unnecessary permissions with uses-permission -->
  8. <!-- If declaring to use Permission that is unnecessary for application behaviors, it gives users a
  9. sense of distrust. -->
  10. <application
  11. android:allowBackup="false"
  12. android:icon="@drawable/ic_launcher"
  13. android:label="@string/app_name" >
  14. <activity
  15. android:name=".MainActivity"
  16. android:label="@string/app_name"
  17. android:exported="true" >
  18. <intent-filter>
  19. <action android:name="android.intent.action.MAIN" />
  20. <category android:name="android.intent.category.LAUNCHER" />
  21. </intent-filter>
  22. </activity>
  23. </application>
  24. </manifest>

5.2.1.2 如何使用内部定义的权限在内部应用之间通信

除了由 Android OS 定义的系统权限之外,应用还可以定义自己的权限。 如果使用内部定义的权限(内部定义的签名权限更准确),则可以构建只允许内部应用之间进行通信的机制。 通过提供基于多个内部应用之间的,应用间通信的复合功能,应用变得更具吸引力,你的企业可以通过将其作为系列销售获得更多利润。 这是使用内部定义的签名权限的情况。

示例应用“内部定义的签名权限(UserApp)”使用Context.startActivity()方法启动示例应用“内部定义的签名权限(ProtectedApp)”。 两个应用都需要使用相同的开发人员密钥进行签名。如果用于签名的密钥不同,则UserApp不会向ProtectedApp发送意图,并且ProtectedApp不处理从UserApp收到的意图。 此外,它还可以防止恶意软件使用安装顺序相关的事项,绕过你自己的签名权限,如高级话题部分中所述。

5.2.1.md - 图2

要点:提供组件的应用

  1. 使用protectionLevel="signature"定义权限。

  2. 对于组件,使用其权限属性强制规定权限。

  3. 如果组件是活动,则必须没有定义意图过滤器。

  4. 在运行时,验证签名权限是否由程序代码本身定义。

  5. 导出 APK 时,请使用与使用该组件的应用相同的开发人员密钥对 APK 进行签名。

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.permission.protectedapp" >
  4. <!-- *** POINT 1 *** Define a permission with protectionLevel="signature" -->
  5. <permission
  6. android:name="org.jssec.android.permission.protectedapp.MY_PERMISSION"
  7. android:protectionLevel="signature" />
  8. <application
  9. android:allowBackup="false"
  10. android:icon="@drawable/ic_launcher"
  11. android:label="@string/app_name" >
  12. <!-- *** POINT 2 *** For a component, enforce the permission with its permission attribute -->
  13. <activity
  14. android:name=".ProtectedActivity"
  15. android:exported="true"
  16. android:label="@string/app_name"
  17. android:permission="org.jssec.android.permission.protectedapp.MY_PERMISSION" >
  18. <!-- *** POINT 3 *** If the component is an activity, you must define no intent-filter -->
  19. </activity>
  20. </application>
  21. </manifest>

ProtectedActivity.java

  1. package org.jssec.android.permission.protectedapp;
  2. import org.jssec.android.shared.SigPerm;
  3. import org.jssec.android.shared.Utils;
  4. import android.app.Activity;
  5. import android.content.Context;
  6. import android.os.Bundle;
  7. import android.widget.TextView;
  8. public class ProtectedActivity extends Activity {
  9. // In-house Signature Permission
  10. private static final String MY_PERMISSION = "org.jssec.android.permission.protectedapp.MY_PERMISSION";
  11. // Hash value of in-house certificate
  12. private static String sMyCertHash = null;
  13. private static String myCertHash(Context context) {
  14. if (sMyCertHash == null) {
  15. if (Utils.isDebuggable(context)) {
  16. // Certificate hash value of "androiddebugkey" of debug.keystore
  17. sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255";
  18. } else {
  19. // Certificate hash value of "my company key" of keystore
  20. sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA";
  21. }
  22. }
  23. return sMyCertHash;
  24. }
  25. private TextView mMessageView;
  26. @Override
  27. public void onCreate(Bundle savedInstanceState) {
  28. super.onCreate(savedInstanceState);
  29. setContentView(R.layout.main);
  30. mMessageView = (TextView) findViewById(R.id.messageView);
  31. // *** POINT 4 *** At run time, verify if the signature permission is defined by itself on the program code
  32. if (!SigPerm.test(this, MY_PERMISSION, myCertHash(this))) {
  33. mMessageView.setText("In-house defined signature permission is not defined by in-house application.");
  34. return;
  35. }
  36. // *** POINT 4 *** Continue processing only when the certificate matches
  37. mMessageView.setText("In-house-defined signature permission is defined by in-house application,
  38. was confirmed.");
  39. }
  40. }

SigPerm.java

  1. package org.jssec.android.shared;
  2. import android.content.Context;
  3. import android.content.pm.PackageManager;
  4. import android.content.pm.PackageManager.NameNotFoundException;
  5. import android.content.pm.PermissionInfo;
  6. public class SigPerm {
  7. public static boolean test(Context ctx, String sigPermName, String correctHash) {
  8. if (correctHash == null) return false;
  9. correctHash = correctHash.replaceAll(" ", "");
  10. return correctHash.equals(hash(ctx, sigPermName));
  11. }
  12. public static String hash(Context ctx, String sigPermName) {
  13. if (sigPermName == null) return null;
  14. try {
  15. // Get the package name of the application which declares a permission named sigPermName.
  16. PackageManager pm = ctx.getPackageManager();
  17. PermissionInfo pi;
  18. pi = pm.getPermissionInfo(sigPermName, PackageManager.GET_META_DATA);
  19. String pkgname = pi.packageName;
  20. // Fail if the permission named sigPermName is not a Signature Permission
  21. if (pi.protectionLevel != PermissionInfo.PROTECTION_SIGNATURE) return null;
  22. // Return the certificate hash value of the application which declares a permission named sigPermName.
  23. return PkgCert.hash(ctx, pkgname);
  24. } catch (NameNotFoundException e) {
  25. return null;
  26. }
  27. }
  28. }

PkgCert.java

  1. package org.jssec.android.shared;
  2. import java.security.MessageDigest;
  3. import java.security.NoSuchAlgorithmException;
  4. import android.content.Context;
  5. import android.content.pm.PackageInfo;
  6. import android.content.pm.PackageManager;
  7. import android.content.pm.PackageManager.NameNotFoundException;
  8. import android.content.pm.Signature;
  9. public class PkgCert {
  10. public static boolean test(Context ctx, String pkgname, String correctHash) {
  11. if (correctHash == null) return false;
  12. correctHash = correctHash.replaceAll(" ", "");
  13. return correctHash.equals(hash(ctx, pkgname));
  14. }
  15. public static String hash(Context ctx, String pkgname) {
  16. if (pkgname == null) return null;
  17. try {
  18. PackageManager pm = ctx.getPackageManager();
  19. PackageInfo pkginfo = pm.getPackageInfo(pkgname, PackageManager.GET_SIGNATURES);
  20. if (pkginfo.signatures.length != 1) return null; // Will not handle multiple signatures.
  21. Signature sig = pkginfo.signatures[0];
  22. byte[] cert = sig.toByteArray();
  23. byte[] sha256 = computeSha256(cert);
  24. return byte2hex(sha256);
  25. } catch (NameNotFoundException e) {
  26. return null;
  27. }
  28. }
  29. private static byte[] computeSha256(byte[] data) {
  30. try {
  31. return MessageDigest.getInstance("SHA-256").digest(data);
  32. } catch (NoSuchAlgorithmException e) {
  33. return null;
  34. }
  35. }
  36. private static String byte2hex(byte[] data) {
  37. if (data == null) return null;
  38. final StringBuilder hexadecimal = new StringBuilder();
  39. for (final byte b : data) {
  40. hexadecimal.append(String.format("%02X", b));
  41. }
  42. return hexadecimal.toString();
  43. }
  44. }

要点 5:导出 APK 时,请使用与使用该组件的应用相同的开发人员密钥对 APK 进行签名。

5.2.1.md - 图3

要点:使用组件的应用

  1. 禁止定义应用使用的相同签名权限。

  2. 使用权限标签声明内部权限。

  3. 验证内部签名权限,是否由提供组件的应用定义。

  4. 验证目标应用是否是内部应用。

  5. 当目标组件是一个活动时,使用显式意图。

  6. 导出 APK 时,请使用与使用该组件的应用相同的开发人员密钥对 APK 进行签名。

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.permission.userapp" >
  4. <!-- *** POINT 6 *** The same signature permission that the application uses must not be defined -->
  5. <!-- *** POINT 7 *** Declare the in-house permission with uses-permission tag -->
  6. <uses-permission
  7. android:name="org.jssec.android.permission.protectedapp.MY_PERMISSION" />
  8. <application
  9. android:allowBackup="false"
  10. android:icon="@drawable/ic_launcher"
  11. android:label="@string/app_name" >
  12. <activity
  13. android:name=".UserActivity"
  14. android:label="@string/app_name"
  15. android:exported="true" >
  16. <intent-filter>
  17. <action android:name="android.intent.action.MAIN" />
  18. <category android:name="android.intent.category.LAUNCHER" />
  19. </intent-filter>
  20. </activity>
  21. </application>
  22. </manifest>

UserActivity.java

  1. package org.jssec.android.permission.userapp;
  2. import org.jssec.android.shared.PkgCert;
  3. import org.jssec.android.shared.SigPerm;
  4. import org.jssec.android.shared.Utils;
  5. import android.app.Activity;
  6. import android.content.Context;
  7. import android.content.Intent;
  8. import android.os.Bundle;
  9. import android.view.View;
  10. import android.widget.Toast;
  11. public class UserActivity extends Activity {
  12. // Requested (Destination) application's Activity information
  13. private static final String TARGET_PACKAGE = "org.jssec.android.permission.protectedapp";
  14. private static final String TARGET_ACTIVITY = "org.jssec.android.permission.protectedapp.ProtectedActivity";
  15. // In-house Signature Permission
  16. private static final String MY_PERMISSION = "org.jssec.android.permission.protectedapp.MY_PERMISSION";
  17. // Hash value of in-house certificate
  18. private static String sMyCertHash = null;
  19. private static String myCertHash(Context context) {
  20. if (sMyCertHash == null) {
  21. if (Utils.isDebuggable(context)) {
  22. // Certificate hash value of "androiddebugkey" of debug.keystore.
  23. sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255";
  24. } else {
  25. // Certificate hash value of "my company key" of keystore.
  26. sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA";
  27. }
  28. }
  29. return sMyCertHash;
  30. }
  31. @Override
  32. public void onCreate(Bundle savedInstanceState) {
  33. super.onCreate(savedInstanceState);
  34. setContentView(R.layout.main);
  35. }
  36. public void onSendButtonClicked(View view) {
  37. // *** POINT 8 *** Verify if the in-house signature permission is defined by the application that provides the component on the program code.
  38. if (!SigPerm.test(this, MY_PERMISSION, myCertHash(this))) {
  39. Toast.makeText(this, "In-house-defined signature permission is not defined by In house application.", Toast.LENGTH_LONG).show();
  40. return;
  41. }
  42. // *** POINT 9 *** Verify if the destination application is an in-house application.
  43. if (!PkgCert.test(this, TARGET_PACKAGE, myCertHash(this))) {
  44. Toast.makeText(this, "Requested (Destination) application is not in-house application.", Toast.LENGTH_LONG).show();
  45. return;
  46. }
  47. // *** POINT 10 *** Use an explicit intent when the destination component is an activity.
  48. try {
  49. Intent intent = new Intent();
  50. intent.setClassName(TARGET_PACKAGE, TARGET_ACTIVITY);
  51. startActivity(intent);
  52. } catch(Exception e) {
  53. Toast.makeText(this,
  54. String.format("Exception occurs:%s", e.getMessage()), Toast.LENGTH_LONG).show();
  55. }
  56. }
  57. }

PkgCertWhitelists.java

  1. package org.jssec.android.shared;
  2. import java.util.HashMap;
  3. import java.util.Map;
  4. import android.content.Context;
  5. public class PkgCertWhitelists {
  6. private Map<String, String> mWhitelists = new HashMap<String, String>();
  7. public boolean add(String pkgname, String sha256) {
  8. if (pkgname == null) return false;
  9. if (sha256 == null) return false;
  10. sha256 = sha256.replaceAll(" ", "");
  11. if (sha256.length() != 64) return false; // SHA-256 -> 32 bytes -> 64 chars
  12. sha256 = sha256.toUpperCase();
  13. if (sha256.replaceAll("[0-9A-F]+", "").length() != 0) return false; // found non hex char
  14. mWhitelists.put(pkgname, sha256);
  15. return true;
  16. }
  17. public boolean test(Context ctx, String pkgname) {
  18. // Get the correct hash value which corresponds to pkgname.
  19. String correctHash = mWhitelists.get(pkgname);
  20. // Compare the actual hash value of pkgname with the correct hash value.
  21. return PkgCert.test(ctx, pkgname, correctHash);
  22. }
  23. }

PkgCert.java

  1. package org.jssec.android.shared;
  2. import java.security.MessageDigest;
  3. import java.security.NoSuchAlgorithmException;
  4. import android.content.Context;
  5. import android.content.pm.PackageInfo;
  6. import android.content.pm.PackageManager;
  7. import android.content.pm.PackageManager.NameNotFoundException;
  8. import android.content.pm.Signature;
  9. public class PkgCert {
  10. public static boolean test(Context ctx, String pkgname, String correctHash) {
  11. if (correctHash == null) return false;
  12. correctHash = correctHash.replaceAll(" ", "");
  13. return correctHash.equals(hash(ctx, pkgname));
  14. }
  15. public static String hash(Context ctx, String pkgname) {
  16. if (pkgname == null) return null;
  17. try {
  18. PackageManager pm = ctx.getPackageManager();
  19. PackageInfo pkginfo = pm.getPackageInfo(pkgname, PackageManager.GET_SIGNATURES);
  20. if (pkginfo.signatures.length != 1) return null; // Will not handle multiple signatures.
  21. Signature sig = pkginfo.signatures[0];
  22. byte[] cert = sig.toByteArray();
  23. byte[] sha256 = computeSha256(cert);
  24. return byte2hex(sha256);
  25. } catch (NameNotFoundException e) {
  26. return null;
  27. }
  28. }
  29. private static byte[] computeSha256(byte[] data) {
  30. try {
  31. return MessageDigest.getInstance("SHA-256").digest(data);
  32. } catch (NoSuchAlgorithmException e) {
  33. return null;
  34. }
  35. }
  36. private static String byte2hex(byte[] data) {
  37. if (data == null) return null;
  38. final StringBuilder hexadecimal = new StringBuilder();
  39. for (final byte b : data) {
  40. hexadecimal.append(String.format("%02X", b));
  41. }
  42. return hexadecimal.toString();
  43. }
  44. }

要点 11:导出 APK 时,请使用与使用该组件的应用相同的开发人员密钥对 APK 进行签名。

5.2.1.md - 图4

5.2.1.3 如何验证应用证书的散列值

我们将说明,如何验证应用证书的散列值,他们在本指南中不同位置出现。 严格来说,散列值意味着“用于签署 APK 的开发人员密钥的公钥证书的 SHA256 散列值”。

如何使用 Keytool 进行验证

使用与 JDK 捆绑在一起的名为 keytool 的程序,你可以获取开发人员密钥的公钥证书的散列值(也称为证书指纹)。 由于散列算法的不同,存在各种散列方法,例如 MD5,SHA1 和 SHA256。 但是,考虑到加密字节长度的安全强度,本指南推荐使用 SHA256。 不幸的是, Android SDK 中使用的, JDK6 绑定的 keytool 不支持 SHA256 来计算哈希值。 因此,有必要使用 JDK7 绑定的 keytool。

通过 keytool 输出 Android 调试证书内容的示例

  1. > keytool -list -v -keystore < keystore file > -storepass < password >
  2. Type of keystore: JKS
  3. Keystore provider: SUN
  4. One entry is included in a keystore
  5. Other name: androiddebugkey
  6. Date of creation: 2012/01/11
  7. Entry type: PrivateKeyEntry
  8. Length of certificate chain: 1
  9. Certificate[1]:
  10. Owner: CN=Android Debug, O=Android, C=US
  11. Issuer: CN=Android Debug, O=Android, C=US
  12. Serial number: 4f0cef98
  13. Start date of validity period: Wed Jan 11 11:10:32 JST 2012 End date: Fri Jan 03 11:10:32 JST 2042
  14. Certificate fingerprint:
  15. MD5: 9E:89:53:18:06:B2:E3:AC:B4:24:CD:6A:56:BF:1E:A1
  16. SHA1: A8:1E:5D:E5:68:24:FD:F6:F1:ED:2F:C3:6E:0F:09:A3:07:F8:5C:0C
  17. SHA256: FB:75:E9:B9:2E:9E:6B:4D:AB:3F:94:B2:EC:A1:F0:33:09:74:D8:7A:CF:42:58:22:A2:56:85:1B:0F
  18. :85:C6:35
  19. Signatrue algorithm name: SHA1withRSA
  20. Version: 3
  21. *******************************************
  22. *******************************************

如何使用 JSSEC 证书散列值检查器进行验证

在不安装JDK7的情况下,你可以使用 JSSEC 证书散列值检查器,轻松验证证书散列值。

5.2.1.md - 图5

这是一个 Android 应用,显示安装在设备中的,应用的证书哈希值列表。 在上图中,sha-256右侧显示的 64 个字符的十六进制字符串是证书哈希值。 本指南附带的示例代码文件夹JSSEC CertHash Checker是一组源代码。 如果你愿意,你可以编译代码并使用它。

5.2.1.4 Android 6.0 及更高版本中使用危险权限的方法

Android 6.0(API Level 23)结合了修改后的规范,与应用实现相关 - 特别是应用被授予权限的时间。

在 Android 5.1(API 级别 22)和更早版本的权限模型下(请参阅“5.2.3.6 Android 6.0 和更高版本中的权限模型规范修改”一节),安装时授予应用申请的所有权限 。 但是,在 Android 6.0 及更高版本中,应用开发人员必须以这样的方式实现应用,即对于危险权限,应用在适当的时候请求权限。 当应用请求权限时,Android OS 会向用户显示如下所示的确认窗口,请求用户决定,是否授予相关权限。 如果用户允许使用权限,则应用可以执行任何需要该权限的操作。

5.2.1.md - 图6

该规范还修改了权限授予的单位。 以前,所有权限都是同时授予的;在 Android 6.0(API Level 23)及更高版本中,权限是单独授予的(按权限组)。 结合这种修改,用户现在可以看到每个权限的单独确认窗口,允许用户在授予权限或拒绝权限时,作出更灵活的决定。 应用开发人员必须重新审视其应用的规格和设计,并充分考虑到权限被拒绝的可能性。

Android 6.0 及更高版本中的权限模型的详细信息,请参见“5.2.3.6 Android 6.0 和更高版本中的权限模型规范修改”部分。

要点:

  1. 应用声明他们将使用的权限

  2. 不要声明不必要的权限

  3. 检查是否应用被授予了权限

  4. 请求权限(打开一个对话框来向用户请求权限)

  5. 对拒绝使用权限的情况实现适当的行为

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.permission.permissionrequestingpermissionatruntime" >
  4. <!-- *** POINT 1 *** Apps declare the Permissions they will use -->
  5. <!-- Permission to read information on contacts (Protection Level: dangerous) -->
  6. <uses-permission android:name="android.permission.READ_CONTACTS" />
  7. <!-- *** POINT 2 *** Do not declare the use of unnecessary Permissions -->
  8. <application
  9. android:allowBackup="true"
  10. android:icon="@mipmap/ic_launcher"
  11. android:label="@string/app_name"
  12. android:supportsRtl="true"
  13. android:theme="@style/AppTheme" >
  14. <activity
  15. android:name=".MainActivity"
  16. android:exported="true">
  17. <intent-filter>
  18. <action android:name="android.intent.action.MAIN" />
  19. <category android:name="android.intent.category.LAUNCHER" />
  20. </intent-filter>
  21. </activity>
  22. <activity
  23. android:name=".ContactListActivity"
  24. android:exported="false">
  25. </activity>
  26. </application>
  27. </manifest>

MainActivity.java

  1. package org.jssec.android.permission.permissionrequestingpermissionatruntime;
  2. import android.Manifest;
  3. import android.content.Intent;
  4. import android.content.pm.PackageManager;
  5. import android.os.Bundle;
  6. import android.support.v4.app.ActivityCompat;
  7. import android.support.v4.content.ContextCompat;
  8. import android.support.v7.app.AppCompatActivity;
  9. import android.view.View;
  10. import android.widget.Button;
  11. import android.widget.Toast;
  12. public class MainActivity extends AppCompatActivity implements View.OnClickListener {
  13. private static final int REQUEST_CODE_READ_CONTACTS = 0;
  14. @Override
  15. protected void onCreate(Bundle savedInstanceState) {
  16. super.onCreate(savedInstanceState);
  17. setContentView(R.layout.activity_main);
  18. Button button = (Button)findViewById(R.id.button);
  19. button.setOnClickListener(this);
  20. }
  21. @Override
  22. public void onClick(View v) {
  23. readContacts();
  24. }
  25. private void readContacts() {
  26. // *** POINT 3 *** Check whether or not Permissions have been granted to the app
  27. if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
  28. // Permission was not granted
  29. // *** POINT 4 *** Request Permissions (open a dialog to request permission from users)
  30. ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_CODE_READ_CONTACTS);
  31. } else {
  32. // Permission was previously granted
  33. showContactList();
  34. }
  35. }
  36. // A callback method that receives the result of the user's selection
  37. @Override
  38. public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
  39. switch (requestCode) {
  40. case REQUEST_CODE_READ_CONTACTS:
  41. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
  42. // Permissions were granted; we may execute operations that use contact information
  43. showContactList();
  44. } else {
  45. // Because the Permission was denied, we may not execute operations that use contact information
  46. // *** POINT 5 *** Implement appropriate behavior for cases in which the use of a Permission is refused
  47. Toast.makeText(this, String.format("Use of contact is not allowed."), Toast.LENGTH_LONG).show();
  48. }
  49. return;
  50. }
  51. }
  52. // Show contact list
  53. private void showContactList() {
  54. // Launch ContactListActivity
  55. Intent intent = new Intent();
  56. intent.setClass(getApplicationContext(), ContactListActivity.class);
  57. startActivity(intent);
  58. }
  59. }