From 2ad812d4aa82cb18e6cd650497acaa7a3f394df9 Mon Sep 17 00:00:00 2001 From: Nick Kralevich Date: Tue, 26 Mar 2013 16:39:21 -0700 Subject: [PATCH] Initial checkin: optional permissions sample code This is an initial checkin of a small demonstration program which demonstrates how optional permissions can be used. This app can do three things, which require permissions: * Vibrate the device (android.permission.VIBRATE) * Access the internet (android.permission.INTERNET) * Get the phone number (android.permission.READ_PHONE_STATE) At install time, this app has zero permissions. However, the app can request that the user grant permissions, based on pressing the "Prompt" button. The user can then choose to allow or deny the permissions. Known bugs and limitations: * Once a permission is granted, it currently cannot be revoked by the application nor the user. * Due to the way it's implemented, the INTERNET permission requires a restart of the application. That isn't in the sample code. * Getting the device phone number doesn't make sense for a tablet. This code was mostly written by klyubin@google.com with heavy modification by myself. Change-Id: I620db52c4a1f10ac7aa604ba34f77f7ec03af023 --- samples/OptionalPermissions/Android.mk | 13 + .../OptionalPermissions/AndroidManifest.xml | 38 ++ .../res/values/strings.xml | 68 ++++ .../res/xml/preference_headers.xml | 19 + .../res/xml/preferences.xml | 89 +++++ .../android/permdemo/MainActivity.java | 53 +++ .../android/permdemo/PrefsActivity.java | 37 ++ .../android/permdemo/PrefsFragment.java | 331 ++++++++++++++++++ 8 files changed, 648 insertions(+) create mode 100644 samples/OptionalPermissions/Android.mk create mode 100644 samples/OptionalPermissions/AndroidManifest.xml create mode 100644 samples/OptionalPermissions/res/values/strings.xml create mode 100644 samples/OptionalPermissions/res/xml/preference_headers.xml create mode 100644 samples/OptionalPermissions/res/xml/preferences.xml create mode 100644 samples/OptionalPermissions/src/com/example/android/permdemo/MainActivity.java create mode 100644 samples/OptionalPermissions/src/com/example/android/permdemo/PrefsActivity.java create mode 100644 samples/OptionalPermissions/src/com/example/android/permdemo/PrefsFragment.java diff --git a/samples/OptionalPermissions/Android.mk b/samples/OptionalPermissions/Android.mk new file mode 100644 index 000000000..758a7ea41 --- /dev/null +++ b/samples/OptionalPermissions/Android.mk @@ -0,0 +1,13 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := samples tests + +# Only compile source java files in this apk. +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_PACKAGE_NAME := OptionalPermissions + +LOCAL_SDK_VERSION := current + +include $(BUILD_PACKAGE) diff --git a/samples/OptionalPermissions/AndroidManifest.xml b/samples/OptionalPermissions/AndroidManifest.xml new file mode 100644 index 000000000..668de5b36 --- /dev/null +++ b/samples/OptionalPermissions/AndroidManifest.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/OptionalPermissions/res/values/strings.xml b/samples/OptionalPermissions/res/values/strings.xml new file mode 100644 index 000000000..66889475a --- /dev/null +++ b/samples/OptionalPermissions/res/values/strings.xml @@ -0,0 +1,68 @@ + + + + + + + Permissions Demo + + Vibrate + Internet + Phone state & identity + All + + + Vibrate + Vibrate Android + Prompt + Prompt for the VIBRATE permission + Status + + Get time from google.com + Get current time from https://www.google.com + Prompt + Prompt for the INTERNET permission + Status + + Show phone number + Read this Android\'s phone number + Prompt + Prompt for the READ_PHONE_STATE permission + Status + + Prompt + Prompt for all of the above permissions + + Granted + Denied + Revoked + + + Vibrate feature not available on this Android + + + + Telephony feature not available on this Android + + + Exception caught + + Getting current time from Google… + + GRANTED + NOT GRANTED + + diff --git a/samples/OptionalPermissions/res/xml/preference_headers.xml b/samples/OptionalPermissions/res/xml/preference_headers.xml new file mode 100644 index 000000000..4f9a64d71 --- /dev/null +++ b/samples/OptionalPermissions/res/xml/preference_headers.xml @@ -0,0 +1,19 @@ + + + +
+ diff --git a/samples/OptionalPermissions/res/xml/preferences.xml b/samples/OptionalPermissions/res/xml/preferences.xml new file mode 100644 index 000000000..7fe6dd334 --- /dev/null +++ b/samples/OptionalPermissions/res/xml/preferences.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/OptionalPermissions/src/com/example/android/permdemo/MainActivity.java b/samples/OptionalPermissions/src/com/example/android/permdemo/MainActivity.java new file mode 100644 index 000000000..c68689ccc --- /dev/null +++ b/samples/OptionalPermissions/src/com/example/android/permdemo/MainActivity.java @@ -0,0 +1,53 @@ +/* +** Copyright 2013, 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.example.android.permdemo; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.preference.PreferenceActivity; + +public class MainActivity extends Activity { + private static final int REQUEST_CODE_PREFS = 1; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + startActivityForResult( + new Intent(this, PrefsActivity.class) + .putExtra(PreferenceActivity.EXTRA_NO_HEADERS, true) + .putExtra( + PreferenceActivity.EXTRA_SHOW_FRAGMENT, + PrefsFragment.class.getName()), + REQUEST_CODE_PREFS); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case REQUEST_CODE_PREFS: + // The PrefsActivity we launched has finished. + finish(); + break; + default: + super.onActivityResult(requestCode, resultCode, data); + break; + } + } + +} diff --git a/samples/OptionalPermissions/src/com/example/android/permdemo/PrefsActivity.java b/samples/OptionalPermissions/src/com/example/android/permdemo/PrefsActivity.java new file mode 100644 index 000000000..eff4d28ae --- /dev/null +++ b/samples/OptionalPermissions/src/com/example/android/permdemo/PrefsActivity.java @@ -0,0 +1,37 @@ +/* +** Copyright 2013, 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.example.android.permdemo; + +import android.os.Bundle; +import android.preference.PreferenceActivity; + +import java.util.List; + +public class PrefsActivity extends PreferenceActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + /** + * Populate the activity with the top-level headers. + */ + @Override + public void onBuildHeaders(List
target) { + loadHeadersFromResource(R.xml.preference_headers, target); + } +} diff --git a/samples/OptionalPermissions/src/com/example/android/permdemo/PrefsFragment.java b/samples/OptionalPermissions/src/com/example/android/permdemo/PrefsFragment.java new file mode 100644 index 000000000..d37f6fae1 --- /dev/null +++ b/samples/OptionalPermissions/src/com/example/android/permdemo/PrefsFragment.java @@ -0,0 +1,331 @@ +/* +** Copyright 2013, 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.example.android.permdemo; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.AsyncTask; +import android.os.Binder; +import android.os.Bundle; +import android.os.Vibrator; +import android.preference.Preference; +import android.preference.PreferenceFragment; +import android.telephony.TelephonyManager; +import android.text.format.DateFormat; +import android.widget.Toast; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Date; + +public class PrefsFragment extends PreferenceFragment { + + private static final int REQUEST_CODE_PROMPT_PERMISSIONS = 1; + + private static final String[] ALL_PERMISSIONS = { + android.Manifest.permission.VIBRATE, + android.Manifest.permission.INTERNET, + android.Manifest.permission.READ_PHONE_STATE, + }; + + private Preference mVibrateStatusPreference; + private Preference mInternetStatusPreference; + private Preference mPhoneStateStatusPreference; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + addPreferencesFromResource(R.xml.preferences); + + findPreference("vibrate_do").setOnPreferenceClickListener( + new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + onVibrateActionClicked(); + return false; + } + }); + findPreference("vibrate_prompt").setOnPreferenceClickListener( + new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + onPromptPermissionsClicked(android.Manifest.permission.VIBRATE); + return false; + } + }); + + findPreference("internet_do").setOnPreferenceClickListener( + new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + onInternetActionClicked(); + return false; + } + }); + findPreference("internet_prompt").setOnPreferenceClickListener( + new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + onPromptPermissionsClicked(android.Manifest.permission.INTERNET); + return false; + } + }); + + findPreference("phone_state_do").setOnPreferenceClickListener( + new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + onReadPhoneStateActionClicked(); + return false; + } + }); + findPreference("phone_state_prompt").setOnPreferenceClickListener( + new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + onPromptPermissionsClicked(android.Manifest.permission.READ_PHONE_STATE); + return false; + } + }); + + findPreference("all_prompt").setOnPreferenceClickListener( + new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + onPromptPermissionsClicked(ALL_PERMISSIONS); + return false; + } + }); + + mVibrateStatusPreference = findPreference("vibrate_status"); + mInternetStatusPreference = findPreference("internet_status"); + mPhoneStateStatusPreference = findPreference("phone_state_status"); + refreshPermissionsStatus(); + } + + private void onVibrateActionClicked() { + Vibrator vibrator = (Vibrator) getActivity().getSystemService(Context.VIBRATOR_SERVICE); + if (vibrator == null) { + Toast.makeText( + getActivity(), + R.string.vibrate_feature_not_available, + Toast.LENGTH_LONG).show(); + return; + } + try { + vibrator.vibrate(300); + } catch (Exception e) { + displayException(e); + } + } + + private void onInternetActionClicked() { + new AsyncTask() { + private ProgressDialog mProgressDialog; + + @Override + protected void onPreExecute() { + mProgressDialog = ProgressDialog.show( + getActivity(), + null, + getString(R.string.get_time_progress_dialog_message), + true, // indeterminate progress + true, // cancelable + new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + // Cancel this AsyncTask + cancel(true); + } + }); + super.onPreExecute(); + } + + @Override + protected Object doInBackground(Void... params) { + HttpURLConnection connection = null; + Date date; + try { + connection = + (HttpURLConnection) new URL("https://www.google.com").openConnection(); + connection.setDefaultUseCaches(false); + connection.setInstanceFollowRedirects(false); + connection.setRequestMethod("HEAD"); + + // Force the request to fail if there's no Internet connectivity + connection.getResponseCode(); + + long timeMillis = connection.getDate(); + if (timeMillis == 0) { + throw new IOException("No time returned by the server"); + } + date = new Date(timeMillis); + } catch (Throwable t) { + t.printStackTrace(); + return t; + } finally { + if (connection != null) { + connection.disconnect(); + } + } + + return date; + } + + @Override + protected void onPostExecute(Object result) { + if (getActivity().isFinishing()) { + // The Activity is already finishing/finished -- no need to display the date + return; + } + + dismissProgressDialog(); + + if (result instanceof Throwable) { + displayException((Throwable) result); + return; + } + + Date date = (Date) result; + String formattedDate = DateFormat.getDateFormat(getActivity()).format(date) + + " " + DateFormat.getTimeFormat(getActivity()).format(date); + new AlertDialog.Builder(getActivity()) + .setMessage(formattedDate) + .setPositiveButton(android.R.string.ok, null) + .show(); + } + + @Override + protected void onCancelled() { + dismissProgressDialog(); + super.onCancelled(); + } + + private void dismissProgressDialog() { + if (mProgressDialog != null) { + mProgressDialog.dismiss(); + mProgressDialog = null; + } + } + + }.execute((Void[]) null); + } + + private void onReadPhoneStateActionClicked() { + TelephonyManager telephonyManager = + (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE); + if (telephonyManager == null) { + Toast.makeText( + getActivity(), + R.string.telephony_feature_not_available, + Toast.LENGTH_LONG).show(); + return; + } + + String phoneNumber; + try { + phoneNumber = telephonyManager.getLine1Number(); + } catch (Exception e) { + displayException(e); + return; + } + + new AlertDialog.Builder(getActivity()) + .setMessage("Phone number: " + phoneNumber) + .setPositiveButton(android.R.string.ok, null) + .show(); + } + + private void onPromptPermissionsClicked(String... permissions) { + Intent i = getActivity().getApplication() + .getPackageManager().buildPermissionRequestIntent(permissions); + startActivityForResult(i, REQUEST_CODE_PROMPT_PERMISSIONS); + } + + private void onGrantPermissionsResult(boolean granted) { + if (granted) { + displayPermissionsGrantApprovedToast(); + } else { + displayPermissionsGrantDeniedToast(); + } + refreshPermissionsStatus(); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case REQUEST_CODE_PROMPT_PERMISSIONS: + onGrantPermissionsResult(resultCode == Activity.RESULT_OK); + break; + default: + super.onActivityResult(requestCode, resultCode, data); + break; + } + } + + private void displayException(Throwable exception) { + new AlertDialog.Builder(getActivity()) + .setTitle(R.string.exception_dialog_title) + .setMessage(exception.toString()) + .setPositiveButton(android.R.string.ok, null) + .show(); + } + + private void displayPermissionsGrantApprovedToast() { + Toast.makeText( + getActivity(), + R.string.toast_permission_grant_approved, + Toast.LENGTH_SHORT).show(); + } + + private void displayPermissionsGrantDeniedToast() { + Toast.makeText( + getActivity(), + R.string.toast_permission_grant_denied, + Toast.LENGTH_SHORT).show(); + } + + private void refreshPermissionsStatus() { + mVibrateStatusPreference.setSummary( + hasPermission(android.Manifest.permission.VIBRATE) + ? R.string.permission_status_granted : R.string.permission_status_not_granted); + mInternetStatusPreference.setSummary( + hasPermission(android.Manifest.permission.INTERNET) + ? R.string.permission_status_granted : R.string.permission_status_not_granted); + mPhoneStateStatusPreference.setSummary( + hasPermission( + android.Manifest.permission.READ_PHONE_STATE) + ? R.string.permission_status_granted : R.string.permission_status_not_granted); + } + + private boolean hasPermission(String permission) { + long identity = Binder.clearCallingIdentity(); + try { + return getActivity().getApplicationContext() + .checkCallingOrSelfPermission(permission) == PackageManager.PERMISSION_GRANTED; + } finally { + Binder.restoreCallingIdentity(identity); + } + } +}