Sync mnc-dev sample prebuilts
Syncing to //developers/samples/android commmit 2be5f5ca32. Change-Id: Ia08c63655bd122c7fbb0cc3a50eec95d8edcb3f1
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 4.9 KiB |
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package com.example.android.basicgesturedetect;
|
package com.example.android.basicgesturedetect;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.os.Build;
|
||||||
import android.view.GestureDetector;
|
import android.view.GestureDetector;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
@@ -29,7 +31,7 @@ public class GestureListener extends GestureDetector.SimpleOnGestureListener {
|
|||||||
@Override
|
@Override
|
||||||
public boolean onSingleTapUp(MotionEvent e) {
|
public boolean onSingleTapUp(MotionEvent e) {
|
||||||
// Up motion completing a single tap occurred.
|
// Up motion completing a single tap occurred.
|
||||||
Log.i(TAG, "Single Tap Up");
|
Log.i(TAG, "Single Tap Up" + getTouchType(e));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,14 +39,14 @@ public class GestureListener extends GestureDetector.SimpleOnGestureListener {
|
|||||||
public void onLongPress(MotionEvent e) {
|
public void onLongPress(MotionEvent e) {
|
||||||
// Touch has been long enough to indicate a long press.
|
// Touch has been long enough to indicate a long press.
|
||||||
// Does not indicate motion is complete yet (no up event necessarily)
|
// Does not indicate motion is complete yet (no up event necessarily)
|
||||||
Log.i(TAG, "Long Press");
|
Log.i(TAG, "Long Press" + getTouchType(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
|
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
|
||||||
float distanceY) {
|
float distanceY) {
|
||||||
// User attempted to scroll
|
// User attempted to scroll
|
||||||
Log.i(TAG, "Scroll");
|
Log.i(TAG, "Scroll" + getTouchType(e1));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,27 +54,27 @@ public class GestureListener extends GestureDetector.SimpleOnGestureListener {
|
|||||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
|
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
|
||||||
float velocityY) {
|
float velocityY) {
|
||||||
// Fling event occurred. Notification of this one happens after an "up" event.
|
// Fling event occurred. Notification of this one happens after an "up" event.
|
||||||
Log.i(TAG, "Fling");
|
Log.i(TAG, "Fling" + getTouchType(e1));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onShowPress(MotionEvent e) {
|
public void onShowPress(MotionEvent e) {
|
||||||
// User performed a down event, and hasn't moved yet.
|
// User performed a down event, and hasn't moved yet.
|
||||||
Log.i(TAG, "Show Press");
|
Log.i(TAG, "Show Press" + getTouchType(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onDown(MotionEvent e) {
|
public boolean onDown(MotionEvent e) {
|
||||||
// "Down" event - User touched the screen.
|
// "Down" event - User touched the screen.
|
||||||
Log.i(TAG, "Down");
|
Log.i(TAG, "Down" + getTouchType(e));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onDoubleTap(MotionEvent e) {
|
public boolean onDoubleTap(MotionEvent e) {
|
||||||
// User tapped the screen twice.
|
// User tapped the screen twice.
|
||||||
Log.i(TAG, "Double tap");
|
Log.i(TAG, "Double tap" + getTouchType(e));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +83,7 @@ public class GestureListener extends GestureDetector.SimpleOnGestureListener {
|
|||||||
// Since double-tap is actually several events which are considered one aggregate
|
// Since double-tap is actually several events which are considered one aggregate
|
||||||
// gesture, there's a separate callback for an individual event within the doubletap
|
// gesture, there's a separate callback for an individual event within the doubletap
|
||||||
// occurring. This occurs for down, up, and move.
|
// occurring. This occurs for down, up, and move.
|
||||||
Log.i(TAG, "Event within double tap");
|
Log.i(TAG, "Event within double tap" + getTouchType(e));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,8 +91,84 @@ public class GestureListener extends GestureDetector.SimpleOnGestureListener {
|
|||||||
public boolean onSingleTapConfirmed(MotionEvent e) {
|
public boolean onSingleTapConfirmed(MotionEvent e) {
|
||||||
// A confirmed single-tap event has occurred. Only called when the detector has
|
// A confirmed single-tap event has occurred. Only called when the detector has
|
||||||
// determined that the first tap stands alone, and is not part of a double tap.
|
// determined that the first tap stands alone, and is not part of a double tap.
|
||||||
Log.i(TAG, "Single tap confirmed");
|
Log.i(TAG, "Single tap confirmed" + getTouchType(e));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// END_INCLUDE(init_gestureListener)
|
// END_INCLUDE(init_gestureListener)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a human-readable string describing the type of touch that triggered a MotionEvent.
|
||||||
|
*/
|
||||||
|
|
||||||
|
private static String getTouchType(MotionEvent e){
|
||||||
|
|
||||||
|
String touchTypeDescription = " ";
|
||||||
|
int touchType = e.getToolType(0);
|
||||||
|
|
||||||
|
switch (touchType) {
|
||||||
|
case MotionEvent.TOOL_TYPE_FINGER:
|
||||||
|
touchTypeDescription += "(finger)";
|
||||||
|
break;
|
||||||
|
case MotionEvent.TOOL_TYPE_STYLUS:
|
||||||
|
touchTypeDescription += "(stylus, ";
|
||||||
|
//Get some additional information about the stylus touch
|
||||||
|
float stylusPressure = e.getPressure();
|
||||||
|
touchTypeDescription += "pressure: " + stylusPressure;
|
||||||
|
|
||||||
|
if(Build.VERSION.SDK_INT >= 21) {
|
||||||
|
touchTypeDescription += ", buttons pressed: " + getButtonsPressed(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
touchTypeDescription += ")";
|
||||||
|
break;
|
||||||
|
case MotionEvent.TOOL_TYPE_ERASER:
|
||||||
|
touchTypeDescription += "(eraser)";
|
||||||
|
break;
|
||||||
|
case MotionEvent.TOOL_TYPE_MOUSE:
|
||||||
|
touchTypeDescription += "(mouse)";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
touchTypeDescription += "(unknown tool)";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return touchTypeDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a human-readable string listing all the stylus buttons that were pressed when the
|
||||||
|
* input MotionEvent occurred.
|
||||||
|
*/
|
||||||
|
@TargetApi(21)
|
||||||
|
private static String getButtonsPressed(MotionEvent e){
|
||||||
|
String buttons = "";
|
||||||
|
|
||||||
|
if(e.isButtonPressed(MotionEvent.BUTTON_PRIMARY)){
|
||||||
|
buttons += " primary";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(e.isButtonPressed(MotionEvent.BUTTON_SECONDARY)){
|
||||||
|
buttons += " secondary";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(e.isButtonPressed(MotionEvent.BUTTON_TERTIARY)){
|
||||||
|
buttons += " tertiary";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(e.isButtonPressed(MotionEvent.BUTTON_BACK)){
|
||||||
|
buttons += " back";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(e.isButtonPressed(MotionEvent.BUTTON_FORWARD)){
|
||||||
|
buttons += " forward";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buttons.equals("")){
|
||||||
|
buttons = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
return buttons;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!--
|
<!--
|
||||||
Copyright 2013 The Android Open Source Project
|
Copyright 2013 The Android Open Source Project
|
||||||
|
|
||||||
@@ -17,23 +17,32 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.example.android.bluetoothadvertisements"
|
package="com.example.android.bluetoothadvertisements"
|
||||||
android:versionCode="1"
|
android:versionCode="1"
|
||||||
android:versionName="1.0">
|
android:versionName="1.0" >
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH"/>
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
|
|
||||||
<application android:allowBackup="true"
|
<application
|
||||||
android:label="@string/app_name"
|
android:allowBackup="true"
|
||||||
android:icon="@drawable/ic_launcher"
|
android:icon="@drawable/ic_launcher"
|
||||||
android:theme="@style/AppTheme">
|
android:label="@string/app_name"
|
||||||
|
android:theme="@style/AppTheme" >
|
||||||
<activity android:name=".MainActivity"
|
<activity
|
||||||
android:label="@string/app_name">
|
android:name=".MainActivity"
|
||||||
|
android:label="@string/app_name" >
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<!-- Service to handle BLE Advertising - Using a service allows advertising to continue
|
||||||
|
when the app is no longer on screen in a reliable manner. -->
|
||||||
|
<service
|
||||||
|
android:name=".AdvertiserService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false" >
|
||||||
|
</service>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 4.8 KiB |
@@ -26,5 +26,8 @@
|
|||||||
<string name="seconds">seconds.</string>
|
<string name="seconds">seconds.</string>
|
||||||
<string name="scan_start_toast">Scanning for</string>
|
<string name="scan_start_toast">Scanning for</string>
|
||||||
<string name="already_scanning">Scanning already started.</string>
|
<string name="already_scanning">Scanning already started.</string>
|
||||||
|
<string name="no_name">(no name)</string>
|
||||||
|
<string name="start_error_unknown">unknown error</string>
|
||||||
|
<string name="advertising_timedout">Advertising stopped due to timeout.</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -16,11 +16,11 @@
|
|||||||
|
|
||||||
package com.example.android.bluetoothadvertisements;
|
package com.example.android.bluetoothadvertisements;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothAdapter;
|
|
||||||
import android.bluetooth.le.AdvertiseCallback;
|
import android.bluetooth.le.AdvertiseCallback;
|
||||||
import android.bluetooth.le.AdvertiseData;
|
import android.content.BroadcastReceiver;
|
||||||
import android.bluetooth.le.AdvertiseSettings;
|
import android.content.Context;
|
||||||
import android.bluetooth.le.BluetoothLeAdvertiser;
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -32,67 +32,121 @@ import android.widget.Toast;
|
|||||||
/**
|
/**
|
||||||
* Allows user to start & stop Bluetooth LE Advertising of their device.
|
* Allows user to start & stop Bluetooth LE Advertising of their device.
|
||||||
*/
|
*/
|
||||||
public class AdvertiserFragment extends Fragment {
|
public class AdvertiserFragment extends Fragment implements View.OnClickListener {
|
||||||
|
|
||||||
private BluetoothAdapter mBluetoothAdapter;
|
|
||||||
|
|
||||||
private BluetoothLeAdvertiser mBluetoothLeAdvertiser;
|
|
||||||
|
|
||||||
private AdvertiseCallback mAdvertiseCallback;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lets user toggle BLE Advertising.
|
||||||
|
*/
|
||||||
private Switch mSwitch;
|
private Switch mSwitch;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Must be called after object creation by MainActivity.
|
* Listens for notifications that the {@code AdvertiserService} has failed to start advertising.
|
||||||
*
|
* This Receiver deals with Fragment UI elements and only needs to be active when the Fragment
|
||||||
* @param btAdapter the local BluetoothAdapter
|
* is on-screen, so it's defined and registered in code instead of the Manifest.
|
||||||
*/
|
*/
|
||||||
public void setBluetoothAdapter(BluetoothAdapter btAdapter) {
|
private BroadcastReceiver advertisingFailureReceiver;
|
||||||
this.mBluetoothAdapter = btAdapter;
|
|
||||||
mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setRetainInstance(true);
|
|
||||||
|
advertisingFailureReceiver = new BroadcastReceiver() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives Advertising error codes from {@code AdvertiserService} and displays error messages
|
||||||
|
* to the user. Sets the advertising toggle to 'false.'
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
|
||||||
|
int errorCode = intent.getIntExtra(AdvertiserService.ADVERTISING_FAILED_EXTRA_CODE, -1);
|
||||||
|
|
||||||
|
mSwitch.setChecked(false);
|
||||||
|
|
||||||
|
String errorMessage = getString(R.string.start_error_prefix);
|
||||||
|
switch (errorCode) {
|
||||||
|
case AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED:
|
||||||
|
errorMessage += " " + getString(R.string.start_error_already_started);
|
||||||
|
break;
|
||||||
|
case AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE:
|
||||||
|
errorMessage += " " + getString(R.string.start_error_too_large);
|
||||||
|
break;
|
||||||
|
case AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED:
|
||||||
|
errorMessage += " " + getString(R.string.start_error_unsupported);
|
||||||
|
break;
|
||||||
|
case AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR:
|
||||||
|
errorMessage += " " + getString(R.string.start_error_internal);
|
||||||
|
break;
|
||||||
|
case AdvertiseCallback.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS:
|
||||||
|
errorMessage += " " + getString(R.string.start_error_too_many);
|
||||||
|
break;
|
||||||
|
case AdvertiserService.ADVERTISING_TIMED_OUT:
|
||||||
|
errorMessage = " " + getString(R.string.advertising_timedout);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
errorMessage += " " + getString(R.string.start_error_unknown);
|
||||||
|
}
|
||||||
|
|
||||||
|
Toast.makeText(getActivity(), errorMessage, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
|
|
||||||
View view = inflater.inflate(R.layout.fragment_advertiser, container, false);
|
View view = inflater.inflate(R.layout.fragment_advertiser, container, false);
|
||||||
|
|
||||||
mSwitch = (Switch) view.findViewById(R.id.advertise_switch);
|
mSwitch = (Switch) view.findViewById(R.id.advertise_switch);
|
||||||
mSwitch.setOnClickListener(new View.OnClickListener() {
|
mSwitch.setOnClickListener(this);
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
onSwitchClicked(v);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When app comes on screen, check if BLE Advertisements are running, set switch accordingly,
|
||||||
|
* and register the Receiver to be notified if Advertising fails.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onStop() {
|
public void onResume() {
|
||||||
super.onStop();
|
super.onResume();
|
||||||
|
|
||||||
if(mAdvertiseCallback != null){
|
if (AdvertiserService.running) {
|
||||||
stopAdvertising();
|
mSwitch.setChecked(true);
|
||||||
|
} else {
|
||||||
|
mSwitch.setChecked(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IntentFilter failureFilter = new IntentFilter(AdvertiserService.ADVERTISING_FAILED);
|
||||||
|
getActivity().registerReceiver(advertisingFailureReceiver, failureFilter);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When app goes off screen, unregister the Advertising failure Receiver to stop memory leaks.
|
||||||
|
* (and because the app doesn't care if Advertising fails while the UI isn't active)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
getActivity().unregisterReceiver(advertisingFailureReceiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Intent addressed to the {@code AdvertiserService} class.
|
||||||
|
*/
|
||||||
|
private static Intent getServiceIntent(Context c) {
|
||||||
|
return new Intent(c, AdvertiserService.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when switch is toggled - starts or stops advertising.
|
* Called when switch is toggled - starts or stops advertising.
|
||||||
*
|
|
||||||
* @param view is the Switch View object
|
|
||||||
*/
|
*/
|
||||||
public void onSwitchClicked(View view) {
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
// Is the toggle on?
|
// Is the toggle on?
|
||||||
boolean on = ((Switch) view).isChecked();
|
boolean on = ((Switch) v).isChecked();
|
||||||
|
|
||||||
if (on) {
|
if (on) {
|
||||||
startAdvertising();
|
startAdvertising();
|
||||||
@@ -102,105 +156,20 @@ public class AdvertiserFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts BLE Advertising.
|
* Starts BLE Advertising by starting {@code AdvertiserService}.
|
||||||
*/
|
*/
|
||||||
private void startAdvertising() {
|
private void startAdvertising() {
|
||||||
|
Context c = getActivity();
|
||||||
mAdvertiseCallback = new SampleAdvertiseCallback();
|
c.startService(getServiceIntent(c));
|
||||||
|
|
||||||
if (mBluetoothLeAdvertiser != null) {
|
|
||||||
mBluetoothLeAdvertiser.startAdvertising(buildAdvertiseSettings(), buildAdvertiseData(),
|
|
||||||
mAdvertiseCallback);
|
|
||||||
} else {
|
|
||||||
mSwitch.setChecked(false);
|
|
||||||
Toast.makeText(getActivity(), getString(R.string.bt_null), Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops BLE Advertising.
|
* Stops BLE Advertising by stopping {@code AdvertiserService}.
|
||||||
*/
|
*/
|
||||||
private void stopAdvertising() {
|
private void stopAdvertising() {
|
||||||
|
Context c = getActivity();
|
||||||
if (mBluetoothLeAdvertiser != null) {
|
c.stopService(getServiceIntent(c));
|
||||||
|
mSwitch.setChecked(false);
|
||||||
mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback);
|
|
||||||
mAdvertiseCallback = null;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
mSwitch.setChecked(false);
|
|
||||||
Toast.makeText(getActivity(), getString(R.string.bt_null), Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
}
|
||||||
* Returns an AdvertiseData object which includes the Service UUID and Device Name.
|
|
||||||
*/
|
|
||||||
private AdvertiseData buildAdvertiseData() {
|
|
||||||
|
|
||||||
// Note: There is a strict limit of 31 Bytes on packets sent over BLE Advertisements.
|
|
||||||
// This includes everything put into AdvertiseData including UUIDs, device info, &
|
|
||||||
// arbitrary service or manufacturer data.
|
|
||||||
// Attempting to send packets over this limit will result in a failure with error code
|
|
||||||
// AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE. Catch this error in the
|
|
||||||
// onStartFailure() method of an AdvertiseCallback implementation.
|
|
||||||
|
|
||||||
AdvertiseData.Builder dataBuilder = new AdvertiseData.Builder();
|
|
||||||
dataBuilder.addServiceUuid(Constants.Service_UUID);
|
|
||||||
dataBuilder.setIncludeDeviceName(true);
|
|
||||||
|
|
||||||
return dataBuilder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an AdvertiseSettings object set to use low power (to help preserve battery life).
|
|
||||||
*/
|
|
||||||
private AdvertiseSettings buildAdvertiseSettings() {
|
|
||||||
AdvertiseSettings.Builder settingsBuilder = new AdvertiseSettings.Builder();
|
|
||||||
settingsBuilder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_POWER);
|
|
||||||
|
|
||||||
return settingsBuilder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom callback after Advertising succeeds or fails to start.
|
|
||||||
*/
|
|
||||||
private class SampleAdvertiseCallback extends AdvertiseCallback {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStartFailure(int errorCode) {
|
|
||||||
super.onStartFailure(errorCode);
|
|
||||||
|
|
||||||
mSwitch.setChecked(false);
|
|
||||||
|
|
||||||
String errorMessage = getString(R.string.start_error_prefix);
|
|
||||||
switch (errorCode) {
|
|
||||||
case AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED:
|
|
||||||
errorMessage += " " + getString(R.string.start_error_already_started);
|
|
||||||
break;
|
|
||||||
case AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE:
|
|
||||||
errorMessage += " " + getString(R.string.start_error_too_large);
|
|
||||||
break;
|
|
||||||
case AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED:
|
|
||||||
errorMessage += " " + getString(R.string.start_error_unsupported);
|
|
||||||
break;
|
|
||||||
case AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR:
|
|
||||||
errorMessage += " " + getString(R.string.start_error_internal);
|
|
||||||
break;
|
|
||||||
case AdvertiseCallback.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS:
|
|
||||||
errorMessage += " " + getString(R.string.start_error_too_many);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Toast.makeText(getActivity(), errorMessage, Toast.LENGTH_LONG).show();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
|
|
||||||
super.onStartSuccess(settingsInEffect);
|
|
||||||
// Don't need to do anything here, advertising successfully started.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
package com.example.android.bluetoothadvertisements;
|
||||||
|
|
||||||
|
import android.app.Service;
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
import android.bluetooth.BluetoothManager;
|
||||||
|
import android.bluetooth.le.AdvertiseCallback;
|
||||||
|
import android.bluetooth.le.AdvertiseData;
|
||||||
|
import android.bluetooth.le.AdvertiseSettings;
|
||||||
|
import android.bluetooth.le.BluetoothLeAdvertiser;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages BLE Advertising independent of the main app.
|
||||||
|
* If the app goes off screen (or gets killed completely) advertising can continue because this
|
||||||
|
* Service is maintaining the necessary Callback in memory.
|
||||||
|
*/
|
||||||
|
public class AdvertiserService extends Service {
|
||||||
|
|
||||||
|
private static final String TAG = AdvertiserService.class.getSimpleName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A global variable to let AdvertiserFragment check if the Service is running without needing
|
||||||
|
* to start or bind to it.
|
||||||
|
* This is the best practice method as defined here:
|
||||||
|
* https://groups.google.com/forum/#!topic/android-developers/jEvXMWgbgzE
|
||||||
|
*/
|
||||||
|
public static boolean running = false;
|
||||||
|
|
||||||
|
public static final String ADVERTISING_FAILED =
|
||||||
|
"com.example.android.bluetoothadvertisements.advertising_failed";
|
||||||
|
|
||||||
|
public static final String ADVERTISING_FAILED_EXTRA_CODE = "failureCode";
|
||||||
|
|
||||||
|
public static final int ADVERTISING_TIMED_OUT = 6;
|
||||||
|
|
||||||
|
private BluetoothLeAdvertiser mBluetoothLeAdvertiser;
|
||||||
|
|
||||||
|
private AdvertiseCallback mAdvertiseCallback;
|
||||||
|
|
||||||
|
private Handler mHandler;
|
||||||
|
|
||||||
|
private Runnable timeoutRunnable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Length of time to allow advertising before automatically shutting off. (10 minutes)
|
||||||
|
*/
|
||||||
|
private long TIMEOUT = TimeUnit.MILLISECONDS.convert(10, TimeUnit.MINUTES);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
running = true;
|
||||||
|
initialize();
|
||||||
|
startAdvertising();
|
||||||
|
setTimeout();
|
||||||
|
super.onCreate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
/**
|
||||||
|
* Note that onDestroy is not guaranteed to be called quickly or at all. Services exist at
|
||||||
|
* the whim of the system, and onDestroy can be delayed or skipped entirely if memory need
|
||||||
|
* is critical.
|
||||||
|
*/
|
||||||
|
running = false;
|
||||||
|
stopAdvertising();
|
||||||
|
mHandler.removeCallbacks(timeoutRunnable);
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Required for extending service, but this will be a Started Service only, so no need for
|
||||||
|
* binding.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get references to system Bluetooth objects if we don't have them already.
|
||||||
|
*/
|
||||||
|
private void initialize() {
|
||||||
|
if (mBluetoothLeAdvertiser == null) {
|
||||||
|
BluetoothManager mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
|
||||||
|
if (mBluetoothManager != null) {
|
||||||
|
BluetoothAdapter mBluetoothAdapter = mBluetoothManager.getAdapter();
|
||||||
|
if (mBluetoothAdapter != null) {
|
||||||
|
mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this, getString(R.string.bt_null), Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this, getString(R.string.bt_null), Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a delayed Runnable that will cause the BLE Advertising to timeout and stop after a
|
||||||
|
* set amount of time.
|
||||||
|
*/
|
||||||
|
private void setTimeout(){
|
||||||
|
mHandler = new Handler();
|
||||||
|
timeoutRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Log.d(TAG, "AdvertiserService has reached timeout of "+TIMEOUT+" milliseconds, stopping advertising.");
|
||||||
|
sendFailureIntent(ADVERTISING_TIMED_OUT);
|
||||||
|
stopSelf();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mHandler.postDelayed(timeoutRunnable, TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts BLE Advertising.
|
||||||
|
*/
|
||||||
|
private void startAdvertising() {
|
||||||
|
Log.d(TAG, "Service: Starting Advertising");
|
||||||
|
|
||||||
|
if (mAdvertiseCallback == null) {
|
||||||
|
AdvertiseSettings settings = buildAdvertiseSettings();
|
||||||
|
AdvertiseData data = buildAdvertiseData();
|
||||||
|
mAdvertiseCallback = new SampleAdvertiseCallback();
|
||||||
|
|
||||||
|
if (mBluetoothLeAdvertiser != null) {
|
||||||
|
mBluetoothLeAdvertiser.startAdvertising(settings, data,
|
||||||
|
mAdvertiseCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops BLE Advertising.
|
||||||
|
*/
|
||||||
|
private void stopAdvertising() {
|
||||||
|
Log.d(TAG, "Service: Stopping Advertising");
|
||||||
|
if (mBluetoothLeAdvertiser != null) {
|
||||||
|
mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback);
|
||||||
|
mAdvertiseCallback = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an AdvertiseData object which includes the Service UUID and Device Name.
|
||||||
|
*/
|
||||||
|
private AdvertiseData buildAdvertiseData() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: There is a strict limit of 31 Bytes on packets sent over BLE Advertisements.
|
||||||
|
* This includes everything put into AdvertiseData including UUIDs, device info, &
|
||||||
|
* arbitrary service or manufacturer data.
|
||||||
|
* Attempting to send packets over this limit will result in a failure with error code
|
||||||
|
* AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE. Catch this error in the
|
||||||
|
* onStartFailure() method of an AdvertiseCallback implementation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
AdvertiseData.Builder dataBuilder = new AdvertiseData.Builder();
|
||||||
|
dataBuilder.addServiceUuid(Constants.Service_UUID);
|
||||||
|
dataBuilder.setIncludeDeviceName(true);
|
||||||
|
|
||||||
|
/* For example - this will cause advertising to fail (exceeds size limit) */
|
||||||
|
//String failureData = "asdghkajsghalkxcjhfa;sghtalksjcfhalskfjhasldkjfhdskf";
|
||||||
|
//dataBuilder.addServiceData(Constants.Service_UUID, failureData.getBytes());
|
||||||
|
|
||||||
|
return dataBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an AdvertiseSettings object set to use low power (to help preserve battery life)
|
||||||
|
* and disable the built-in timeout since this code uses its own timeout runnable.
|
||||||
|
*/
|
||||||
|
private AdvertiseSettings buildAdvertiseSettings() {
|
||||||
|
AdvertiseSettings.Builder settingsBuilder = new AdvertiseSettings.Builder();
|
||||||
|
settingsBuilder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_POWER);
|
||||||
|
settingsBuilder.setTimeout(0);
|
||||||
|
return settingsBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom callback after Advertising succeeds or fails to start. Broadcasts the error code
|
||||||
|
* in an Intent to be picked up by AdvertiserFragment and stops this Service.
|
||||||
|
*/
|
||||||
|
private class SampleAdvertiseCallback extends AdvertiseCallback {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartFailure(int errorCode) {
|
||||||
|
super.onStartFailure(errorCode);
|
||||||
|
|
||||||
|
Log.d(TAG, "Advertising failed");
|
||||||
|
sendFailureIntent(errorCode);
|
||||||
|
stopSelf();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
|
||||||
|
super.onStartSuccess(settingsInEffect);
|
||||||
|
Log.d(TAG, "Advertising successfully started");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds and sends a broadcast intent indicating Advertising has failed. Includes the error
|
||||||
|
* code as an extra. This is intended to be picked up by the {@code AdvertiserFragment}.
|
||||||
|
*/
|
||||||
|
private void sendFailureIntent(int errorCode){
|
||||||
|
Intent failureIntent = new Intent();
|
||||||
|
failureIntent.setAction(ADVERTISING_FAILED);
|
||||||
|
failureIntent.putExtra(ADVERTISING_FAILED_EXTRA_CODE, errorCode);
|
||||||
|
sendBroadcast(failureIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -39,7 +39,7 @@ public class MainActivity extends FragmentActivity {
|
|||||||
setContentView(R.layout.activity_main);
|
setContentView(R.layout.activity_main);
|
||||||
setTitle(R.string.activity_main_title);
|
setTitle(R.string.activity_main_title);
|
||||||
|
|
||||||
if (savedInstanceState == null ) {
|
if (savedInstanceState == null) {
|
||||||
|
|
||||||
mBluetoothAdapter = ((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE))
|
mBluetoothAdapter = ((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE))
|
||||||
.getAdapter();
|
.getAdapter();
|
||||||
@@ -112,11 +112,11 @@ public class MainActivity extends FragmentActivity {
|
|||||||
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||||
|
|
||||||
ScannerFragment scannerFragment = new ScannerFragment();
|
ScannerFragment scannerFragment = new ScannerFragment();
|
||||||
|
// Fragments can't access system services directly, so pass it the BluetoothAdapter
|
||||||
scannerFragment.setBluetoothAdapter(mBluetoothAdapter);
|
scannerFragment.setBluetoothAdapter(mBluetoothAdapter);
|
||||||
transaction.replace(R.id.scanner_fragment_container, scannerFragment);
|
transaction.replace(R.id.scanner_fragment_container, scannerFragment);
|
||||||
|
|
||||||
AdvertiserFragment advertiserFragment = new AdvertiserFragment();
|
AdvertiserFragment advertiserFragment = new AdvertiserFragment();
|
||||||
advertiserFragment.setBluetoothAdapter(mBluetoothAdapter);
|
|
||||||
transaction.replace(R.id.advertiser_fragment_container, advertiserFragment);
|
transaction.replace(R.id.advertiser_fragment_container, advertiserFragment);
|
||||||
|
|
||||||
transaction.commit();
|
transaction.commit();
|
||||||
|
|||||||
@@ -75,7 +75,11 @@ public class ScanResultAdapter extends BaseAdapter {
|
|||||||
|
|
||||||
ScanResult scanResult = mArrayList.get(position);
|
ScanResult scanResult = mArrayList.get(position);
|
||||||
|
|
||||||
deviceNameView.setText(scanResult.getDevice().getName());
|
String name = scanResult.getDevice().getName();
|
||||||
|
if (name == null) {
|
||||||
|
name = mContext.getResources().getString(R.string.no_name);
|
||||||
|
}
|
||||||
|
deviceNameView.setText(name);
|
||||||
deviceAddressView.setText(scanResult.getDevice().getAddress());
|
deviceAddressView.setText(scanResult.getDevice().getAddress());
|
||||||
lastSeenView.setText(getTimeSinceString(mContext, scanResult.getTimestampNanos()));
|
lastSeenView.setText(getTimeSinceString(mContext, scanResult.getTimestampNanos()));
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ public class ScannerFragment extends ListFragment {
|
|||||||
// We could get a LayoutInflater from the ApplicationContext but it messes with the
|
// We could get a LayoutInflater from the ApplicationContext but it messes with the
|
||||||
// default theme, so generate it from getActivity() and pass it in separately.
|
// default theme, so generate it from getActivity() and pass it in separately.
|
||||||
mAdapter = new ScanResultAdapter(getActivity().getApplicationContext(),
|
mAdapter = new ScanResultAdapter(getActivity().getApplicationContext(),
|
||||||
LayoutInflater.from(getActivity()));
|
LayoutInflater.from(getActivity()));
|
||||||
mHandler = new Handler();
|
mHandler = new Handler();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -180,6 +180,7 @@ public class ScannerFragment extends ListFragment {
|
|||||||
List<ScanFilter> scanFilters = new ArrayList<>();
|
List<ScanFilter> scanFilters = new ArrayList<>();
|
||||||
|
|
||||||
ScanFilter.Builder builder = new ScanFilter.Builder();
|
ScanFilter.Builder builder = new ScanFilter.Builder();
|
||||||
|
// Comment out the below line to see all BLE devices around you
|
||||||
builder.setServiceUuid(Constants.Service_UUID);
|
builder.setServiceUuid(Constants.Service_UUID);
|
||||||
scanFilters.add(builder.build());
|
scanFilters.add(builder.build());
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.example.android.fingerprintdialog;
|
package com.example.android.fingerprintdialog;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.app.DialogFragment;
|
import android.app.DialogFragment;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.hardware.fingerprint.FingerprintManager;
|
import android.hardware.fingerprint.FingerprintManager;
|
||||||
@@ -54,6 +55,7 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment
|
|||||||
|
|
||||||
private FingerprintManager.CryptoObject mCryptoObject;
|
private FingerprintManager.CryptoObject mCryptoObject;
|
||||||
private FingerprintUiHelper mFingerprintUiHelper;
|
private FingerprintUiHelper mFingerprintUiHelper;
|
||||||
|
private MainActivity mActivity;
|
||||||
|
|
||||||
@Inject FingerprintUiHelper.FingerprintUiHelperBuilder mFingerprintUiHelperBuilder;
|
@Inject FingerprintUiHelper.FingerprintUiHelperBuilder mFingerprintUiHelperBuilder;
|
||||||
@Inject InputMethodManager mInputMethodManager;
|
@Inject InputMethodManager mInputMethodManager;
|
||||||
@@ -135,6 +137,12 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment
|
|||||||
mFingerprintUiHelper.stopListening();
|
mFingerprintUiHelper.stopListening();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Activity activity) {
|
||||||
|
super.onAttach(activity);
|
||||||
|
mActivity = (MainActivity) activity;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the crypto object to be passed in when authenticating with fingerprint.
|
* Sets the crypto object to be passed in when authenticating with fingerprint.
|
||||||
*/
|
*/
|
||||||
@@ -167,7 +175,6 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment
|
|||||||
if (!checkPassword(mPassword.getText().toString())) {
|
if (!checkPassword(mPassword.getText().toString())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MainActivity activity = ((MainActivity) getActivity());
|
|
||||||
if (mStage == Stage.NEW_FINGERPRINT_ENROLLED) {
|
if (mStage == Stage.NEW_FINGERPRINT_ENROLLED) {
|
||||||
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
||||||
editor.putBoolean(getString(R.string.use_fingerprint_to_authenticate_key),
|
editor.putBoolean(getString(R.string.use_fingerprint_to_authenticate_key),
|
||||||
@@ -176,12 +183,12 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment
|
|||||||
|
|
||||||
if (mUseFingerprintFutureCheckBox.isChecked()) {
|
if (mUseFingerprintFutureCheckBox.isChecked()) {
|
||||||
// Re-create the key so that fingerprints including new ones are validated.
|
// Re-create the key so that fingerprints including new ones are validated.
|
||||||
activity.createKey();
|
mActivity.createKey();
|
||||||
mStage = Stage.FINGERPRINT;
|
mStage = Stage.FINGERPRINT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mPassword.setText("");
|
mPassword.setText("");
|
||||||
((MainActivity) getActivity()).onPurchased(false /* without Fingerprint */);
|
mActivity.onPurchased(false /* without Fingerprint */);
|
||||||
dismiss();
|
dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,7 +245,7 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment
|
|||||||
public void onAuthenticated() {
|
public void onAuthenticated() {
|
||||||
// Callback from FingerprintUiHelper. Let the activity know that authentication was
|
// Callback from FingerprintUiHelper. Let the activity know that authentication was
|
||||||
// successful.
|
// successful.
|
||||||
((MainActivity) getActivity()).onPurchased(true /* withFingerprint */);
|
mActivity.onPurchased(true /* withFingerprint */);
|
||||||
dismiss();
|
dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ sample.group=System
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|
||||||
This sample shows runtime permissions available in the Android M and above.
|
This sample shows runtime permissions available in Android M and above.
|
||||||
Display the log to follow the execution.
|
Display the log on screen to follow the execution.
|
||||||
If executed on an Android M device, an additional option to access contacts is shown.
|
If executed on an Android M device, an additional option to access contacts is shown
|
||||||
|
that is declared with optional, M and above only permissions.
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -21,9 +21,10 @@
|
|||||||
<![CDATA[
|
<![CDATA[
|
||||||
|
|
||||||
|
|
||||||
This sample shows runtime permissions available in the Android M and above.
|
This sample shows runtime permissions available in Android M and above.
|
||||||
Display the log to follow the execution.
|
Display the log on screen to follow the execution.
|
||||||
If executed on an Android M device, an additional option to access contacts is shown.
|
If executed on an Android M device, an additional option to access contacts is shown
|
||||||
|
that is declared with optional, M and above only permissions.
|
||||||
|
|
||||||
|
|
||||||
]]>
|
]]>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
<string name="ok">OK</string>
|
||||||
<string name="contacts_string">Total number of contacts: %1$,d\nFirst contact:<b>%2$s</b></string>
|
<string name="contacts_string">Total number of contacts: %1$,d\nFirst contact:<b>%2$s</b></string>
|
||||||
<string name="contacts_none">No contacts stored on device.</string>
|
<string name="contacts_none">No contacts stored on device.</string>
|
||||||
<string name="contacts_empty">Contacts not loaded.</string>
|
<string name="contacts_empty">Contacts not loaded.</string>
|
||||||
@@ -17,5 +18,5 @@
|
|||||||
<string name="permision_available_contacts">Contacts Permissions have been granted. Contacts screen can now be opened.</string>
|
<string name="permision_available_contacts">Contacts Permissions have been granted. Contacts screen can now be opened.</string>
|
||||||
<string name="permissions_not_granted">Permissions were not granted.</string>
|
<string name="permissions_not_granted">Permissions were not granted.</string>
|
||||||
<string name="permission_camera_rationale">Camera permission is needed to show the camera preview.</string>
|
<string name="permission_camera_rationale">Camera permission is needed to show the camera preview.</string>
|
||||||
<string name="permission_contacts_rationale">Contacts permissions are needed to demonstrate access to the contacts database.</string>
|
<string name="permission_contacts_rationale">Contacts permissions are needed to demonstrate access.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package com.example.android.system.runtimepermissions;
|
package com.example.android.system.runtimepermissions;
|
||||||
|
|
||||||
import com.example.android.common.activities.SampleActivityBase;
|
|
||||||
import com.example.android.common.logger.Log;
|
import com.example.android.common.logger.Log;
|
||||||
import com.example.android.common.logger.LogFragment;
|
import com.example.android.common.logger.LogFragment;
|
||||||
import com.example.android.common.logger.LogWrapper;
|
import com.example.android.common.logger.LogWrapper;
|
||||||
@@ -26,15 +25,20 @@ import com.example.android.system.runtimepermissions.contacts.ContactsFragment;
|
|||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.design.widget.Snackbar;
|
||||||
|
import android.support.v4.app.ActivityCompat;
|
||||||
import android.support.v4.app.FragmentTransaction;
|
import android.support.v4.app.FragmentTransaction;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Toast;
|
|
||||||
import android.widget.ViewAnimator;
|
import android.widget.ViewAnimator;
|
||||||
|
|
||||||
|
import common.activities.SampleActivityBase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launcher Activity that demonstrates the use of runtime permissions for Android M.
|
* Launcher Activity that demonstrates the use of runtime permissions for Android M.
|
||||||
* It contains a summary sample description, sample log and a Fragment that calls callbacks on this
|
* It contains a summary sample description, sample log and a Fragment that calls callbacks on this
|
||||||
@@ -46,15 +50,18 @@ import android.widget.ViewAnimator;
|
|||||||
* android.Manifest.permission#WRITE_CONTACTS})) are requested when the 'Show and Add Contacts'
|
* android.Manifest.permission#WRITE_CONTACTS})) are requested when the 'Show and Add Contacts'
|
||||||
* button is
|
* button is
|
||||||
* clicked to display the first contact in the contacts database and to add a dummy contact
|
* clicked to display the first contact in the contacts database and to add a dummy contact
|
||||||
* directly
|
* directly to it. Permissions are verified and requested through compat helpers in the support v4
|
||||||
* to it. First, permissions are checked if they have already been granted through {@link
|
* library, in this Activity using {@link ActivityCompat}.
|
||||||
* android.app.Activity#checkSelfPermission(String)} (wrapped in {@link
|
* First, permissions are checked if they have already been granted through {@link
|
||||||
* PermissionUtil#hasSelfPermission(Activity, String)} and {@link PermissionUtil#hasSelfPermission(Activity,
|
* ActivityCompat#checkSelfPermission(Context, String)}.
|
||||||
* String[])} for compatibility). If permissions have not been granted, they are requested through
|
* If permissions have not been granted, they are requested through
|
||||||
* {@link Activity#requestPermissions(String[], int)} and the return value checked in {@link
|
* {@link ActivityCompat#requestPermissions(Activity, String[], int)} and the return value checked
|
||||||
* Activity#onRequestPermissionsResult(int, String[], int[])}.
|
* in
|
||||||
|
* a callback to the {@link android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback}
|
||||||
|
* interface.
|
||||||
* <p>
|
* <p>
|
||||||
* Before requesting permissions, {@link Activity#shouldShowRequestPermissionRationale(String)}
|
* Before requesting permissions, {@link ActivityCompat#shouldShowRequestPermissionRationale(Activity,
|
||||||
|
* String)}
|
||||||
* should be called to provide the user with additional context for the use of permissions if they
|
* should be called to provide the user with additional context for the use of permissions if they
|
||||||
* have been denied previously.
|
* have been denied previously.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -73,7 +80,8 @@ import android.widget.ViewAnimator;
|
|||||||
* <p>
|
* <p>
|
||||||
* (This class is based on the MainActivity used in the SimpleFragment sample template.)
|
* (This class is based on the MainActivity used in the SimpleFragment sample template.)
|
||||||
*/
|
*/
|
||||||
public class MainActivity extends SampleActivityBase {
|
public class MainActivity extends SampleActivityBase
|
||||||
|
implements ActivityCompat.OnRequestPermissionsResultCallback {
|
||||||
|
|
||||||
public static final String TAG = "MainActivity";
|
public static final String TAG = "MainActivity";
|
||||||
|
|
||||||
@@ -96,6 +104,10 @@ public class MainActivity extends SampleActivityBase {
|
|||||||
// Whether the Log Fragment is currently shown.
|
// Whether the Log Fragment is currently shown.
|
||||||
private boolean mLogShown;
|
private boolean mLogShown;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Root of the layout of this Activity.
|
||||||
|
*/
|
||||||
|
private View mLayout;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the 'show camera' button is clicked.
|
* Called when the 'show camera' button is clicked.
|
||||||
@@ -105,62 +117,122 @@ public class MainActivity extends SampleActivityBase {
|
|||||||
Log.i(TAG, "Show camera button pressed. Checking permission.");
|
Log.i(TAG, "Show camera button pressed. Checking permission.");
|
||||||
// BEGIN_INCLUDE(camera_permission)
|
// BEGIN_INCLUDE(camera_permission)
|
||||||
// Check if the Camera permission is already available.
|
// Check if the Camera permission is already available.
|
||||||
if (PermissionUtil.hasSelfPermission(this, Manifest.permission.CAMERA)) {
|
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
|
||||||
|
!= PackageManager.PERMISSION_GRANTED) {
|
||||||
|
// Camera permission has not been granted.
|
||||||
|
|
||||||
|
requestCameraPermission();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
// Camera permissions is already available, show the camera preview.
|
// Camera permissions is already available, show the camera preview.
|
||||||
Log.i(TAG,
|
Log.i(TAG,
|
||||||
"CAMERA permission has already been granted. Displaying camera preview.");
|
"CAMERA permission has already been granted. Displaying camera preview.");
|
||||||
showCameraPreview();
|
showCameraPreview();
|
||||||
} else {
|
|
||||||
// Camera permission has not been granted.
|
|
||||||
Log.i(TAG, "CAMERA permission has NOT been granted. Requesting permission.");
|
|
||||||
|
|
||||||
// Provide an additional rationale to the user if the permission was not granted
|
|
||||||
// and the user would benefit from additional context for the use of the permission.
|
|
||||||
if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
|
|
||||||
Log.i(TAG,
|
|
||||||
"Displaying camera permission rationale to provide additional context.");
|
|
||||||
Toast.makeText(this, R.string.permission_camera_rationale, Toast.LENGTH_SHORT)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request Camera permission
|
|
||||||
requestPermissions(new String[]{Manifest.permission.CAMERA},
|
|
||||||
REQUEST_CAMERA);
|
|
||||||
}
|
}
|
||||||
// END_INCLUDE(camera_permission)
|
// END_INCLUDE(camera_permission)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests the Camera permission.
|
||||||
|
* If the permission has been denied previously, a SnackBar will prompt the user to grant the
|
||||||
|
* permission, otherwise it is requested directly.
|
||||||
|
*/
|
||||||
|
private void requestCameraPermission() {
|
||||||
|
Log.i(TAG, "CAMERA permission has NOT been granted. Requesting permission.");
|
||||||
|
|
||||||
|
// BEGIN_INCLUDE(camera_permission_request)
|
||||||
|
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
|
||||||
|
Manifest.permission.CAMERA)) {
|
||||||
|
// Provide an additional rationale to the user if the permission was not granted
|
||||||
|
// and the user would benefit from additional context for the use of the permission.
|
||||||
|
// For example if the user has previously denied the permission.
|
||||||
|
Log.i(TAG,
|
||||||
|
"Displaying camera permission rationale to provide additional context.");
|
||||||
|
Snackbar.make(mLayout, R.string.permission_camera_rationale,
|
||||||
|
Snackbar.LENGTH_INDEFINITE)
|
||||||
|
.setAction(R.string.ok, new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
ActivityCompat.requestPermissions(MainActivity.this,
|
||||||
|
new String[]{Manifest.permission.CAMERA},
|
||||||
|
REQUEST_CAMERA);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.show();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Camera permission has not been granted yet. Request it directly.
|
||||||
|
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA},
|
||||||
|
REQUEST_CAMERA);
|
||||||
|
}
|
||||||
|
// END_INCLUDE(camera_permission_request)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the 'show camera' button is clicked.
|
* Called when the 'show camera' button is clicked.
|
||||||
* Callback is defined in resource layout definition.
|
* Callback is defined in resource layout definition.
|
||||||
*/
|
*/
|
||||||
public void showContacts(View v) {
|
public void showContacts(View v) {
|
||||||
Log.i(TAG, "Show contacts button pressed. Checking permissions.");
|
Log.i(TAG, "Show contacts button pressed. Checking permissions.");
|
||||||
|
|
||||||
// Verify that all required contact permissions have been granted.
|
// Verify that all required contact permissions have been granted.
|
||||||
if (PermissionUtil.hasSelfPermission(this, PERMISSIONS_CONTACT)) {
|
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)
|
||||||
|
!= PackageManager.PERMISSION_GRANTED
|
||||||
|
|| ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_CONTACTS)
|
||||||
|
!= PackageManager.PERMISSION_GRANTED) {
|
||||||
|
// Contacts permissions have not been granted.
|
||||||
|
Log.i(TAG, "Contact permissions has NOT been granted. Requesting permissions.");
|
||||||
|
requestContactsPermissions();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Contact permissions have been granted. Show the contacts fragment.
|
||||||
Log.i(TAG,
|
Log.i(TAG,
|
||||||
"Contact permissions have already been granted. Displaying contact details.");
|
"Contact permissions have already been granted. Displaying contact details.");
|
||||||
// Contact permissions have been granted. Show the contacts fragment.
|
|
||||||
showContactDetails();
|
showContactDetails();
|
||||||
} else {
|
}
|
||||||
// Contacts permissions have not been granted.
|
}
|
||||||
Log.i(TAG, "Contact permissions has NOT been granted. Requesting permission.");
|
|
||||||
|
/**
|
||||||
|
* Requests the Contacts permissions.
|
||||||
|
* If the permission has been denied previously, a SnackBar will prompt the user to grant the
|
||||||
|
* permission, otherwise it is requested directly.
|
||||||
|
*/
|
||||||
|
private void requestContactsPermissions() {
|
||||||
|
// BEGIN_INCLUDE(contacts_permission_request)
|
||||||
|
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
|
||||||
|
Manifest.permission.READ_CONTACTS)
|
||||||
|
|| ActivityCompat.shouldShowRequestPermissionRationale(this,
|
||||||
|
Manifest.permission.WRITE_CONTACTS)) {
|
||||||
|
|
||||||
// Provide an additional rationale to the user if the permission was not granted
|
// Provide an additional rationale to the user if the permission was not granted
|
||||||
// and the user would benefit from additional context for the use of the permission.
|
// and the user would benefit from additional context for the use of the permission.
|
||||||
if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
|
// For example, if the request has been denied previously.
|
||||||
Log.i(TAG,
|
Log.i(TAG,
|
||||||
"Displaying contacts permission rationale to provide additional context.");
|
"Displaying contacts permission rationale to provide additional context.");
|
||||||
Toast.makeText(this, R.string.permission_contacts_rationale, Toast.LENGTH_SHORT)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
// contact permissions has not been granted (read and write contacts). Request them.
|
// Display a SnackBar with an explanation and a button to trigger the request.
|
||||||
requestPermissions(PERMISSIONS_CONTACT, REQUEST_CONTACTS);
|
Snackbar.make(mLayout, R.string.permission_contacts_rationale,
|
||||||
|
Snackbar.LENGTH_INDEFINITE)
|
||||||
|
.setAction(R.string.ok, new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
ActivityCompat
|
||||||
|
.requestPermissions(MainActivity.this, PERMISSIONS_CONTACT,
|
||||||
|
REQUEST_CONTACTS);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.show();
|
||||||
|
} else {
|
||||||
|
// Contact permissions have not been granted yet. Request them directly.
|
||||||
|
ActivityCompat.requestPermissions(this, PERMISSIONS_CONTACT, REQUEST_CONTACTS);
|
||||||
}
|
}
|
||||||
|
// END_INCLUDE(contacts_permission_request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the {@link CameraPreviewFragment} in the content area if the required Camera
|
* Display the {@link CameraPreviewFragment} in the content area if the required Camera
|
||||||
* permission has been granted.
|
* permission has been granted.
|
||||||
@@ -189,8 +261,8 @@ public class MainActivity extends SampleActivityBase {
|
|||||||
* Callback received when a permissions request has been completed.
|
* Callback received when a permissions request has been completed.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onRequestPermissionsResult(int requestCode, String[] permissions,
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
|
||||||
int[] grantResults) {
|
@NonNull int[] grantResults) {
|
||||||
|
|
||||||
if (requestCode == REQUEST_CAMERA) {
|
if (requestCode == REQUEST_CAMERA) {
|
||||||
// BEGIN_INCLUDE(permission_result)
|
// BEGIN_INCLUDE(permission_result)
|
||||||
@@ -198,14 +270,15 @@ public class MainActivity extends SampleActivityBase {
|
|||||||
Log.i(TAG, "Received response for Camera permission request.");
|
Log.i(TAG, "Received response for Camera permission request.");
|
||||||
|
|
||||||
// Check if the only required permission has been granted
|
// Check if the only required permission has been granted
|
||||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
// Camera permission has been granted, preview can be displayed
|
// Camera permission has been granted, preview can be displayed
|
||||||
Log.i(TAG, "CAMERA permission has now been granted. Showing preview.");
|
Log.i(TAG, "CAMERA permission has now been granted. Showing preview.");
|
||||||
Toast.makeText(this, R.string.permision_available_camera, Toast.LENGTH_SHORT)
|
Snackbar.make(mLayout, R.string.permision_available_camera,
|
||||||
.show();
|
Snackbar.LENGTH_SHORT).show();
|
||||||
} else {
|
} else {
|
||||||
Log.i(TAG, "CAMERA permission was NOT granted.");
|
Log.i(TAG, "CAMERA permission was NOT granted.");
|
||||||
Toast.makeText(this, R.string.permissions_not_granted, Toast.LENGTH_SHORT).show();
|
Snackbar.make(mLayout, R.string.permissions_not_granted,
|
||||||
|
Snackbar.LENGTH_SHORT).show();
|
||||||
|
|
||||||
}
|
}
|
||||||
// END_INCLUDE(permission_result)
|
// END_INCLUDE(permission_result)
|
||||||
@@ -217,11 +290,14 @@ public class MainActivity extends SampleActivityBase {
|
|||||||
// checked.
|
// checked.
|
||||||
if (PermissionUtil.verifyPermissions(grantResults)) {
|
if (PermissionUtil.verifyPermissions(grantResults)) {
|
||||||
// All required permissions have been granted, display contacts fragment.
|
// All required permissions have been granted, display contacts fragment.
|
||||||
Toast.makeText(this, R.string.permision_available_contacts, Toast.LENGTH_SHORT)
|
Snackbar.make(mLayout, R.string.permision_available_contacts,
|
||||||
|
Snackbar.LENGTH_SHORT)
|
||||||
.show();
|
.show();
|
||||||
} else {
|
} else {
|
||||||
Log.i(TAG, "Contacts permissions were NOT granted.");
|
Log.i(TAG, "Contacts permissions were NOT granted.");
|
||||||
Toast.makeText(this, R.string.permissions_not_granted, Toast.LENGTH_SHORT).show();
|
Snackbar.make(mLayout, R.string.permissions_not_granted,
|
||||||
|
Snackbar.LENGTH_SHORT)
|
||||||
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -291,6 +367,7 @@ public class MainActivity extends SampleActivityBase {
|
|||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_main);
|
setContentView(R.layout.activity_main);
|
||||||
|
mLayout = findViewById(R.id.sample_main_layout);
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ package com.example.android.system.runtimepermissions;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Build;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class that wraps access to the runtime permissions API in M and provides basic helper
|
* Utility class that wraps access to the runtime permissions API in M and provides basic helper
|
||||||
@@ -33,6 +32,11 @@ public abstract class PermissionUtil {
|
|||||||
* @see Activity#onRequestPermissionsResult(int, String[], int[])
|
* @see Activity#onRequestPermissionsResult(int, String[], int[])
|
||||||
*/
|
*/
|
||||||
public static boolean verifyPermissions(int[] grantResults) {
|
public static boolean verifyPermissions(int[] grantResults) {
|
||||||
|
// At least one result must be checked.
|
||||||
|
if(grantResults.length < 1){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Verify that each required permission has been granted, otherwise return false.
|
// Verify that each required permission has been granted, otherwise return false.
|
||||||
for (int result : grantResults) {
|
for (int result : grantResults) {
|
||||||
if (result != PackageManager.PERMISSION_GRANTED) {
|
if (result != PackageManager.PERMISSION_GRANTED) {
|
||||||
@@ -42,43 +46,4 @@ public abstract class PermissionUtil {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the Activity has access to all given permissions.
|
|
||||||
* Always returns true on platforms below M.
|
|
||||||
*
|
|
||||||
* @see Activity#checkSelfPermission(String)
|
|
||||||
*/
|
|
||||||
public static boolean hasSelfPermission(Activity activity, String[] permissions) {
|
|
||||||
// Below Android M all permissions are granted at install time and are already available.
|
|
||||||
if (!isMNC()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that all required permissions have been granted
|
|
||||||
for (String permission : permissions) {
|
|
||||||
if (activity.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the Activity has access to a given permission.
|
|
||||||
* Always returns true on platforms below M.
|
|
||||||
*
|
|
||||||
* @see Activity#checkSelfPermission(String)
|
|
||||||
*/
|
|
||||||
public static boolean hasSelfPermission(Activity activity, String permission) {
|
|
||||||
// Below Android M all permissions are granted at install time and are already available.
|
|
||||||
if (!isMNC()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return activity.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isMNC() {
|
|
||||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.example.android.system.runtimepermissions;
|
package com.example.android.system.runtimepermissions;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
@@ -32,16 +33,16 @@ public class RuntimePermissionsFragment extends Fragment {
|
|||||||
View root = inflater.inflate(R.layout.fragment_main, null);
|
View root = inflater.inflate(R.layout.fragment_main, null);
|
||||||
|
|
||||||
// BEGIN_INCLUDE(m_only_permission)
|
// BEGIN_INCLUDE(m_only_permission)
|
||||||
if (!PermissionUtil.isMNC()) {
|
if (Build.VERSION.SDK_INT < 23) {
|
||||||
/*
|
/*
|
||||||
The contacts permissions have been declared in the AndroidManifest for Android M only.
|
The contacts permissions have been declared in the AndroidManifest for Android M and
|
||||||
They are not available on older platforms, so we are hiding the button to access the
|
above only. They are not available on older platforms, so we are hiding the button to
|
||||||
contacts database.
|
access the contacts database.
|
||||||
This shows how new runtime-only permissions can be added, that do not apply to older
|
This shows how new runtime-only permissions can be added, that do not apply to older
|
||||||
platform versions. This can be useful for automated updates where additional
|
platform versions. This can be useful for automated updates where additional
|
||||||
permissions might prompt the user on upgrade.
|
permissions might prompt the user on upgrade.
|
||||||
*/
|
*/
|
||||||
root.findViewById(R.id.button_camera).setVisibility(View.GONE);
|
root.findViewById(R.id.button_contacts).setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
// END_INCLUDE(m_only_permission)
|
// END_INCLUDE(m_only_permission)
|
||||||
|
|
||||||
|
|||||||
@@ -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 common.activities;
|
||||||
|
|
||||||
|
import com.example.android.common.logger.Log;
|
||||||
|
import com.example.android.common.logger.LogWrapper;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base launcher activity, to handle most of the common plumbing for samples.
|
||||||
|
*/
|
||||||
|
public class SampleActivityBase extends AppCompatActivity {
|
||||||
|
|
||||||
|
public static final String TAG = "SampleActivityBase";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
initializeLogging();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set up targets to receive log data */
|
||||||
|
public void initializeLogging() {
|
||||||
|
// Using Log, front-end to the logging chain, emulates android.util.log method signatures.
|
||||||
|
// Wraps Android's native log framework
|
||||||
|
LogWrapper logWrapper = new LogWrapper();
|
||||||
|
Log.setLogNode(logWrapper);
|
||||||
|
|
||||||
|
Log.i(TAG, "Ready");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@ sample.group=System
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|
||||||
This sample shows runtime permissions available in the Android M and above.
|
|
||||||
This sample shows a basic implementation for requesting permissions at runtime. Click the button to request the Camera permission and open a full-screen camera preview.
|
This sample shows a basic implementation for requesting permissions at runtime. Click the button to request the Camera permission and open a full-screen camera preview.
|
||||||
Note: The "RuntimePermissions" sample provides a more complete overview over the runtime permission features available.
|
Note: The "RuntimePermissions" sample provides a more complete overview over the runtime permission features available.
|
||||||
|
|
||||||
|
|||||||
@@ -14,27 +14,28 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout android:id="@+id/main_layout"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_height="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:paddingLeft="@dimen/horizontal_page_margin"
|
android:layout_height="match_parent"
|
||||||
android:paddingRight="@dimen/horizontal_page_margin"
|
android:orientation="vertical"
|
||||||
android:paddingTop="@dimen/vertical_page_margin"
|
android:paddingBottom="@dimen/vertical_page_margin"
|
||||||
android:paddingBottom="@dimen/vertical_page_margin"
|
android:paddingLeft="@dimen/horizontal_page_margin"
|
||||||
android:orientation="vertical"
|
android:paddingRight="@dimen/horizontal_page_margin"
|
||||||
tools:context=".MainActivity">
|
android:paddingTop="@dimen/vertical_page_margin"
|
||||||
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:text="@string/intro"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_marginBottom="@dimen/horizontal_page_margin"
|
||||||
android:layout_marginBottom="@dimen/horizontal_page_margin"/>
|
android:text="@string/intro" />
|
||||||
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/button_open_camera"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:text="Open Camera Preview"
|
android:layout_height="wrap_content"
|
||||||
android:id="@+id/button_open_camera"/>
|
android:text="Open Camera Preview" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
<![CDATA[
|
<![CDATA[
|
||||||
|
|
||||||
|
|
||||||
This sample shows runtime permissions available in the Android M and above.
|
|
||||||
This sample shows a basic implementation for requesting permissions at runtime. Click the button to request the Camera permission and open a full-screen camera preview.
|
This sample shows a basic implementation for requesting permissions at runtime. Click the button to request the Camera permission and open a full-screen camera preview.
|
||||||
Note: The "RuntimePermissions" sample provides a more complete overview over the runtime permission features available.
|
Note: The "RuntimePermissions" sample provides a more complete overview over the runtime permission features available.
|
||||||
|
|
||||||
|
|||||||
@@ -20,13 +20,15 @@ import com.example.android.basicpermissions.camera.CameraPreviewActivity;
|
|||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.design.widget.Snackbar;
|
||||||
|
import android.support.v4.app.ActivityCompat;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launcher Activity that demonstrates the use of runtime permissions for Android M.
|
* Launcher Activity that demonstrates the use of runtime permissions for Android M.
|
||||||
@@ -36,22 +38,32 @@ import android.widget.Toast;
|
|||||||
* the permission has been granted.
|
* the permission has been granted.
|
||||||
* <p>
|
* <p>
|
||||||
* First, the status of the Camera permission is checked using {@link
|
* First, the status of the Camera permission is checked using {@link
|
||||||
* Activity#checkSelfPermission(String)}.
|
* ActivityCompat#checkSelfPermission(Context, String)}
|
||||||
* If it has not been granted ({@link PackageManager#PERMISSION_GRANTED}), it is requested by
|
* If it has not been granted ({@link PackageManager#PERMISSION_GRANTED}), it is requested by
|
||||||
* calling
|
* calling
|
||||||
* {@link Activity#requestPermissions(String[], int)}. The result of the request is returned in
|
* {@link ActivityCompat#requestPermissions(Activity, String[], int)}. The result of the request is
|
||||||
* {@link Activity#onRequestPermissionsResult(int, String[], int[])}, which starts {@link
|
* returned to the
|
||||||
* CameraPreviewActivity}
|
* {@link android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback}, which starts
|
||||||
* if the permission has been granted.
|
* {@link
|
||||||
|
* CameraPreviewActivity} if the permission has been granted.
|
||||||
|
* <p>
|
||||||
|
* Note that there is no need to check the API level, the support library
|
||||||
|
* already takes care of this. Similar helper methods for permissions are also available in
|
||||||
|
* ({@link ActivityCompat},
|
||||||
|
* {@link android.support.v4.content.ContextCompat} and {@link android.support.v4.app.Fragment}).
|
||||||
*/
|
*/
|
||||||
public class MainActivity extends Activity {
|
public class MainActivity extends AppCompatActivity
|
||||||
|
implements ActivityCompat.OnRequestPermissionsResultCallback {
|
||||||
|
|
||||||
private static final int PERMISSION_REQUEST_CAMERA = 0;
|
private static final int PERMISSION_REQUEST_CAMERA = 0;
|
||||||
|
|
||||||
|
private View mLayout;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_main);
|
setContentView(R.layout.activity_main);
|
||||||
|
mLayout = findViewById(R.id.main_layout);
|
||||||
|
|
||||||
// Register a listener for the 'Show Camera Preview' button.
|
// Register a listener for the 'Show Camera Preview' button.
|
||||||
Button b = (Button) findViewById(R.id.button_open_camera);
|
Button b = (Button) findViewById(R.id.button_open_camera);
|
||||||
@@ -69,15 +81,16 @@ public class MainActivity extends Activity {
|
|||||||
// BEGIN_INCLUDE(onRequestPermissionsResult)
|
// BEGIN_INCLUDE(onRequestPermissionsResult)
|
||||||
if (requestCode == PERMISSION_REQUEST_CAMERA) {
|
if (requestCode == PERMISSION_REQUEST_CAMERA) {
|
||||||
// Request for camera permission.
|
// Request for camera permission.
|
||||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
// Permission has been granted. Start camera preview Activity.
|
// Permission has been granted. Start camera preview Activity.
|
||||||
Toast.makeText(this, "Camera permission was granted. Starting preview.",
|
Snackbar.make(mLayout, "Camera permission was granted. Starting preview.",
|
||||||
Toast.LENGTH_SHORT)
|
Snackbar.LENGTH_SHORT)
|
||||||
.show();
|
.show();
|
||||||
startCamera();
|
startCamera();
|
||||||
} else {
|
} else {
|
||||||
// Permission request was denied.
|
// Permission request was denied.
|
||||||
Toast.makeText(this, "Camera permission request was denied.", Toast.LENGTH_SHORT)
|
Snackbar.make(mLayout, "Camera permission request was denied.",
|
||||||
|
Snackbar.LENGTH_SHORT)
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,51 +99,57 @@ public class MainActivity extends Activity {
|
|||||||
|
|
||||||
private void showCameraPreview() {
|
private void showCameraPreview() {
|
||||||
// BEGIN_INCLUDE(startCamera)
|
// BEGIN_INCLUDE(startCamera)
|
||||||
if (!isMNC()) {
|
|
||||||
// Below Android M there is no need to check for runtime permissions
|
|
||||||
Toast.makeText(this,
|
|
||||||
"Requested permissions are granted at install time below M and are always "
|
|
||||||
+ "available at runtime.",
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
startCamera();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the Camera permission has been granted
|
// Check if the Camera permission has been granted
|
||||||
if (checkSelfPermission(Manifest.permission.CAMERA)
|
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
|
||||||
!= PackageManager.PERMISSION_GRANTED) {
|
== PackageManager.PERMISSION_GRANTED) {
|
||||||
// Permission has not been granted and must be requested.
|
|
||||||
|
|
||||||
if (shouldShowRequestPermissionRationale(
|
|
||||||
Manifest.permission.CAMERA)) {
|
|
||||||
// Provide an additional rationale to the user if the permission was not granted
|
|
||||||
// and the user would benefit from additional context for the use of the permission.
|
|
||||||
Toast.makeText(this, "Camera access is required to display a camera preview.",
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
Toast.makeText(this,
|
|
||||||
"Permission is not available. Requesting camera permission.",
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
|
|
||||||
// Request the permission. The result will be received in onRequestPermissionResult()
|
|
||||||
requestPermissions(new String[]{Manifest.permission.CAMERA},
|
|
||||||
PERMISSION_REQUEST_CAMERA);
|
|
||||||
} else {
|
|
||||||
// Permission is already available, start camera preview
|
// Permission is already available, start camera preview
|
||||||
startCamera();
|
Snackbar.make(mLayout,
|
||||||
Toast.makeText(this,
|
|
||||||
"Camera permission is available. Starting preview.",
|
"Camera permission is available. Starting preview.",
|
||||||
Toast.LENGTH_SHORT).show();
|
Snackbar.LENGTH_SHORT).show();
|
||||||
|
startCamera();
|
||||||
|
} else {
|
||||||
|
// Permission is missing and must be requested.
|
||||||
|
requestCameraPermission();
|
||||||
}
|
}
|
||||||
// END_INCLUDE(startCamera)
|
// END_INCLUDE(startCamera)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests the {@link android.Manifest.permission#CAMERA} permission.
|
||||||
|
* If an additional rationale should be displayed, the user has to launch the request from
|
||||||
|
* a SnackBar that includes additional information.
|
||||||
|
*/
|
||||||
|
private void requestCameraPermission() {
|
||||||
|
// Permission has not been granted and must be requested.
|
||||||
|
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
|
||||||
|
Manifest.permission.CAMERA)) {
|
||||||
|
// Provide an additional rationale to the user if the permission was not granted
|
||||||
|
// and the user would benefit from additional context for the use of the permission.
|
||||||
|
// Display a SnackBar with a button to request the missing permission.
|
||||||
|
Snackbar.make(mLayout, "Camera access is required to display the camera preview.",
|
||||||
|
Snackbar.LENGTH_INDEFINITE).setAction("OK", new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
// Request the permission
|
||||||
|
ActivityCompat.requestPermissions(MainActivity.this,
|
||||||
|
new String[]{Manifest.permission.CAMERA},
|
||||||
|
PERMISSION_REQUEST_CAMERA);
|
||||||
|
}
|
||||||
|
}).show();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Snackbar.make(mLayout,
|
||||||
|
"Permission is not available. Requesting camera permission.",
|
||||||
|
Snackbar.LENGTH_SHORT).show();
|
||||||
|
// Request the permission. The result will be received in onRequestPermissionResult().
|
||||||
|
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA},
|
||||||
|
PERMISSION_REQUEST_CAMERA);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void startCamera() {
|
private void startCamera() {
|
||||||
Intent intent = new Intent(this, CameraPreviewActivity.class);
|
Intent intent = new Intent(this, CameraPreviewActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isMNC() {
|
|
||||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||