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;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
@@ -29,7 +31,7 @@ public class GestureListener extends GestureDetector.SimpleOnGestureListener {
|
||||
@Override
|
||||
public boolean onSingleTapUp(MotionEvent e) {
|
||||
// Up motion completing a single tap occurred.
|
||||
Log.i(TAG, "Single Tap Up");
|
||||
Log.i(TAG, "Single Tap Up" + getTouchType(e));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -37,14 +39,14 @@ public class GestureListener extends GestureDetector.SimpleOnGestureListener {
|
||||
public void onLongPress(MotionEvent e) {
|
||||
// Touch has been long enough to indicate a long press.
|
||||
// Does not indicate motion is complete yet (no up event necessarily)
|
||||
Log.i(TAG, "Long Press");
|
||||
Log.i(TAG, "Long Press" + getTouchType(e));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
|
||||
float distanceY) {
|
||||
// User attempted to scroll
|
||||
Log.i(TAG, "Scroll");
|
||||
Log.i(TAG, "Scroll" + getTouchType(e1));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -52,27 +54,27 @@ public class GestureListener extends GestureDetector.SimpleOnGestureListener {
|
||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
|
||||
float velocityY) {
|
||||
// Fling event occurred. Notification of this one happens after an "up" event.
|
||||
Log.i(TAG, "Fling");
|
||||
Log.i(TAG, "Fling" + getTouchType(e1));
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShowPress(MotionEvent e) {
|
||||
// User performed a down event, and hasn't moved yet.
|
||||
Log.i(TAG, "Show Press");
|
||||
Log.i(TAG, "Show Press" + getTouchType(e));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDown(MotionEvent e) {
|
||||
// "Down" event - User touched the screen.
|
||||
Log.i(TAG, "Down");
|
||||
Log.i(TAG, "Down" + getTouchType(e));
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDoubleTap(MotionEvent e) {
|
||||
// User tapped the screen twice.
|
||||
Log.i(TAG, "Double tap");
|
||||
Log.i(TAG, "Double tap" + getTouchType(e));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -81,7 +83,7 @@ public class GestureListener extends GestureDetector.SimpleOnGestureListener {
|
||||
// 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
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -89,8 +91,84 @@ public class GestureListener extends GestureDetector.SimpleOnGestureListener {
|
||||
public boolean onSingleTapConfirmed(MotionEvent e) {
|
||||
// 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.
|
||||
Log.i(TAG, "Single tap confirmed");
|
||||
Log.i(TAG, "Single tap confirmed" + getTouchType(e));
|
||||
return false;
|
||||
}
|
||||
// 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
|
||||
|
||||
@@ -17,23 +17,32 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.android.bluetoothadvertisements"
|
||||
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"/>
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
|
||||
<application android:allowBackup="true"
|
||||
android:label="@string/app_name"
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<activity android:name=".MainActivity"
|
||||
android:label="@string/app_name">
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme" >
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</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>
|
||||
|
||||
</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="scan_start_toast">Scanning for</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>
|
||||
@@ -16,11 +16,11 @@
|
||||
|
||||
package com.example.android.bluetoothadvertisements;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.le.AdvertiseCallback;
|
||||
import android.bluetooth.le.AdvertiseData;
|
||||
import android.bluetooth.le.AdvertiseSettings;
|
||||
import android.bluetooth.le.BluetoothLeAdvertiser;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -32,67 +32,121 @@ import android.widget.Toast;
|
||||
/**
|
||||
* Allows user to start & stop Bluetooth LE Advertising of their device.
|
||||
*/
|
||||
public class AdvertiserFragment extends Fragment {
|
||||
|
||||
private BluetoothAdapter mBluetoothAdapter;
|
||||
|
||||
private BluetoothLeAdvertiser mBluetoothLeAdvertiser;
|
||||
|
||||
private AdvertiseCallback mAdvertiseCallback;
|
||||
public class AdvertiserFragment extends Fragment implements View.OnClickListener {
|
||||
|
||||
/**
|
||||
* Lets user toggle BLE Advertising.
|
||||
*/
|
||||
private Switch mSwitch;
|
||||
|
||||
/**
|
||||
* Must be called after object creation by MainActivity.
|
||||
*
|
||||
* @param btAdapter the local BluetoothAdapter
|
||||
* 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
|
||||
* is on-screen, so it's defined and registered in code instead of the Manifest.
|
||||
*/
|
||||
public void setBluetoothAdapter(BluetoothAdapter btAdapter) {
|
||||
this.mBluetoothAdapter = btAdapter;
|
||||
mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
|
||||
}
|
||||
private BroadcastReceiver advertisingFailureReceiver;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle 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
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
Bundle savedInstanceState) {
|
||||
|
||||
View view = inflater.inflate(R.layout.fragment_advertiser, container, false);
|
||||
|
||||
mSwitch = (Switch) view.findViewById(R.id.advertise_switch);
|
||||
mSwitch.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
onSwitchClicked(v);
|
||||
}
|
||||
});
|
||||
mSwitch.setOnClickListener(this);
|
||||
|
||||
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
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if(mAdvertiseCallback != null){
|
||||
stopAdvertising();
|
||||
if (AdvertiserService.running) {
|
||||
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.
|
||||
*
|
||||
* @param view is the Switch View object
|
||||
*/
|
||||
public void onSwitchClicked(View view) {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// Is the toggle on?
|
||||
boolean on = ((Switch) view).isChecked();
|
||||
boolean on = ((Switch) v).isChecked();
|
||||
|
||||
if (on) {
|
||||
startAdvertising();
|
||||
@@ -102,105 +156,20 @@ public class AdvertiserFragment extends Fragment {
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts BLE Advertising.
|
||||
* Starts BLE Advertising by starting {@code AdvertiserService}.
|
||||
*/
|
||||
private void startAdvertising() {
|
||||
|
||||
mAdvertiseCallback = new SampleAdvertiseCallback();
|
||||
|
||||
if (mBluetoothLeAdvertiser != null) {
|
||||
mBluetoothLeAdvertiser.startAdvertising(buildAdvertiseSettings(), buildAdvertiseData(),
|
||||
mAdvertiseCallback);
|
||||
} else {
|
||||
mSwitch.setChecked(false);
|
||||
Toast.makeText(getActivity(), getString(R.string.bt_null), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
Context c = getActivity();
|
||||
c.startService(getServiceIntent(c));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops BLE Advertising.
|
||||
* Stops BLE Advertising by stopping {@code AdvertiserService}.
|
||||
*/
|
||||
private void stopAdvertising() {
|
||||
|
||||
if (mBluetoothLeAdvertiser != null) {
|
||||
|
||||
mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback);
|
||||
mAdvertiseCallback = null;
|
||||
|
||||
} else {
|
||||
mSwitch.setChecked(false);
|
||||
Toast.makeText(getActivity(), getString(R.string.bt_null), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
Context c = getActivity();
|
||||
c.stopService(getServiceIntent(c));
|
||||
mSwitch.setChecked(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
setTitle(R.string.activity_main_title);
|
||||
|
||||
if (savedInstanceState == null ) {
|
||||
if (savedInstanceState == null) {
|
||||
|
||||
mBluetoothAdapter = ((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE))
|
||||
.getAdapter();
|
||||
@@ -112,11 +112,11 @@ public class MainActivity extends FragmentActivity {
|
||||
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||
|
||||
ScannerFragment scannerFragment = new ScannerFragment();
|
||||
// Fragments can't access system services directly, so pass it the BluetoothAdapter
|
||||
scannerFragment.setBluetoothAdapter(mBluetoothAdapter);
|
||||
transaction.replace(R.id.scanner_fragment_container, scannerFragment);
|
||||
|
||||
AdvertiserFragment advertiserFragment = new AdvertiserFragment();
|
||||
advertiserFragment.setBluetoothAdapter(mBluetoothAdapter);
|
||||
transaction.replace(R.id.advertiser_fragment_container, advertiserFragment);
|
||||
|
||||
transaction.commit();
|
||||
|
||||
@@ -75,7 +75,11 @@ public class ScanResultAdapter extends BaseAdapter {
|
||||
|
||||
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());
|
||||
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
|
||||
// default theme, so generate it from getActivity() and pass it in separately.
|
||||
mAdapter = new ScanResultAdapter(getActivity().getApplicationContext(),
|
||||
LayoutInflater.from(getActivity()));
|
||||
LayoutInflater.from(getActivity()));
|
||||
mHandler = new Handler();
|
||||
|
||||
}
|
||||
@@ -180,6 +180,7 @@ public class ScannerFragment extends ListFragment {
|
||||
List<ScanFilter> scanFilters = new ArrayList<>();
|
||||
|
||||
ScanFilter.Builder builder = new ScanFilter.Builder();
|
||||
// Comment out the below line to see all BLE devices around you
|
||||
builder.setServiceUuid(Constants.Service_UUID);
|
||||
scanFilters.add(builder.build());
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.example.android.fingerprintdialog;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.DialogFragment;
|
||||
import android.content.SharedPreferences;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
@@ -54,6 +55,7 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment
|
||||
|
||||
private FingerprintManager.CryptoObject mCryptoObject;
|
||||
private FingerprintUiHelper mFingerprintUiHelper;
|
||||
private MainActivity mActivity;
|
||||
|
||||
@Inject FingerprintUiHelper.FingerprintUiHelperBuilder mFingerprintUiHelperBuilder;
|
||||
@Inject InputMethodManager mInputMethodManager;
|
||||
@@ -135,6 +137,12 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment
|
||||
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.
|
||||
*/
|
||||
@@ -167,7 +175,6 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment
|
||||
if (!checkPassword(mPassword.getText().toString())) {
|
||||
return;
|
||||
}
|
||||
MainActivity activity = ((MainActivity) getActivity());
|
||||
if (mStage == Stage.NEW_FINGERPRINT_ENROLLED) {
|
||||
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
||||
editor.putBoolean(getString(R.string.use_fingerprint_to_authenticate_key),
|
||||
@@ -176,12 +183,12 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment
|
||||
|
||||
if (mUseFingerprintFutureCheckBox.isChecked()) {
|
||||
// Re-create the key so that fingerprints including new ones are validated.
|
||||
activity.createKey();
|
||||
mActivity.createKey();
|
||||
mStage = Stage.FINGERPRINT;
|
||||
}
|
||||
}
|
||||
mPassword.setText("");
|
||||
((MainActivity) getActivity()).onPurchased(false /* without Fingerprint */);
|
||||
mActivity.onPurchased(false /* without Fingerprint */);
|
||||
dismiss();
|
||||
}
|
||||
|
||||
@@ -238,7 +245,7 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment
|
||||
public void onAuthenticated() {
|
||||
// Callback from FingerprintUiHelper. Let the activity know that authentication was
|
||||
// successful.
|
||||
((MainActivity) getActivity()).onPurchased(true /* withFingerprint */);
|
||||
mActivity.onPurchased(true /* withFingerprint */);
|
||||
dismiss();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,9 @@ sample.group=System
|
||||
|
||||
<p>
|
||||
|
||||
This sample shows runtime permissions available in the Android M and above.
|
||||
Display the log to follow the execution.
|
||||
If executed on an Android M device, an additional option to access contacts is shown.
|
||||
This sample shows runtime permissions available in Android M and above.
|
||||
Display the log on screen to follow the execution.
|
||||
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>
|
||||
|
||||
@@ -21,9 +21,10 @@
|
||||
<![CDATA[
|
||||
|
||||
|
||||
This sample shows runtime permissions available in the Android M and above.
|
||||
Display the log to follow the execution.
|
||||
If executed on an Android M device, an additional option to access contacts is shown.
|
||||
This sample shows runtime permissions available in Android M and above.
|
||||
Display the log on screen to follow the execution.
|
||||
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"?>
|
||||
<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_none">No contacts stored on device.</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="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_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>
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
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.LogFragment;
|
||||
import com.example.android.common.logger.LogWrapper;
|
||||
@@ -26,15 +25,20 @@ import com.example.android.system.runtimepermissions.contacts.ContactsFragment;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
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.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ViewAnimator;
|
||||
|
||||
import common.activities.SampleActivityBase;
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -46,15 +50,18 @@ import android.widget.ViewAnimator;
|
||||
* android.Manifest.permission#WRITE_CONTACTS})) are requested when the 'Show and Add Contacts'
|
||||
* button is
|
||||
* clicked to display the first contact in the contacts database and to add a dummy contact
|
||||
* directly
|
||||
* to it. First, permissions are checked if they have already been granted through {@link
|
||||
* android.app.Activity#checkSelfPermission(String)} (wrapped in {@link
|
||||
* PermissionUtil#hasSelfPermission(Activity, String)} and {@link PermissionUtil#hasSelfPermission(Activity,
|
||||
* String[])} for compatibility). If permissions have not been granted, they are requested through
|
||||
* {@link Activity#requestPermissions(String[], int)} and the return value checked in {@link
|
||||
* Activity#onRequestPermissionsResult(int, String[], int[])}.
|
||||
* directly to it. Permissions are verified and requested through compat helpers in the support v4
|
||||
* library, in this Activity using {@link ActivityCompat}.
|
||||
* First, permissions are checked if they have already been granted through {@link
|
||||
* ActivityCompat#checkSelfPermission(Context, String)}.
|
||||
* If permissions have not been granted, they are requested through
|
||||
* {@link ActivityCompat#requestPermissions(Activity, String[], int)} and the return value checked
|
||||
* in
|
||||
* a callback to the {@link android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback}
|
||||
* interface.
|
||||
* <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
|
||||
* have been denied previously.
|
||||
* <p>
|
||||
@@ -73,7 +80,8 @@ import android.widget.ViewAnimator;
|
||||
* <p>
|
||||
* (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";
|
||||
|
||||
@@ -96,6 +104,10 @@ public class MainActivity extends SampleActivityBase {
|
||||
// Whether the Log Fragment is currently shown.
|
||||
private boolean mLogShown;
|
||||
|
||||
/**
|
||||
* Root of the layout of this Activity.
|
||||
*/
|
||||
private View mLayout;
|
||||
|
||||
/**
|
||||
* 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.");
|
||||
// BEGIN_INCLUDE(camera_permission)
|
||||
// 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.
|
||||
Log.i(TAG,
|
||||
"CAMERA permission has already been granted. Displaying camera preview.");
|
||||
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)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Callback is defined in resource layout definition.
|
||||
*/
|
||||
public void showContacts(View v) {
|
||||
Log.i(TAG, "Show contacts button pressed. Checking permissions.");
|
||||
|
||||
// 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,
|
||||
"Contact permissions have already been granted. Displaying contact details.");
|
||||
// Contact permissions have been granted. Show the contacts fragment.
|
||||
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
|
||||
// and the user would benefit from additional context for the use of the permission.
|
||||
if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
|
||||
Log.i(TAG,
|
||||
"Displaying contacts permission rationale to provide additional context.");
|
||||
Toast.makeText(this, R.string.permission_contacts_rationale, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
// For example, if the request has been denied previously.
|
||||
Log.i(TAG,
|
||||
"Displaying contacts permission rationale to provide additional context.");
|
||||
|
||||
// contact permissions has not been granted (read and write contacts). Request them.
|
||||
requestPermissions(PERMISSIONS_CONTACT, REQUEST_CONTACTS);
|
||||
// Display a SnackBar with an explanation and a button to trigger the request.
|
||||
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
|
||||
* permission has been granted.
|
||||
@@ -189,8 +261,8 @@ public class MainActivity extends SampleActivityBase {
|
||||
* Callback received when a permissions request has been completed.
|
||||
*/
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions,
|
||||
int[] grantResults) {
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
|
||||
@NonNull int[] grantResults) {
|
||||
|
||||
if (requestCode == REQUEST_CAMERA) {
|
||||
// BEGIN_INCLUDE(permission_result)
|
||||
@@ -198,14 +270,15 @@ public class MainActivity extends SampleActivityBase {
|
||||
Log.i(TAG, "Received response for Camera permission request.");
|
||||
|
||||
// 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
|
||||
Log.i(TAG, "CAMERA permission has now been granted. Showing preview.");
|
||||
Toast.makeText(this, R.string.permision_available_camera, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
Snackbar.make(mLayout, R.string.permision_available_camera,
|
||||
Snackbar.LENGTH_SHORT).show();
|
||||
} else {
|
||||
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)
|
||||
@@ -217,11 +290,14 @@ public class MainActivity extends SampleActivityBase {
|
||||
// checked.
|
||||
if (PermissionUtil.verifyPermissions(grantResults)) {
|
||||
// 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();
|
||||
} else {
|
||||
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 {
|
||||
@@ -291,6 +367,7 @@ public class MainActivity extends SampleActivityBase {
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
mLayout = findViewById(R.id.sample_main_layout);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||
|
||||
@@ -18,7 +18,6 @@ package com.example.android.system.runtimepermissions;
|
||||
|
||||
import android.app.Activity;
|
||||
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
|
||||
@@ -33,6 +32,11 @@ public abstract class PermissionUtil {
|
||||
* @see Activity#onRequestPermissionsResult(int, String[], int[])
|
||||
*/
|
||||
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.
|
||||
for (int result : grantResults) {
|
||||
if (result != PackageManager.PERMISSION_GRANTED) {
|
||||
@@ -42,43 +46,4 @@ public abstract class PermissionUtil {
|
||||
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;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
@@ -32,16 +33,16 @@ public class RuntimePermissionsFragment extends Fragment {
|
||||
View root = inflater.inflate(R.layout.fragment_main, null);
|
||||
|
||||
// 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.
|
||||
They are not available on older platforms, so we are hiding the button to access the
|
||||
contacts database.
|
||||
The contacts permissions have been declared in the AndroidManifest for Android M and
|
||||
above only. They are not available on older platforms, so we are hiding the button to
|
||||
access the contacts database.
|
||||
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
|
||||
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)
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
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.
|
||||
Note: The "RuntimePermissions" sample provides a more complete overview over the runtime permission features available.
|
||||
|
||||
|
||||
@@ -14,27 +14,28 @@
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingLeft="@dimen/horizontal_page_margin"
|
||||
android:paddingRight="@dimen/horizontal_page_margin"
|
||||
android:paddingTop="@dimen/vertical_page_margin"
|
||||
android:paddingBottom="@dimen/vertical_page_margin"
|
||||
android:orientation="vertical"
|
||||
tools:context=".MainActivity">
|
||||
<LinearLayout android:id="@+id/main_layout"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="@dimen/vertical_page_margin"
|
||||
android:paddingLeft="@dimen/horizontal_page_margin"
|
||||
android:paddingRight="@dimen/horizontal_page_margin"
|
||||
android:paddingTop="@dimen/vertical_page_margin"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<TextView
|
||||
android:text="@string/intro"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/horizontal_page_margin"/>
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/horizontal_page_margin"
|
||||
android:text="@string/intro" />
|
||||
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Open Camera Preview"
|
||||
android:id="@+id/button_open_camera"/>
|
||||
android:id="@+id/button_open_camera"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Open Camera Preview" />
|
||||
</LinearLayout>
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
<![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.
|
||||
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.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
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.widget.Button;
|
||||
import android.widget.Toast;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* <p>
|
||||
* 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
|
||||
* calling
|
||||
* {@link Activity#requestPermissions(String[], int)}. The result of the request is returned in
|
||||
* {@link Activity#onRequestPermissionsResult(int, String[], int[])}, which starts {@link
|
||||
* CameraPreviewActivity}
|
||||
* if the permission has been granted.
|
||||
* {@link ActivityCompat#requestPermissions(Activity, String[], int)}. The result of the request is
|
||||
* returned to the
|
||||
* {@link android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback}, which starts
|
||||
* {@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 View mLayout;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
mLayout = findViewById(R.id.main_layout);
|
||||
|
||||
// Register a listener for the 'Show Camera Preview' button.
|
||||
Button b = (Button) findViewById(R.id.button_open_camera);
|
||||
@@ -69,15 +81,16 @@ public class MainActivity extends Activity {
|
||||
// BEGIN_INCLUDE(onRequestPermissionsResult)
|
||||
if (requestCode == PERMISSION_REQUEST_CAMERA) {
|
||||
// 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.
|
||||
Toast.makeText(this, "Camera permission was granted. Starting preview.",
|
||||
Toast.LENGTH_SHORT)
|
||||
Snackbar.make(mLayout, "Camera permission was granted. Starting preview.",
|
||||
Snackbar.LENGTH_SHORT)
|
||||
.show();
|
||||
startCamera();
|
||||
} else {
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
@@ -86,51 +99,57 @@ public class MainActivity extends Activity {
|
||||
|
||||
private void showCameraPreview() {
|
||||
// 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
|
||||
if (checkSelfPermission(Manifest.permission.CAMERA)
|
||||
!= 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 {
|
||||
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
|
||||
== PackageManager.PERMISSION_GRANTED) {
|
||||
// Permission is already available, start camera preview
|
||||
startCamera();
|
||||
Toast.makeText(this,
|
||||
Snackbar.make(mLayout,
|
||||
"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)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
Intent intent = new Intent(this, CameraPreviewActivity.class);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
public static boolean isMNC() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
|
||||
}
|
||||
}
|
||||
|
||||