diff --git a/samples/browseable/BasicGestureDetect/res/drawable-hdpi/ic_launcher.png b/samples/browseable/BasicGestureDetect/res/drawable-hdpi/ic_launcher.png index b1efaf4b2..4252db138 100644 Binary files a/samples/browseable/BasicGestureDetect/res/drawable-hdpi/ic_launcher.png and b/samples/browseable/BasicGestureDetect/res/drawable-hdpi/ic_launcher.png differ diff --git a/samples/browseable/BasicGestureDetect/res/drawable-mdpi/ic_launcher.png b/samples/browseable/BasicGestureDetect/res/drawable-mdpi/ic_launcher.png index f5f9244f2..969456ee0 100644 Binary files a/samples/browseable/BasicGestureDetect/res/drawable-mdpi/ic_launcher.png and b/samples/browseable/BasicGestureDetect/res/drawable-mdpi/ic_launcher.png differ diff --git a/samples/browseable/BasicGestureDetect/res/drawable-xhdpi/ic_launcher.png b/samples/browseable/BasicGestureDetect/res/drawable-xhdpi/ic_launcher.png index 5d07b3f06..608151afd 100644 Binary files a/samples/browseable/BasicGestureDetect/res/drawable-xhdpi/ic_launcher.png and b/samples/browseable/BasicGestureDetect/res/drawable-xhdpi/ic_launcher.png differ diff --git a/samples/browseable/BasicGestureDetect/res/drawable-xxhdpi/ic_launcher.png b/samples/browseable/BasicGestureDetect/res/drawable-xxhdpi/ic_launcher.png index 6ef21e1f4..23ffa9a08 100644 Binary files a/samples/browseable/BasicGestureDetect/res/drawable-xxhdpi/ic_launcher.png and b/samples/browseable/BasicGestureDetect/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/samples/browseable/BasicGestureDetect/src/com.example.android.basicgesturedetect/GestureListener.java b/samples/browseable/BasicGestureDetect/src/com.example.android.basicgesturedetect/GestureListener.java index 2e2921d46..19b588a66 100644 --- a/samples/browseable/BasicGestureDetect/src/com.example.android.basicgesturedetect/GestureListener.java +++ b/samples/browseable/BasicGestureDetect/src/com.example.android.basicgesturedetect/GestureListener.java @@ -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; + } + } diff --git a/samples/browseable/BluetoothAdvertisements/AndroidManifest.xml b/samples/browseable/BluetoothAdvertisements/AndroidManifest.xml index 48084fc97..cd2a65e59 100644 --- a/samples/browseable/BluetoothAdvertisements/AndroidManifest.xml +++ b/samples/browseable/BluetoothAdvertisements/AndroidManifest.xml @@ -1,4 +1,4 @@ - + + + diff --git a/samples/browseable/BluetoothAdvertisements/res/drawable-hdpi/ic_launcher.png b/samples/browseable/BluetoothAdvertisements/res/drawable-hdpi/ic_launcher.png index b1efaf4b2..48d646544 100644 Binary files a/samples/browseable/BluetoothAdvertisements/res/drawable-hdpi/ic_launcher.png and b/samples/browseable/BluetoothAdvertisements/res/drawable-hdpi/ic_launcher.png differ diff --git a/samples/browseable/BluetoothAdvertisements/res/drawable-mdpi/ic_launcher.png b/samples/browseable/BluetoothAdvertisements/res/drawable-mdpi/ic_launcher.png index f5f9244f2..42b3b1edf 100644 Binary files a/samples/browseable/BluetoothAdvertisements/res/drawable-mdpi/ic_launcher.png and b/samples/browseable/BluetoothAdvertisements/res/drawable-mdpi/ic_launcher.png differ diff --git a/samples/browseable/BluetoothAdvertisements/res/drawable-xhdpi/ic_launcher.png b/samples/browseable/BluetoothAdvertisements/res/drawable-xhdpi/ic_launcher.png index 5d07b3f06..13663e8be 100644 Binary files a/samples/browseable/BluetoothAdvertisements/res/drawable-xhdpi/ic_launcher.png and b/samples/browseable/BluetoothAdvertisements/res/drawable-xhdpi/ic_launcher.png differ diff --git a/samples/browseable/BluetoothAdvertisements/res/drawable-xxhdpi/ic_launcher.png b/samples/browseable/BluetoothAdvertisements/res/drawable-xxhdpi/ic_launcher.png index 6ef21e1f4..5e84f3a58 100644 Binary files a/samples/browseable/BluetoothAdvertisements/res/drawable-xxhdpi/ic_launcher.png and b/samples/browseable/BluetoothAdvertisements/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/samples/browseable/BluetoothAdvertisements/res/values/strings.xml b/samples/browseable/BluetoothAdvertisements/res/values/strings.xml index 197178d00..927f3b603 100644 --- a/samples/browseable/BluetoothAdvertisements/res/values/strings.xml +++ b/samples/browseable/BluetoothAdvertisements/res/values/strings.xml @@ -26,5 +26,8 @@ seconds. Scanning for Scanning already started. + (no name) + unknown error + Advertising stopped due to timeout. \ No newline at end of file diff --git a/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/AdvertiserFragment.java b/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/AdvertiserFragment.java index f3645fc29..c97b90435 100644 --- a/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/AdvertiserFragment.java +++ b/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/AdvertiserFragment.java @@ -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. - } - } - -} +} \ No newline at end of file diff --git a/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/AdvertiserService.java b/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/AdvertiserService.java new file mode 100644 index 000000000..0cc3ff03e --- /dev/null +++ b/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/AdvertiserService.java @@ -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); + } + +} \ No newline at end of file diff --git a/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/MainActivity.java b/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/MainActivity.java index 871935d9d..7ea389160 100644 --- a/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/MainActivity.java +++ b/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/MainActivity.java @@ -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(); diff --git a/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/ScanResultAdapter.java b/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/ScanResultAdapter.java index f3c141d36..5a9b95483 100644 --- a/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/ScanResultAdapter.java +++ b/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/ScanResultAdapter.java @@ -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())); diff --git a/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/ScannerFragment.java b/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/ScannerFragment.java index ebb1ad085..4f5c2aa59 100644 --- a/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/ScannerFragment.java +++ b/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/ScannerFragment.java @@ -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 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()); diff --git a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintAuthenticationDialogFragment.java b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintAuthenticationDialogFragment.java index 8909f752f..b17ebb0ba 100644 --- a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintAuthenticationDialogFragment.java +++ b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintAuthenticationDialogFragment.java @@ -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(); } diff --git a/samples/browseable/RuntimePermissions/_index.jd b/samples/browseable/RuntimePermissions/_index.jd index 75c6fee3e..a78f3b0cd 100644 --- a/samples/browseable/RuntimePermissions/_index.jd +++ b/samples/browseable/RuntimePermissions/_index.jd @@ -5,8 +5,9 @@ sample.group=System

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

diff --git a/samples/browseable/RuntimePermissions/res/values/base-strings.xml b/samples/browseable/RuntimePermissions/res/values/base-strings.xml index 58d75f931..33c535ac6 100644 --- a/samples/browseable/RuntimePermissions/res/values/base-strings.xml +++ b/samples/browseable/RuntimePermissions/res/values/base-strings.xml @@ -21,9 +21,10 @@ diff --git a/samples/browseable/RuntimePermissions/res/values/strings.xml b/samples/browseable/RuntimePermissions/res/values/strings.xml index 82d7b719d..edd2c1532 100644 --- a/samples/browseable/RuntimePermissions/res/values/strings.xml +++ b/samples/browseable/RuntimePermissions/res/values/strings.xml @@ -1,5 +1,6 @@ + OK Total number of contacts: %1$,d\nFirst contact:%2$s No contacts stored on device. Contacts not loaded. @@ -17,5 +18,5 @@ Contacts Permissions have been granted. Contacts screen can now be opened. Permissions were not granted. Camera permission is needed to show the camera preview. - Contacts permissions are needed to demonstrate access to the contacts database. + Contacts permissions are needed to demonstrate access. diff --git a/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/MainActivity.java b/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/MainActivity.java index 5f38bad8d..7abc538cf 100644 --- a/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/MainActivity.java +++ b/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/MainActivity.java @@ -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. *

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

@@ -73,7 +80,8 @@ import android.widget.ViewAnimator; *

* (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(); diff --git a/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/PermissionUtil.java b/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/PermissionUtil.java index d0742ead9..b9be6258e 100644 --- a/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/PermissionUtil.java +++ b/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/PermissionUtil.java @@ -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; - } } diff --git a/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/RuntimePermissionsFragment.java b/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/RuntimePermissionsFragment.java index b35bfebc0..d38195f57 100644 --- a/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/RuntimePermissionsFragment.java +++ b/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/RuntimePermissionsFragment.java @@ -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) diff --git a/samples/browseable/RuntimePermissions/src/common.activities/SampleActivityBase.java b/samples/browseable/RuntimePermissions/src/common.activities/SampleActivityBase.java new file mode 100644 index 000000000..ac3928ef0 --- /dev/null +++ b/samples/browseable/RuntimePermissions/src/common.activities/SampleActivityBase.java @@ -0,0 +1,53 @@ +/* +* Copyright 2013 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package 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"); + } +} diff --git a/samples/browseable/RuntimePermissionsBasic/_index.jd b/samples/browseable/RuntimePermissionsBasic/_index.jd index c4b5d4ca8..4fe611862 100644 --- a/samples/browseable/RuntimePermissionsBasic/_index.jd +++ b/samples/browseable/RuntimePermissionsBasic/_index.jd @@ -5,7 +5,6 @@ sample.group=System

- 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. diff --git a/samples/browseable/RuntimePermissionsBasic/res/layout/activity_main.xml b/samples/browseable/RuntimePermissionsBasic/res/layout/activity_main.xml index c3bf99c5f..146b8b1e7 100644 --- a/samples/browseable/RuntimePermissionsBasic/res/layout/activity_main.xml +++ b/samples/browseable/RuntimePermissionsBasic/res/layout/activity_main.xml @@ -14,27 +14,28 @@ limitations under the License. --> - + + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/horizontal_page_margin" + android:text="@string/intro" />