Sync mnc-dev sample prebuilts

Syncing to //developers/samples/android commmit 2be5f5ca32.

Change-Id: Ia08c63655bd122c7fbb0cc3a50eec95d8edcb3f1
This commit is contained in:
Trevor Johns
2015-08-17 09:37:47 -07:00
parent a56a634166
commit c8e94bf583
28 changed files with 734 additions and 323 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -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;
}
} }

View File

@@ -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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@@ -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>

View File

@@ -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.
}
}
}

View File

@@ -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);
}
}

View File

@@ -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();

View File

@@ -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()));

View File

@@ -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());

View File

@@ -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();
} }

View File

@@ -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>

View File

@@ -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.
]]> ]]>

View File

@@ -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>

View File

@@ -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();

View File

@@ -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;
}
} }

View File

@@ -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)

View File

@@ -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");
}
}

View File

@@ -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.

View File

@@ -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>

View File

@@ -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.

View File

@@ -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;
}
} }