diff options
author | Trevor Johns <trevorjohns@google.com> | 2015-08-20 16:10:16 +0000 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2015-08-20 16:10:16 +0000 |
commit | e00d9e659c233f74c69f5f3a4520ef28a6e35bde (patch) | |
tree | 151b24ba99d1773c92781ece1240551e8aa2d420 | |
parent | 7f59e62cf6207497dc3c205097e49b451087e231 (diff) | |
parent | d95a687e5a9820f9859997dc733da9a18afb57c2 (diff) | |
download | android_development-e00d9e659c233f74c69f5f3a4520ef28a6e35bde.tar.gz android_development-e00d9e659c233f74c69f5f3a4520ef28a6e35bde.tar.bz2 android_development-e00d9e659c233f74c69f5f3a4520ef28a6e35bde.zip |
am d95a687e: Sync sample prebuilts for mnc-dev
* commit 'd95a687e5a9820f9859997dc733da9a18afb57c2':
Sync sample prebuilts for mnc-dev
73 files changed, 1151 insertions, 440 deletions
diff --git a/samples/browseable/AgendaData/Application/src/com.example.android.wearable.agendadata/MainActivity.java b/samples/browseable/AgendaData/Application/src/com.example.android.wearable.agendadata/MainActivity.java index 8e4063d8c..34e327b3a 100644 --- a/samples/browseable/AgendaData/Application/src/com.example.android.wearable.agendadata/MainActivity.java +++ b/samples/browseable/AgendaData/Application/src/com.example.android.wearable.agendadata/MainActivity.java @@ -33,7 +33,6 @@ import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks; import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener; import com.google.android.gms.common.api.ResultCallback; -import com.google.android.gms.common.data.FreezableUtils; import com.google.android.gms.wearable.DataApi; import com.google.android.gms.wearable.DataItem; import com.google.android.gms.wearable.DataItemBuffer; @@ -96,28 +95,29 @@ public class MainActivity extends Activity implements NodeApi.NodeListener, Conn .setResultCallback(new ResultCallback<DataItemBuffer>() { @Override public void onResult(DataItemBuffer result) { - if (result.getStatus().isSuccess()) { - deleteDataItems(result); - } else { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "onDeleteEventsClicked(): failed to get Data Items"); + try { + if (result.getStatus().isSuccess()) { + deleteDataItems(result); + } else { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG,"onDeleteEventsClicked(): failed to get Data " + + "Items"); + + } } + } finally { + result.release(); } - result.close(); } }); } else { Log.e(TAG, "Failed to delete data items" - + " - Client disconnected from Google Play Services"); + + " - Client disconnected from Google Play Services"); } } - private void deleteDataItems(DataItemBuffer dataItems) { + private void deleteDataItems(final DataItemBuffer dataItemList) { if (mGoogleApiClient.isConnected()) { - // Store the DataItem URIs in a List and close the buffer. Then use these URIs - // to delete the DataItems. - final List<DataItem> dataItemList = FreezableUtils.freezeIterable(dataItems); - dataItems.close(); for (final DataItem dataItem : dataItemList) { final Uri dataItemUri = dataItem.getUri(); // In a real calendar application, this might delete the corresponding calendar diff --git a/samples/browseable/AgendaData/Wearable/src/com.example.android.wearable.agendadata/HomeListenerService.java b/samples/browseable/AgendaData/Wearable/src/com.example.android.wearable.agendadata/HomeListenerService.java index ef47977e3..0cbda71e8 100644 --- a/samples/browseable/AgendaData/Wearable/src/com.example.android.wearable.agendadata/HomeListenerService.java +++ b/samples/browseable/AgendaData/Wearable/src/com.example.android.wearable.agendadata/HomeListenerService.java @@ -75,7 +75,6 @@ public class HomeListenerService extends WearableListenerService { UpdateNotificationForDataItem(event.getDataItem()); } } - dataEvents.close(); } @Override 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 f8daefb04..f3645fc29 100644 --- a/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/AdvertiserFragment.java +++ b/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/AdvertiserFragment.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.example.android.bluetoothadvertisements; import android.bluetooth.BluetoothAdapter; diff --git a/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/Constants.java b/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/Constants.java index d3941e2ab..793ac9214 100644 --- a/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/Constants.java +++ b/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/Constants.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.example.android.bluetoothadvertisements; import android.os.ParcelUuid; 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 f0044a3e8..871935d9d 100644 --- a/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/MainActivity.java +++ b/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/MainActivity.java @@ -1,18 +1,18 @@ /* -* 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. -*/ + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.example.android.bluetoothadvertisements; 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 0f905ea7a..f3c141d36 100644 --- a/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/ScanResultAdapter.java +++ b/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/ScanResultAdapter.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.example.android.bluetoothadvertisements; import android.bluetooth.le.ScanResult; 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 b9ad4d966..ebb1ad085 100644 --- a/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/ScannerFragment.java +++ b/samples/browseable/BluetoothAdvertisements/src/com.example.android.bluetoothadvertisements/ScannerFragment.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.example.android.bluetoothadvertisements; import android.bluetooth.BluetoothAdapter; diff --git a/samples/browseable/Camera2Basic/AndroidManifest.xml b/samples/browseable/Camera2Basic/AndroidManifest.xml index 87d9af131..5b0b5b041 100644 --- a/samples/browseable/Camera2Basic/AndroidManifest.xml +++ b/samples/browseable/Camera2Basic/AndroidManifest.xml @@ -15,16 +15,14 @@ limitations under the License. --> - - <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.example.android.camera2basic" - android:versionCode="1" - android:versionName="1.0"> + package="com.example.android.camera2basic"> - <!-- Min/target SDK versions (<uses-sdk>) managed by build.gradle --> <uses-permission android:name="android.permission.CAMERA" /> + <uses-feature android:name="android.hardware.camera" /> + <uses-feature android:name="android.hardware.camera.autofocus" /> + <application android:allowBackup="true" android:label="@string/app_name" android:icon="@drawable/ic_launcher" diff --git a/samples/browseable/Camera2Basic/res/layout-land/fragment_camera2_basic.xml b/samples/browseable/Camera2Basic/res/layout-land/fragment_camera2_basic.xml index 3a3e2ff36..8b2e1ad1d 100644 --- a/samples/browseable/Camera2Basic/res/layout-land/fragment_camera2_basic.xml +++ b/samples/browseable/Camera2Basic/res/layout-land/fragment_camera2_basic.xml @@ -26,14 +26,14 @@ android:layout_alignParentTop="true" /> <FrameLayout + android:id="@+id/control" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentEnd="true" android:layout_alignParentTop="true" - android:layout_below="@id/texture" android:layout_toRightOf="@id/texture" - android:background="#4285f4" + android:background="@color/control_background" android:orientation="horizontal"> <Button diff --git a/samples/browseable/Camera2Basic/res/layout/fragment_camera2_basic.xml b/samples/browseable/Camera2Basic/res/layout/fragment_camera2_basic.xml index 7d05ab3f4..9c6dc6505 100644 --- a/samples/browseable/Camera2Basic/res/layout/fragment_camera2_basic.xml +++ b/samples/browseable/Camera2Basic/res/layout/fragment_camera2_basic.xml @@ -25,12 +25,12 @@ android:layout_alignParentTop="true" /> <FrameLayout + android:id="@+id/control" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="112dp" android:layout_alignParentBottom="true" android:layout_alignParentStart="true" - android:layout_below="@id/texture" - android:background="#4285f4"> + android:background="@color/control_background"> <Button android:id="@+id/picture" diff --git a/samples/browseable/Camera2Basic/res/values/colors.xml b/samples/browseable/Camera2Basic/res/values/colors.xml new file mode 100644 index 000000000..4b75d2b2b --- /dev/null +++ b/samples/browseable/Camera2Basic/res/values/colors.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2015 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. +--> +<resources> + <color name="control_background">#cc4285f4</color> +</resources> diff --git a/samples/browseable/Camera2Basic/src/com.example.android.camera2basic/Camera2BasicFragment.java b/samples/browseable/Camera2Basic/src/com.example.android.camera2basic/Camera2BasicFragment.java index 2414ed657..020ca14d3 100644 --- a/samples/browseable/Camera2Basic/src/com.example.android.camera2basic/Camera2BasicFragment.java +++ b/samples/browseable/Camera2Basic/src/com.example.android.camera2basic/Camera2BasicFragment.java @@ -55,7 +55,6 @@ import android.view.ViewGroup; import android.widget.Toast; import java.io.File; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; @@ -264,14 +263,16 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen break; } case STATE_WAITING_LOCK: { - int afState = result.get(CaptureResult.CONTROL_AF_STATE); - if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState || + Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); + if (afState == null) { + captureStillPicture(); + } else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState || CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) { // CONTROL_AE_STATE can be null on some devices Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) { - mState = STATE_WAITING_NON_PRECAPTURE; + mState = STATE_PICTURE_TAKEN; captureStillPicture(); } else { runPrecaptureSequence(); @@ -636,6 +637,8 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen (float) viewWidth / mPreviewSize.getWidth()); matrix.postScale(scale, scale, centerX, centerY); matrix.postRotate(90 * (rotation - 2), centerX, centerY); + } else if (Surface.ROTATION_180 == rotation) { + matrix.postRotate(180, centerX, centerY); } mTextureView.setTransform(matrix); } @@ -657,7 +660,7 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen CameraMetadata.CONTROL_AF_TRIGGER_START); // Tell #mCaptureCallback to wait for the lock. mState = STATE_WAITING_LOCK; - mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback, + mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); @@ -794,8 +797,6 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen try { output = new FileOutputStream(mFile); output.write(bytes); - } catch (FileNotFoundException e) { - e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { diff --git a/samples/browseable/ConfirmCredential/src/com.example.android.confirmcredential/MainActivity.java b/samples/browseable/ConfirmCredential/src/com.example.android.confirmcredential/MainActivity.java index b8cf6e111..c19875a3c 100644 --- a/samples/browseable/ConfirmCredential/src/com.example.android.confirmcredential/MainActivity.java +++ b/samples/browseable/ConfirmCredential/src/com.example.android.confirmcredential/MainActivity.java @@ -95,7 +95,7 @@ public class MainActivity extends Activity { * Tries to encrypt some data with the generated key in {@link #createKey} which is * only works if the user has just authenticated via device credentials. */ - private void tryEncrypt() { + private boolean tryEncrypt() { try { KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); keyStore.load(null); @@ -111,15 +111,18 @@ public class MainActivity extends Activity { // If the user has recently authenticated, you will reach here. showAlreadyAuthenticated(); + return true; } catch (UserNotAuthenticatedException e) { // User is not authenticated, let's authenticate with device credentials. showAuthenticationScreen(); + return false; } catch (KeyPermanentlyInvalidatedException e) { // This happens if the lock screen has been disabled or reset after the key was // generated after the key was generated. Toast.makeText(this, "Keys are invalidated after created. Retry the purchase\n" + e.getMessage(), Toast.LENGTH_LONG).show(); + return false; } catch (BadPaddingException | IllegalBlockSizeException | KeyStoreException | CertificateException | UnrecoverableKeyException | IOException | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) { @@ -172,7 +175,9 @@ public class MainActivity extends Activity { if (requestCode == REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) { // Challenge completed, proceed with using cipher if (resultCode == RESULT_OK) { - showPurchaseConfirmation(); + if (tryEncrypt()) { + showPurchaseConfirmation(); + } } else { // The user canceled or didn’t complete the lock screen // operation. Go to error/cancellation flow. diff --git a/samples/browseable/DataLayer/Application/src/com.example.android.wearable.datalayer/MainActivity.java b/samples/browseable/DataLayer/Application/src/com.example.android.wearable.datalayer/MainActivity.java index e6e4aa2cf..1c67c0e03 100644 --- a/samples/browseable/DataLayer/Application/src/com.example.android.wearable.datalayer/MainActivity.java +++ b/samples/browseable/DataLayer/Application/src/com.example.android.wearable.datalayer/MainActivity.java @@ -216,8 +216,8 @@ public class MainActivity extends Activity implements DataApi.DataListener, @Override //DataListener public void onDataChanged(DataEventBuffer dataEvents) { LOGD(TAG, "onDataChanged: " + dataEvents); + // Need to freeze the dataEvents so they will exist later on the UI thread final List<DataEvent> events = FreezableUtils.freezeIterable(dataEvents); - dataEvents.close(); runOnUiThread(new Runnable() { @Override public void run() { diff --git a/samples/browseable/DataLayer/Wearable/src/com.example.android.wearable.datalayer/DataLayerListenerService.java b/samples/browseable/DataLayer/Wearable/src/com.example.android.wearable.datalayer/DataLayerListenerService.java index 34d3ca390..67dcef94f 100644 --- a/samples/browseable/DataLayer/Wearable/src/com.example.android.wearable.datalayer/DataLayerListenerService.java +++ b/samples/browseable/DataLayer/Wearable/src/com.example.android.wearable.datalayer/DataLayerListenerService.java @@ -22,7 +22,6 @@ import android.util.Log; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.data.FreezableUtils; import com.google.android.gms.wearable.DataEvent; import com.google.android.gms.wearable.DataEventBuffer; import com.google.android.gms.wearable.MessageEvent; @@ -59,8 +58,6 @@ public class DataLayerListenerService extends WearableListenerService { @Override public void onDataChanged(DataEventBuffer dataEvents) { LOGD(TAG, "onDataChanged: " + dataEvents); - final List<DataEvent> events = FreezableUtils.freezeIterable(dataEvents); - dataEvents.close(); if (!mGoogleApiClient.isConnected() || !mGoogleApiClient.isConnecting()) { ConnectionResult connectionResult = mGoogleApiClient .blockingConnect(30, TimeUnit.SECONDS); @@ -72,7 +69,7 @@ public class DataLayerListenerService extends WearableListenerService { } // Loop through the events and send a message back to the node that created the data item. - for (DataEvent event : events) { + for (DataEvent event : dataEvents) { Uri uri = event.getDataItem().getUri(); String path = uri.getPath(); if (COUNT_PATH.equals(path)) { diff --git a/samples/browseable/DataLayer/Wearable/src/com.example.android.wearable.datalayer/MainActivity.java b/samples/browseable/DataLayer/Wearable/src/com.example.android.wearable.datalayer/MainActivity.java index 7d2a28c8c..678e428ca 100644 --- a/samples/browseable/DataLayer/Wearable/src/com.example.android.wearable.datalayer/MainActivity.java +++ b/samples/browseable/DataLayer/Wearable/src/com.example.android.wearable.datalayer/MainActivity.java @@ -42,7 +42,6 @@ import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks; import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener; import com.google.android.gms.common.api.ResultCallback; -import com.google.android.gms.common.data.FreezableUtils; import com.google.android.gms.wearable.Asset; import com.google.android.gms.wearable.CapabilityApi; import com.google.android.gms.wearable.CapabilityInfo; @@ -151,9 +150,7 @@ public class MainActivity extends Activity implements ConnectionCallbacks, public void onDataChanged(DataEventBuffer dataEvents) { LOGD(TAG, "onDataChanged(): " + dataEvents); - final List<DataEvent> events = FreezableUtils.freezeIterable(dataEvents); - dataEvents.close(); - for (DataEvent event : events) { + for (DataEvent event : dataEvents) { if (event.getType() == DataEvent.TYPE_CHANGED) { String path = event.getDataItem().getUri().getPath(); if (DataLayerListenerService.IMAGE_PATH.equals(path)) { diff --git a/samples/browseable/ElizaChat/src/com.example.android.wearable.elizachat/MainActivity.java b/samples/browseable/ElizaChat/src/com.example.android.wearable.elizachat/MainActivity.java index 982e3def5..2e132a8fc 100644 --- a/samples/browseable/ElizaChat/src/com.example.android.wearable.elizachat/MainActivity.java +++ b/samples/browseable/ElizaChat/src/com.example.android.wearable.elizachat/MainActivity.java @@ -55,11 +55,12 @@ public class MainActivity extends Activity { } }; mHistoryView = (TextView) findViewById(R.id.history); - startResponderService(); + startResponderService(ResponderService.ACTION_INCOMING); } - private void startResponderService() { - Intent serviceIntent = new Intent(ResponderService.ACTION_INCOMING); + private void startResponderService(String action) { + Intent serviceIntent = new Intent(this, ResponderService.class); + serviceIntent.setAction(action); startService(serviceIntent); } @@ -69,9 +70,7 @@ public class MainActivity extends Activity { LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, new IntentFilter(ACTION_NOTIFY)); mHistoryView.setText(""); - Intent serviceIntent = new Intent(ACTION_GET_CONVERSATION); - startService(serviceIntent); - + startResponderService(ACTION_GET_CONVERSATION); } @Override diff --git a/samples/browseable/FindMyPhone/Application/src/com.example.android.wearable.findphone/SoundAlarmListenerService.java b/samples/browseable/FindMyPhone/Application/src/com.example.android.wearable.findphone/SoundAlarmListenerService.java index c89db9d3c..3de750158 100644 --- a/samples/browseable/FindMyPhone/Application/src/com.example.android.wearable.findphone/SoundAlarmListenerService.java +++ b/samples/browseable/FindMyPhone/Application/src/com.example.android.wearable.findphone/SoundAlarmListenerService.java @@ -95,7 +95,6 @@ public class SoundAlarmListenerService extends WearableListenerService { } } } - dataEvents.close(); } } diff --git a/samples/browseable/FindMyPhone/Wearable/src/com.example.android.wearable.findphone/FindPhoneService.java b/samples/browseable/FindMyPhone/Wearable/src/com.example.android.wearable.findphone/FindPhoneService.java index 97204b475..c6c6d67b5 100644 --- a/samples/browseable/FindMyPhone/Wearable/src/com.example.android.wearable.findphone/FindPhoneService.java +++ b/samples/browseable/FindMyPhone/Wearable/src/com.example.android.wearable.findphone/FindPhoneService.java @@ -73,19 +73,22 @@ public class FindPhoneService extends IntentService implements GoogleApiClient.C if (intent.getAction().equals(ACTION_TOGGLE_ALARM)) { // Get current state of the alarm. DataItemBuffer result = Wearable.DataApi.getDataItems(mGoogleApiClient).await(); - if (result.getStatus().isSuccess()) { - if (result.getCount() == 1) { - alarmOn = DataMap.fromByteArray(result.get(0).getData()) - .getBoolean(FIELD_ALARM_ON, false); - } else { - Log.e(TAG, "Unexpected number of DataItems found.\n" - + "\tExpected: 1\n" - + "\tActual: " + result.getCount()); + try { + if (result.getStatus().isSuccess()) { + if (result.getCount() == 1) { + alarmOn = DataMap.fromByteArray(result.get(0).getData()) + .getBoolean(FIELD_ALARM_ON, false); + } else { + Log.e(TAG, "Unexpected number of DataItems found.\n" + + "\tExpected: 1\n" + + "\tActual: " + result.getCount()); + } + } else if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "onHandleIntent: failed to get current alarm state"); } - } else if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "onHandleIntent: failed to get current alarm state"); + } finally { + result.release(); } - result.close(); // Toggle alarm. alarmOn = !alarmOn; // Change notification text based on new value of alarmOn. diff --git a/samples/browseable/FingerprintDialog/AndroidManifest.xml b/samples/browseable/FingerprintDialog/AndroidManifest.xml index 5f754ad46..3b19fbec0 100644 --- a/samples/browseable/FingerprintDialog/AndroidManifest.xml +++ b/samples/browseable/FingerprintDialog/AndroidManifest.xml @@ -35,7 +35,9 @@ <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> - </application> - + <activity + android:name=".SettingsActivity" + android:label="@string/action_settings" /> + </application> </manifest> diff --git a/samples/browseable/FingerprintDialog/res/layout/fingerprint_dialog_backup.xml b/samples/browseable/FingerprintDialog/res/layout/fingerprint_dialog_backup.xml index 0b88e3311..2be05b11b 100644 --- a/samples/browseable/FingerprintDialog/res/layout/fingerprint_dialog_backup.xml +++ b/samples/browseable/FingerprintDialog/res/layout/fingerprint_dialog_backup.xml @@ -16,21 +16,37 @@ --> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/backup_container" - android:layout_width="match_parent" android:layout_height="match_parent" + android:layout_width="match_parent" + android:layout_height="match_parent" android:paddingTop="16dp" android:paddingBottom="8dp"> - <TextView + <FrameLayout + android:id="@+id/description" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textAppearance="@android:style/TextAppearance.Material.Subhead" - android:text="@string/password_description" - android:id="@+id/description" android:layout_alignParentTop="true" android:layout_alignParentStart="true" android:layout_marginStart="24dp" android:layout_marginEnd="24dp" - android:textColor="?android:attr/textColorSecondary"/> + > + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="@android:style/TextAppearance.Material.Subhead" + android:text="@string/password_description" + android:id="@+id/password_description" + android:textColor="?android:attr/textColorSecondary" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="@android:style/TextAppearance.Material.Subhead" + android:text="@string/new_fingerprint_enrolled_description" + android:id="@+id/new_fingerprint_enrolled_description" + android:visibility="gone" + android:textColor="?android:attr/textColorSecondary" /> + </FrameLayout> <EditText android:layout_width="wrap_content" @@ -46,4 +62,17 @@ android:layout_marginEnd="20dp" android:layout_alignParentStart="true" /> + <CheckBox + android:id="@+id/use_fingerprint_in_future_check" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@+id/password" + android:layout_alignParentStart="true" + android:layout_marginTop="16dp" + android:layout_marginStart="20dp" + android:layout_marginEnd="20dp" + android:checked="true" + android:visibility="gone" + android:text="@string/use_fingerprint_in_future" /> + </RelativeLayout>
\ No newline at end of file diff --git a/samples/browseable/FingerprintDialog/res/layout/fingerprint_dialog_content.xml b/samples/browseable/FingerprintDialog/res/layout/fingerprint_dialog_content.xml index b56ccbbce..3929ebae6 100644 --- a/samples/browseable/FingerprintDialog/res/layout/fingerprint_dialog_content.xml +++ b/samples/browseable/FingerprintDialog/res/layout/fingerprint_dialog_content.xml @@ -19,8 +19,8 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="8dp" - android:paddingLeft="24dp" - android:paddingRight="24dp" + android:paddingStart="24dp" + android:paddingEnd="24dp" android:paddingTop="16dp"> <TextView @@ -50,7 +50,7 @@ android:layout_height="wrap_content" android:layout_alignBottom="@+id/fingerprint_icon" android:layout_alignTop="@+id/fingerprint_icon" - android:layout_marginLeft="16dp" + android:layout_marginStart="16dp" android:layout_toEndOf="@+id/fingerprint_icon" android:gravity="center_vertical" android:text="@string/fingerprint_hint" diff --git a/samples/browseable/FingerprintDialog/res/menu/menu_main.xml b/samples/browseable/FingerprintDialog/res/menu/menu_main.xml new file mode 100644 index 000000000..73f5e89a0 --- /dev/null +++ b/samples/browseable/FingerprintDialog/res/menu/menu_main.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2015 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 + --> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity"> + <item android:id="@+id/action_settings" android:title="@string/action_settings" + android:orderInCategory="100" android:showAsAction="never" /> +</menu> diff --git a/samples/browseable/FingerprintDialog/res/values/strings.xml b/samples/browseable/FingerprintDialog/res/values/strings.xml index 8a6ecde75..9f5a6fd14 100644 --- a/samples/browseable/FingerprintDialog/res/values/strings.xml +++ b/samples/browseable/FingerprintDialog/res/values/strings.xml @@ -31,4 +31,8 @@ <string name="item_price">$62.68</string> <string name="item_description">Mesh backpack in white. Black textile trim throughout.</string> <string name="purchase_done">Purchase successful</string> + <string name="new_fingerprint_enrolled_description">A new fingerprint was added to this device, so your password is required.</string> + <string name="use_fingerprint_in_future">Use fingerprint in the future</string> + <string name="use_fingerprint_to_authenticate_title">Use fingerprint to authenticate</string> + <string name="use_fingerprint_to_authenticate_key" >use_fingerprint_to_authenticate_key</string> </resources> diff --git a/samples/browseable/FingerprintDialog/res/xml/preferences.xml b/samples/browseable/FingerprintDialog/res/xml/preferences.xml new file mode 100644 index 000000000..761391d5a --- /dev/null +++ b/samples/browseable/FingerprintDialog/res/xml/preferences.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2015 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 + --> +<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> + <CheckBoxPreference + android:key="@string/use_fingerprint_to_authenticate_key" + android:title="@string/use_fingerprint_to_authenticate_title" + android:persistent="true" + android:defaultValue="true" /> +</PreferenceScreen>
\ No newline at end of file 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 57c00de08..8909f752f 100644 --- a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintAuthenticationDialogFragment.java +++ b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintAuthenticationDialogFragment.java @@ -17,6 +17,7 @@ package com.example.android.fingerprintdialog; import android.app.DialogFragment; +import android.content.SharedPreferences; import android.hardware.fingerprint.FingerprintManager; import android.os.Bundle; import android.view.KeyEvent; @@ -26,6 +27,7 @@ import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.Button; +import android.widget.CheckBox; import android.widget.EditText; import android.widget.ImageView; import android.widget.TextView; @@ -44,6 +46,9 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment private View mFingerprintContent; private View mBackupContent; private EditText mPassword; + private CheckBox mUseFingerprintFutureCheckBox; + private TextView mPasswordDescriptionTextView; + private TextView mNewFingerprintEnrolledTextView; private Stage mStage = Stage.FINGERPRINT; @@ -52,6 +57,7 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment @Inject FingerprintUiHelper.FingerprintUiHelperBuilder mFingerprintUiHelperBuilder; @Inject InputMethodManager mInputMethodManager; + @Inject SharedPreferences mSharedPreferences; @Inject public FingerprintAuthenticationDialogFragment() {} @@ -93,6 +99,11 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment mBackupContent = v.findViewById(R.id.backup_container); mPassword = (EditText) v.findViewById(R.id.password); mPassword.setOnEditorActionListener(this); + mPasswordDescriptionTextView = (TextView) v.findViewById(R.id.password_description); + mUseFingerprintFutureCheckBox = (CheckBox) + v.findViewById(R.id.use_fingerprint_in_future_check); + mNewFingerprintEnrolledTextView = (TextView) + v.findViewById(R.id.new_fingerprint_enrolled_description); mFingerprintUiHelper = mFingerprintUiHelperBuilder.build( (ImageView) v.findViewById(R.id.fingerprint_icon), (TextView) v.findViewById(R.id.fingerprint_status), this); @@ -114,6 +125,10 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment } } + public void setStage(Stage stage) { + mStage = stage; + } + @Override public void onPause() { super.onPause(); @@ -149,12 +164,25 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment * let's the activity know about the result. */ private void verifyPassword() { - if (checkPassword(mPassword.getText().toString())) { - ((MainActivity) getActivity()).onPurchased(false /* without Fingerprint */); - dismiss(); - } else { - // assume the password is always correct. + 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), + mUseFingerprintFutureCheckBox.isChecked()); + editor.apply(); + + if (mUseFingerprintFutureCheckBox.isChecked()) { + // Re-create the key so that fingerprints including new ones are validated. + activity.createKey(); + mStage = Stage.FINGERPRINT; + } + } + mPassword.setText(""); + ((MainActivity) getActivity()).onPurchased(false /* without Fingerprint */); + dismiss(); } /** @@ -181,11 +209,18 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment mFingerprintContent.setVisibility(View.VISIBLE); mBackupContent.setVisibility(View.GONE); break; + case NEW_FINGERPRINT_ENROLLED: + // Intentional fall through case PASSWORD: mCancelButton.setText(R.string.cancel); mSecondDialogButton.setText(R.string.ok); mFingerprintContent.setVisibility(View.GONE); mBackupContent.setVisibility(View.VISIBLE); + if (mStage == Stage.NEW_FINGERPRINT_ENROLLED) { + mPasswordDescriptionTextView.setVisibility(View.GONE); + mNewFingerprintEnrolledTextView.setVisibility(View.VISIBLE); + mUseFingerprintFutureCheckBox.setVisibility(View.VISIBLE); + } break; } } @@ -215,8 +250,9 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment /** * Enumeration to indicate which authentication method the user is trying to authenticate with. */ - private enum Stage { + public enum Stage { FINGERPRINT, + NEW_FINGERPRINT_ENROLLED, PASSWORD } } diff --git a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintModule.java b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintModule.java index 16d5067ea..964e1f6d8 100644 --- a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintModule.java +++ b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintModule.java @@ -18,7 +18,10 @@ package com.example.android.fingerprintdialog; import android.app.KeyguardManager; import android.content.Context; +import android.content.SharedPreferences; import android.hardware.fingerprint.FingerprintManager; +import android.preference.PreferenceManager; +import android.security.keystore.KeyProperties; import android.view.inputmethod.InputMethodManager; import java.security.KeyStore; @@ -75,7 +78,7 @@ public class FingerprintModule { @Provides public KeyGenerator providesKeyGenerator() { try { - return KeyGenerator.getInstance("AES", "AndroidKeyStore"); + return KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); } catch (NoSuchAlgorithmException | NoSuchProviderException e) { throw new RuntimeException("Failed to get an instance of KeyGenerator", e); } @@ -84,7 +87,9 @@ public class FingerprintModule { @Provides public Cipher providesCipher(KeyStore keyStore) { try { - return Cipher.getInstance("AES/CBC/PKCS7Padding"); + return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + + KeyProperties.BLOCK_MODE_CBC + "/" + + KeyProperties.ENCRYPTION_PADDING_PKCS7); } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { throw new RuntimeException("Failed to get an instance of Cipher", e); } @@ -94,4 +99,9 @@ public class FingerprintModule { public InputMethodManager providesInputMethodManager(Context context) { return (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); } + + @Provides + public SharedPreferences providesSharedPreferences(Context context) { + return PreferenceManager.getDefaultSharedPreferences(context); + } } diff --git a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintUiHelper.java b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintUiHelper.java index ab7570ca7..92fcdb1d0 100644 --- a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintUiHelper.java +++ b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintUiHelper.java @@ -82,7 +82,8 @@ public class FingerprintUiHelper extends FingerprintManager.AuthenticationCallba } mCancellationSignal = new CancellationSignal(); mSelfCancelled = false; - mFingerprintManager.authenticate(cryptoObject, mCancellationSignal, this, 0 /* flags */); + mFingerprintManager + .authenticate(cryptoObject, mCancellationSignal, 0 /* flags */, this, null); mIcon.setImageResource(R.drawable.ic_fp_40px); } diff --git a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/MainActivity.java b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/MainActivity.java index 9d09765eb..c954bfa7b 100644 --- a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/MainActivity.java +++ b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/MainActivity.java @@ -19,6 +19,8 @@ package com.example.android.fingerprintdialog; import android.Manifest; import android.app.Activity; import android.app.KeyguardManager; +import android.content.Intent; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.hardware.fingerprint.FingerprintManager; import android.os.Bundle; @@ -27,6 +29,8 @@ import android.security.keystore.KeyPermanentlyInvalidatedException; import android.security.keystore.KeyProperties; import android.util.Base64; import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.TextView; @@ -60,23 +64,29 @@ public class MainActivity extends Activity { /** Alias for our key in the Android Key Store */ private static final String KEY_NAME = "my_key"; + private static final int FINGERPRINT_PERMISSION_REQUEST_CODE = 0; + @Inject KeyguardManager mKeyguardManager; + @Inject FingerprintManager mFingerprintManager; @Inject FingerprintAuthenticationDialogFragment mFragment; @Inject KeyStore mKeyStore; @Inject KeyGenerator mKeyGenerator; @Inject Cipher mCipher; + @Inject SharedPreferences mSharedPreferences; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ((InjectedApplication) getApplication()).inject(this); - requestPermissions(new String[]{Manifest.permission.USE_FINGERPRINT}, 0); + requestPermissions(new String[]{Manifest.permission.USE_FINGERPRINT}, + FINGERPRINT_PERMISSION_REQUEST_CODE); } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] state) { - if (requestCode == 0 && state[0] == PackageManager.PERMISSION_GRANTED) { + if (requestCode == FINGERPRINT_PERMISSION_REQUEST_CODE + && state[0] == PackageManager.PERMISSION_GRANTED) { setContentView(R.layout.activity_main); Button purchaseButton = (Button) findViewById(R.id.purchase_button); if (!mKeyguardManager.isKeyguardSecure()) { @@ -88,35 +98,71 @@ public class MainActivity extends Activity { purchaseButton.setEnabled(false); return; } + if (!mFingerprintManager.hasEnrolledFingerprints()) { + purchaseButton.setEnabled(false); + // This happens when no fingerprints are registered. + Toast.makeText(this, + "Go to 'Settings -> Security -> Fingerprint' and register at least one fingerprint", + Toast.LENGTH_LONG).show(); + return; + } createKey(); + purchaseButton.setEnabled(true); purchaseButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - - // Show the fingerprint dialog. The user has the option to use the fingerprint with - // crypto, or you can fall back to using a server-side verified password. - mFragment.setCryptoObject(new FingerprintManager.CryptoObject(mCipher)); - mFragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG); + findViewById(R.id.confirmation_message).setVisibility(View.GONE); + findViewById(R.id.encrypted_message).setVisibility(View.GONE); + + // Set up the crypto object for later. The object will be authenticated by use + // of the fingerprint. + if (initCipher()) { + + // Show the fingerprint dialog. The user has the option to use the fingerprint with + // crypto, or you can fall back to using a server-side verified password. + mFragment.setCryptoObject(new FingerprintManager.CryptoObject(mCipher)); + boolean useFingerprintPreference = mSharedPreferences + .getBoolean(getString(R.string.use_fingerprint_to_authenticate_key), + true); + if (useFingerprintPreference) { + mFragment.setStage( + FingerprintAuthenticationDialogFragment.Stage.FINGERPRINT); + } else { + mFragment.setStage( + FingerprintAuthenticationDialogFragment.Stage.PASSWORD); + } + mFragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG); + } else { + // This happens if the lock screen has been disabled or or a fingerprint got + // enrolled. Thus show the dialog to authenticate with their password first + // and ask the user if they want to authenticate with fingerprints in the + // future + mFragment.setCryptoObject(new FingerprintManager.CryptoObject(mCipher)); + mFragment.setStage( + FingerprintAuthenticationDialogFragment.Stage.NEW_FINGERPRINT_ENROLLED); + mFragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG); + } } }); - - // Set up the crypto object for later. The object will be authenticated by use - // of the fingerprint. - initCipher(); } } - private void initCipher() { + /** + * Initialize the {@link Cipher} instance with the created key in the {@link #createKey()} + * method. + * + * @return {@code true} if initialization is successful, {@code false} if the lock screen has + * been disabled or reset after the key was generated, or if a fingerprint got enrolled after + * the key was generated. + */ + private boolean initCipher() { try { mKeyStore.load(null); SecretKey key = (SecretKey) mKeyStore.getKey(KEY_NAME, null); mCipher.init(Cipher.ENCRYPT_MODE, key); + return true; } catch (KeyPermanentlyInvalidatedException e) { - // This happens if the lock screen has been disabled or reset after the key was - // generated, or if a fingerprint got enrolled after the key was generated. - Toast.makeText(this, "Keys are invalidated after created. Retry the purchase\n" - + e.getMessage(), - Toast.LENGTH_LONG).show(); + return false; } catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException | NoSuchAlgorithmException | InvalidKeyException e) { throw new RuntimeException("Failed to init Cipher", e); @@ -124,7 +170,6 @@ public class MainActivity extends Activity { } public void onPurchased(boolean withFingerprint) { - findViewById(R.id.purchase_button).setVisibility(View.GONE); if (withFingerprint) { // If the user has authenticated with fingerprint, verify that using cryptography and // then show the confirmation message. @@ -164,7 +209,7 @@ public class MainActivity extends Activity { * Creates a symmetric key in the Android Key Store which can only be used after the user has * authenticated with fingerprint. */ - private void createKey() { + public void createKey() { // The enrolling flow for fingerprint. This is where you ask the user to set up fingerprint // for your flow. Use of keys is necessary if you need to know if the set of // enrolled fingerprints has changed. @@ -187,4 +232,22 @@ public class MainActivity extends Activity { throw new RuntimeException(e); } } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + + if (id == R.id.action_settings) { + Intent intent = new Intent(this, SettingsActivity.class); + startActivity(intent); + return true; + } + return super.onOptionsItemSelected(item); + } } diff --git a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/SettingsActivity.java b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/SettingsActivity.java new file mode 100644 index 000000000..08b391140 --- /dev/null +++ b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/SettingsActivity.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.example.android.fingerprintdialog; + +import android.app.Activity; +import android.os.Bundle; +import android.preference.PreferenceFragment; + +public class SettingsActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Display the fragment as the main content. + getFragmentManager().beginTransaction().replace(android.R.id.content, + new SettingsFragment()).commit(); + } + + /** + * Fragment for settings. + */ + public static class SettingsFragment extends PreferenceFragment { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.preferences); + } + } +} + + diff --git a/samples/browseable/Geofencing/Wearable/src/com.example.android.wearable.geofencing/HomeListenerService.java b/samples/browseable/Geofencing/Wearable/src/com.example.android.wearable.geofencing/HomeListenerService.java index 415fc46aa..251360c9e 100644 --- a/samples/browseable/Geofencing/Wearable/src/com.example.android.wearable.geofencing/HomeListenerService.java +++ b/samples/browseable/Geofencing/Wearable/src/com.example.android.wearable.geofencing/HomeListenerService.java @@ -79,7 +79,6 @@ public class HomeListenerService extends WearableListenerService { postNotificationForGeofenceId(geofenceId, event.getDataItem().getUri()); } } - dataEvents.close(); } /** diff --git a/samples/browseable/HdrViewfinder/AndroidManifest.xml b/samples/browseable/HdrViewfinder/AndroidManifest.xml index 772b7dfdf..63066f6d4 100644 --- a/samples/browseable/HdrViewfinder/AndroidManifest.xml +++ b/samples/browseable/HdrViewfinder/AndroidManifest.xml @@ -20,10 +20,6 @@ android:versionCode="1" android:versionName="1.0"> - <uses-sdk - android:minSdkVersion="21" - android:targetSdkVersion="21"/> - <uses-feature android:name="android.hardware.camera"/> <uses-feature android:name="android.hardware.camera.front" @@ -31,8 +27,6 @@ <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> - <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <application android:allowBackup="true" @@ -43,7 +37,8 @@ <activity android:name=".HdrViewfinderActivity" android:label="@string/app_name" - android:screenOrientation="landscape"> + android:screenOrientation="landscape" + android:theme="@style/Theme.AppCompat.Light"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> diff --git a/samples/browseable/HdrViewfinder/res/layout/main.xml b/samples/browseable/HdrViewfinder/res/layout/main.xml index 7507709e5..6fe56ef0f 100644 --- a/samples/browseable/HdrViewfinder/res/layout/main.xml +++ b/samples/browseable/HdrViewfinder/res/layout/main.xml @@ -15,13 +15,14 @@ limitations under the License. --> <LinearLayout - android:id="@+id/panels" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:custom="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/panels" android:layout_width="match_parent" android:layout_height="match_parent" - android:orientation="horizontal"> + android:orientation="horizontal" + tools:context="com.example.android.hdrviewfinder.HdrViewfinderActivity"> <com.example.android.hdrviewfinder.FixedAspectSurfaceView android:id="@+id/preview" @@ -36,7 +37,8 @@ android:layout_width="0px" android:layout_height="match_parent" android:layout_weight="1" - android:orientation="vertical"> + android:orientation="vertical" + android:layout_margin="5dp"> <Button android:id="@+id/help_button" diff --git a/samples/browseable/HdrViewfinder/res/values/strings.xml b/samples/browseable/HdrViewfinder/res/values/strings.xml index 21838d549..d910ab1a1 100644 --- a/samples/browseable/HdrViewfinder/res/values/strings.xml +++ b/samples/browseable/HdrViewfinder/res/values/strings.xml @@ -50,10 +50,18 @@ <string name="info">Info</string> + <string name="camera_permission_rationale">This sample app requires camera access in order to + demo the API.</string> <string name="camera_no_good">No back-facing sufficiently capable camera available!</string> <string name="camera_disabled">Camera is disabled by device policy</string> <string name="camera_disconnected">Camera was disconnected before it was opened</string> <string name="camera_error">Camera service reported an error</string> <string name="camera_unknown">Unknown camera error: %s</string> + <string name="camera_permission_denied_explanation">You\'ve denied a permission that the app + needs for core functionality. If you selected "don\'t ask again" in the past then + you need to use Settings to re-enable the permission.</string> + + <string name="ok">OK</string> + <string name="settings">Settings</string> </resources> diff --git a/samples/browseable/HdrViewfinder/src/com.example.android.hdrviewfinder/CameraOps.java b/samples/browseable/HdrViewfinder/src/com.example.android.hdrviewfinder/CameraOps.java index 5769760b2..173ef08ee 100644 --- a/samples/browseable/HdrViewfinder/src/com.example.android.hdrviewfinder/CameraOps.java +++ b/samples/browseable/HdrViewfinder/src/com.example.android.hdrviewfinder/CameraOps.java @@ -78,7 +78,7 @@ public class CameraOps { } /** - * Open the first backfacing camera listed by the camera manager. + * Open the first back-facing camera listed by the camera manager. * Displays a dialog if it cannot open a camera. */ public void openCamera(final String cameraId) { diff --git a/samples/browseable/HdrViewfinder/src/com.example.android.hdrviewfinder/HdrViewfinderActivity.java b/samples/browseable/HdrViewfinder/src/com.example.android.hdrviewfinder/HdrViewfinderActivity.java index 79f1bb61e..ca49ea0ba 100644 --- a/samples/browseable/HdrViewfinder/src/com.example.android.hdrviewfinder/HdrViewfinderActivity.java +++ b/samples/browseable/HdrViewfinder/src/com.example.android.hdrviewfinder/HdrViewfinderActivity.java @@ -16,7 +16,9 @@ package com.example.android.hdrviewfinder; -import android.app.Activity; +import android.Manifest; +import android.content.Intent; +import android.content.pm.PackageManager; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; @@ -26,10 +28,16 @@ import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.params.StreamConfigurationMap; +import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Looper; +import android.provider.Settings; import android.renderscript.RenderScript; +import android.support.annotation.NonNull; +import android.support.design.widget.Snackbar; +import android.support.v4.app.ActivityCompat; +import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.util.Size; import android.view.GestureDetector; @@ -76,19 +84,26 @@ import java.util.List; * Android {@link android.view.Surface} class, which allows for zero-copy transport of large * buffers between processes and subsystems.</p> */ -public class HdrViewfinderActivity extends Activity implements +public class HdrViewfinderActivity extends AppCompatActivity implements SurfaceHolder.Callback, CameraOps.ErrorDisplayer, CameraOps.CameraReadyListener { private static final String TAG = "HdrViewfinderDemo"; private static final String FRAGMENT_DIALOG = "dialog"; + private static final int REQUEST_PERMISSIONS_REQUEST_CODE = 34; + /** * View for the camera preview. */ private FixedAspectSurfaceView mPreviewView; /** + * Root view of this activity. + */ + private View rootView; + + /** * This shows the current mode of the app. */ private TextView mModeText; @@ -132,6 +147,8 @@ public class HdrViewfinderActivity extends Activity implements super.onCreate(savedInstanceState); setContentView(R.layout.main); + rootView = findViewById(R.id.panels); + mPreviewView = (FixedAspectSurfaceView) findViewById(R.id.preview); mPreviewView.getHolder().addCallback(this); mPreviewView.setGestureListener(this, mViewListener); @@ -146,23 +163,20 @@ public class HdrViewfinderActivity extends Activity implements mUiHandler = new Handler(Looper.getMainLooper()); - mCameraManager = (CameraManager) getSystemService(CAMERA_SERVICE); - mCameraOps = new CameraOps(mCameraManager, - /*errorDisplayer*/ this, - /*readyListener*/ this, - /*readyHandler*/ mUiHandler); - - mHdrRequests.add(null); - mHdrRequests.add(null); - mRS = RenderScript.create(this); + + // When permissions are revoked the app is restarted so onCreate is sufficient to check for + // permissions core to the Activity's functionality. + if (!checkCameraPermissions()) { + requestCameraPermissions(); + } else { + findAndOpenCamera(); + } } @Override protected void onResume() { super.onResume(); - - findAndOpenCamera(); } @Override @@ -170,7 +184,10 @@ public class HdrViewfinderActivity extends Activity implements super.onPause(); // Wait until camera is closed to ensure the next application can open it - mCameraOps.closeCameraAndWait(); + if (mCameraOps != null) { + mCameraOps.closeCameraAndWait(); + mCameraOps = null; + } } @Override @@ -232,7 +249,9 @@ public class HdrViewfinderActivity extends Activity implements } }; - // Show help dialog + /** + * Show help dialogs. + */ private View.OnClickListener mHelpButtonListener = new View.OnClickListener() { public void onClick(View v) { MessageDialogFragment.newInstance(R.string.help_text) @@ -240,54 +259,176 @@ public class HdrViewfinderActivity extends Activity implements } }; - private void findAndOpenCamera() { + /** + * Return the current state of the camera permissions. + */ + private boolean checkCameraPermissions() { + int permissionState = ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA); + + // Check if the Camera permission is already available. + if (permissionState != PackageManager.PERMISSION_GRANTED) { + // Camera permission has not been granted. + Log.i(TAG, "CAMERA permission has NOT been granted."); + return false; + } else { + // Camera permissions are available. + Log.i(TAG, "CAMERA permission has already been granted."); + return true; + } + } - String errorMessage = "Unknown error"; - boolean foundCamera = false; - try { - // Find first back-facing camera that has necessary capability - String[] cameraIds = mCameraManager.getCameraIdList(); - for (String id : cameraIds) { - CameraCharacteristics info = mCameraManager.getCameraCharacteristics(id); - int facing = info.get(CameraCharacteristics.LENS_FACING); - - int level = info.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); - boolean hasFullLevel - = (level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL); - - int[] capabilities = info.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES); - int syncLatency = info.get(CameraCharacteristics.SYNC_MAX_LATENCY); - boolean hasManualControl = hasCapability(capabilities, - CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR); - boolean hasEnoughCapability = hasManualControl && - syncLatency == CameraCharacteristics.SYNC_MAX_LATENCY_PER_FRAME_CONTROL; - - // All these are guaranteed by - // CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, but checking for only - // the things we care about expands range of devices we can run on - // We want: - // - Back-facing camera - // - Manual sensor control - // - Per-frame synchronization (so that exposure can be changed every frame) - if (facing == CameraCharacteristics.LENS_FACING_BACK && - (hasFullLevel || hasEnoughCapability)) { - // Found suitable camera - get info, open, and set up outputs - mCameraInfo = info; - mCameraOps.openCamera(id); - configureSurfaces(); - foundCamera = true; - break; - } - } - if (!foundCamera) { - errorMessage = getString(R.string.camera_no_good); + /** + * Attempt to initialize the camera. + */ + private void initializeCamera() { + mCameraManager = (CameraManager) getSystemService(CAMERA_SERVICE); + if (mCameraManager != null) { + mCameraOps = new CameraOps(mCameraManager, + /*errorDisplayer*/ this, + /*readyListener*/ this, + /*readyHandler*/ mUiHandler); + + mHdrRequests.add(null); + mHdrRequests.add(null); + } else { + Log.e(TAG, "Couldn't initialize the camera"); + } + } + + private void requestCameraPermissions() { + boolean shouldProvideRationale = + ActivityCompat.shouldShowRequestPermissionRationale(this, + Manifest.permission.CAMERA); + + // Provide an additional rationale to the user. This would happen if the user denied the + // request previously, but didn't check the "Don't ask again" checkbox. + if (shouldProvideRationale) { + Log.i(TAG, "Displaying camera permission rationale to provide additional context."); + Snackbar.make(rootView, R.string.camera_permission_rationale, Snackbar + .LENGTH_INDEFINITE) + .setAction(R.string.ok, new View.OnClickListener() { + @Override + public void onClick(View view) { + // Request Camera permission + ActivityCompat.requestPermissions(HdrViewfinderActivity.this, + new String[]{Manifest.permission.CAMERA}, + REQUEST_PERMISSIONS_REQUEST_CODE); + } + }) + .show(); + } else { + Log.i(TAG, "Requesting camera permission"); + // Request Camera permission. It's possible this can be auto answered if device policy + // sets the permission in a given state or the user denied the permission + // previously and checked "Never ask again". + ActivityCompat.requestPermissions(HdrViewfinderActivity.this, + new String[]{Manifest.permission.CAMERA}, + REQUEST_PERMISSIONS_REQUEST_CODE); + } + } + + /** + * Callback received when a permissions request has been completed. + */ + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { + Log.i(TAG, "onRequestPermissionResult"); + if (requestCode == REQUEST_PERMISSIONS_REQUEST_CODE) { + if (grantResults.length <= 0) { + // If user interaction was interrupted, the permission request is cancelled and you + // receive empty arrays. + Log.i(TAG, "User interaction was cancelled."); + } else if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + // Permission was granted. + findAndOpenCamera(); + } else { + // Permission denied. + + // In this Activity we've chosen to notify the user that they + // have rejected a core permission for the app since it makes the Activity useless. + // We're communicating this message in a Snackbar since this is a sample app, but + // core permissions would typically be best requested during a welcome-screen flow. + + // Additionally, it is important to remember that a permission might have been + // rejected without asking the user for permission (device policy or "Never ask + // again" prompts). Therefore, a user interface affordance is typically implemented + // when permissions are denied. Otherwise, your app could appear unresponsive to + // touches or interactions which have required permissions. + Snackbar.make(rootView, R.string.camera_permission_denied_explanation, Snackbar + .LENGTH_INDEFINITE) + .setAction(R.string.settings, new View.OnClickListener() { + @Override + public void onClick(View view) { + // Build intent that displays the App settings screen. + Intent intent = new Intent(); + intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + Uri uri = Uri.fromParts("package", BuildConfig.APPLICATION_ID, null); + intent.setData(uri); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + }) + .show(); } - } catch (CameraAccessException e) { - errorMessage = getErrorString(e); } + } - if (!foundCamera) { - showErrorDialog(errorMessage); + private void findAndOpenCamera() { + boolean cameraPermissions = checkCameraPermissions(); + if (cameraPermissions) { + String errorMessage = "Unknown error"; + boolean foundCamera = false; + initializeCamera(); + if (cameraPermissions && mCameraOps != null) { + try { + // Find first back-facing camera that has necessary capability. + String[] cameraIds = mCameraManager.getCameraIdList(); + for (String id : cameraIds) { + CameraCharacteristics info = mCameraManager.getCameraCharacteristics(id); + int facing = info.get(CameraCharacteristics.LENS_FACING); + + int level = info.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); + boolean hasFullLevel + = (level + == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL); + + int[] capabilities = info + .get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES); + int syncLatency = info.get(CameraCharacteristics.SYNC_MAX_LATENCY); + boolean hasManualControl = hasCapability(capabilities, + CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR); + boolean hasEnoughCapability = hasManualControl && + syncLatency + == CameraCharacteristics.SYNC_MAX_LATENCY_PER_FRAME_CONTROL; + + // All these are guaranteed by + // CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, but checking + // for only the things we care about expands range of devices we can run on. + // We want: + // - Back-facing camera + // - Manual sensor control + // - Per-frame synchronization (so that exposure can be changed every frame) + if (facing == CameraCharacteristics.LENS_FACING_BACK && + (hasFullLevel || hasEnoughCapability)) { + // Found suitable camera - get info, open, and set up outputs + mCameraInfo = info; + mCameraOps.openCamera(id); + configureSurfaces(); + foundCamera = true; + break; + } + } + if (!foundCamera) { + errorMessage = getString(R.string.camera_no_good); + } + } catch (CameraAccessException e) { + errorMessage = getErrorString(e); + } + if (!foundCamera) { + showErrorDialog(errorMessage); + } + } } } @@ -299,23 +440,25 @@ public class HdrViewfinderActivity extends Activity implements } private void switchRenderMode(int direction) { - mRenderMode = (mRenderMode + direction) % 3; + if (mCameraOps != null) { + mRenderMode = (mRenderMode + direction) % 3; - mModeText.setText(getResources().getStringArray(R.array.mode_label_array)[mRenderMode]); + mModeText.setText(getResources().getStringArray(R.array.mode_label_array)[mRenderMode]); - if (mProcessor != null) { - mProcessor.setRenderMode(mRenderMode); - } - if (mRenderMode == ViewfinderProcessor.MODE_NORMAL) { - mCameraOps.setRepeatingRequest(mPreviewRequest, - mCaptureCallback, mUiHandler); - } else { - setHdrBurst(); + if (mProcessor != null) { + mProcessor.setRenderMode(mRenderMode); + } + if (mRenderMode == ViewfinderProcessor.MODE_NORMAL) { + mCameraOps.setRepeatingRequest(mPreviewRequest, + mCaptureCallback, mUiHandler); + } else { + setHdrBurst(); + } } } /** - * Configure the surfaceview and RS processing + * Configure the surfaceview and RS processing. */ private void configureSurfaces() { // Find a good size for output - largest 16:9 aspect ratio that's less than 720p diff --git a/samples/browseable/MessagingService/AndroidManifest.xml b/samples/browseable/MessagingService/AndroidManifest.xml index f8a5850d1..955f8d4f4 100644 --- a/samples/browseable/MessagingService/AndroidManifest.xml +++ b/samples/browseable/MessagingService/AndroidManifest.xml @@ -37,13 +37,17 @@ <service android:name=".MessagingService"> </service> - <receiver android:name=".MessageReadReceiver"> + <receiver + android:name=".MessageReadReceiver" + android:exported="false"> <intent-filter> <action android:name="com.example.android.messagingservice.ACTION_MESSAGE_READ"/> </intent-filter> </receiver> - <receiver android:name=".MessageReplyReceiver"> + <receiver + android:name=".MessageReplyReceiver" + android:exported="false"> <intent-filter> <action android:name="com.example.android.messagingservice.ACTION_MESSAGE_REPLY"/> </intent-filter> diff --git a/samples/browseable/MessagingService/res/layout-land/fragment_message_me.xml b/samples/browseable/MessagingService/res/layout-land/fragment_message_me.xml index 6f4f88bac..0cfd1cf0d 100644 --- a/samples/browseable/MessagingService/res/layout-land/fragment_message_me.xml +++ b/samples/browseable/MessagingService/res/layout-land/fragment_message_me.xml @@ -21,7 +21,8 @@ android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" - android:paddingTop="@dimen/activity_vertical_margin"> + android:paddingTop="@dimen/activity_vertical_margin" + android:baselineAligned="false"> <LinearLayout android:layout_width="0dp" android:layout_height="match_parent" diff --git a/samples/browseable/MessagingService/res/layout/fragment_message_me.xml b/samples/browseable/MessagingService/res/layout/fragment_message_me.xml index 29a8c441a..404bd4de9 100644 --- a/samples/browseable/MessagingService/res/layout/fragment_message_me.xml +++ b/samples/browseable/MessagingService/res/layout/fragment_message_me.xml @@ -14,7 +14,6 @@ limitations under the License. --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" diff --git a/samples/browseable/MessagingService/res/values/strings.xml b/samples/browseable/MessagingService/res/values/strings.xml index 001b10eed..3f8390dfc 100644 --- a/samples/browseable/MessagingService/res/values/strings.xml +++ b/samples/browseable/MessagingService/res/values/strings.xml @@ -16,8 +16,6 @@ --> <resources> <string name="app_name">Messaging Sample</string> - <string name="action_settings">Settings</string> - <string name="title">Messaging Sample</string> <string name="notification_reply">Reply by Voice</string> <string name="send_2_conversations">Send 2 conversations with 1 message</string> <string name="send_1_conversation">Send 1 conversation with 1 message</string> diff --git a/samples/browseable/MessagingService/src/com.example.android.messagingservice/Conversations.java b/samples/browseable/MessagingService/src/com.example.android.messagingservice/Conversations.java index 7425df499..88ef7aa5f 100644 --- a/samples/browseable/MessagingService/src/com.example.android.messagingservice/Conversations.java +++ b/samples/browseable/MessagingService/src/com.example.android.messagingservice/Conversations.java @@ -17,7 +17,6 @@ package com.example.android.messagingservice; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.ThreadLocalRandom; diff --git a/samples/browseable/MessagingService/src/com.example.android.messagingservice/MessageLogger.java b/samples/browseable/MessagingService/src/com.example.android.messagingservice/MessageLogger.java index d1007b5ad..3459178d0 100644 --- a/samples/browseable/MessagingService/src/com.example.android.messagingservice/MessageLogger.java +++ b/samples/browseable/MessagingService/src/com.example.android.messagingservice/MessageLogger.java @@ -19,6 +19,7 @@ package com.example.android.messagingservice; import android.content.Context; import android.content.SharedPreferences; +import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; @@ -27,13 +28,13 @@ import java.util.Date; * and replies. Don't use this in a real world application. This logger is only * used for displaying the messages in the text view. */ -public class MessageLogger { +class MessageLogger { private static final String PREF_MESSAGE = "MESSAGE_LOGGER"; - private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + private static final DateFormat DATE_FORMAT = SimpleDateFormat.getDateTimeInstance(); + private static final String LINE_BREAKS = "\n\n"; public static final String LOG_KEY = "message_data"; - public static final String LINE_BREAKS = "\n\n"; public static void logMessage(Context context, String message) { SharedPreferences prefs = getPrefs(context); diff --git a/samples/browseable/MessagingService/src/com.example.android.messagingservice/MessageReadReceiver.java b/samples/browseable/MessagingService/src/com.example.android.messagingservice/MessageReadReceiver.java index f28a3a778..63c244f86 100644 --- a/samples/browseable/MessagingService/src/com.example.android.messagingservice/MessageReadReceiver.java +++ b/samples/browseable/MessagingService/src/com.example.android.messagingservice/MessageReadReceiver.java @@ -16,7 +16,6 @@ package com.example.android.messagingservice; -import android.app.NotificationManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -35,8 +34,8 @@ public class MessageReadReceiver extends BroadcastReceiver { if (conversationId != -1) { Log.d(TAG, "Conversation " + conversationId + " was read"); MessageLogger.logMessage(context, "Conversation " + conversationId + " was read."); - NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); - notificationManager.cancel(conversationId); + NotificationManagerCompat.from(context) + .cancel(conversationId); } } } diff --git a/samples/browseable/MessagingService/src/com.example.android.messagingservice/MessagingFragment.java b/samples/browseable/MessagingService/src/com.example.android.messagingservice/MessagingFragment.java index f8efcc0c7..703bc8083 100644 --- a/samples/browseable/MessagingService/src/com.example.android.messagingservice/MessagingFragment.java +++ b/samples/browseable/MessagingService/src/com.example.android.messagingservice/MessagingFragment.java @@ -51,7 +51,7 @@ public class MessagingFragment extends Fragment implements View.OnClickListener private Messenger mService; private boolean mBound; - private ServiceConnection mConnection = new ServiceConnection() { + private final ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder service) { mService = new Messenger(service); @@ -67,7 +67,7 @@ public class MessagingFragment extends Fragment implements View.OnClickListener } }; - private SharedPreferences.OnSharedPreferenceChangeListener listener = + private final SharedPreferences.OnSharedPreferenceChangeListener listener = new SharedPreferences.OnSharedPreferenceChangeListener() { @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { diff --git a/samples/browseable/MessagingService/src/com.example.android.messagingservice/MessagingService.java b/samples/browseable/MessagingService/src/com.example.android.messagingservice/MessagingService.java index f5900610d..73199ed54 100644 --- a/samples/browseable/MessagingService/src/com.example.android.messagingservice/MessagingService.java +++ b/samples/browseable/MessagingService/src/com.example.android.messagingservice/MessagingService.java @@ -31,41 +31,24 @@ import android.support.v4.app.NotificationManagerCompat; import android.support.v4.app.RemoteInput; import android.util.Log; +import java.lang.ref.WeakReference; import java.util.Iterator; public class MessagingService extends Service { private static final String TAG = MessagingService.class.getSimpleName(); - - public static final String READ_ACTION = + private static final String EOL = "\n"; + private static final String READ_ACTION = "com.example.android.messagingservice.ACTION_MESSAGE_READ"; + public static final String REPLY_ACTION = "com.example.android.messagingservice.ACTION_MESSAGE_REPLY"; public static final String CONVERSATION_ID = "conversation_id"; public static final String EXTRA_VOICE_REPLY = "extra_voice_reply"; public static final int MSG_SEND_NOTIFICATION = 1; - public static final String EOL = "\n"; private NotificationManagerCompat mNotificationManager; - private final Messenger mMessenger = new Messenger(new IncomingHandler()); - - /** - * Handler of incoming messages from clients. - */ - class IncomingHandler extends Handler { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_SEND_NOTIFICATION: - int howManyConversations = msg.arg1 <= 0 ? 1 : msg.arg1; - int messagesPerConv = msg.arg2 <= 0 ? 1 : msg.arg2; - sendNotification(howManyConversations, messagesPerConv); - break; - default: - super.handleMessage(msg); - } - } - } + private final Messenger mMessenger = new Messenger(new IncomingHandler(this)); @Override public void onCreate() { @@ -79,18 +62,6 @@ public class MessagingService extends Service { return mMessenger.getBinder(); } - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - Log.d(TAG, "onStartCommand"); - return START_STICKY; - } - - @Override - public void onDestroy() { - super.onDestroy(); - Log.d(TAG, "onDestroy"); - } - // Creates an intent that will be triggered when a message is marked as read. private Intent getMessageReadIntent(int id) { return new Intent() @@ -171,4 +142,31 @@ public class MessagingService extends Service { mNotificationManager.notify(conversation.getConversationId(), builder.build()); } + + /** + * Handler for incoming messages from clients. + */ + private static class IncomingHandler extends Handler { + private final WeakReference<MessagingService> mReference; + + IncomingHandler(MessagingService service) { + mReference = new WeakReference<>(service); + } + + @Override + public void handleMessage(Message msg) { + MessagingService service = mReference.get(); + switch (msg.what) { + case MSG_SEND_NOTIFICATION: + int howManyConversations = msg.arg1 <= 0 ? 1 : msg.arg1; + int messagesPerConversation = msg.arg2 <= 0 ? 1 : msg.arg2; + if (service != null) { + service.sendNotification(howManyConversations, messagesPerConversation); + } + break; + default: + super.handleMessage(msg); + } + } + } } diff --git a/samples/browseable/Quiz/Application/src/com.example.android.wearable.quiz/MainActivity.java b/samples/browseable/Quiz/Application/src/com.example.android.wearable.quiz/MainActivity.java index 1e589353c..de8eb7450 100644 --- a/samples/browseable/Quiz/Application/src/com.example.android.wearable.quiz/MainActivity.java +++ b/samples/browseable/Quiz/Application/src/com.example.android.wearable.quiz/MainActivity.java @@ -325,8 +325,8 @@ public class MainActivity extends Activity implements DataApi.DataListener, @Override public void onDataChanged(DataEventBuffer dataEvents) { + // Need to freeze the dataEvents so they will exist later on the UI thread final List<DataEvent> events = FreezableUtils.freezeIterable(dataEvents); - dataEvents.close(); runOnUiThread(new Runnable() { @Override public void run() { @@ -445,16 +445,17 @@ public class MainActivity extends Activity implements DataApi.DataListener, .setResultCallback(new ResultCallback<DataItemBuffer>() { @Override public void onResult(DataItemBuffer result) { - if (result.getStatus().isSuccess()) { - List<DataItem> dataItemList = FreezableUtils.freezeIterable(result); - result.close(); - resetDataItems(dataItemList); - } else { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Reset quiz: failed to get Data Items to reset"); + try { + if (result.getStatus().isSuccess()) { + resetDataItems(result); + } else { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Reset quiz: failed to get Data Items to reset"); + } } + } finally { + result.release(); } - result.close(); } }); } else { @@ -467,7 +468,7 @@ public class MainActivity extends Activity implements DataApi.DataListener, mNumSkipped = 0; } - private void resetDataItems(List<DataItem> dataItemList) { + private void resetDataItems(DataItemBuffer dataItemList) { if (mGoogleApiClient.isConnected()) { for (final DataItem dataItem : dataItemList) { final Uri dataItemUri = dataItem.getUri(); @@ -521,19 +522,23 @@ public class MainActivity extends Activity implements DataApi.DataListener, .setResultCallback(new ResultCallback<DataItemBuffer>() { @Override public void onResult(DataItemBuffer result) { - if (result.getStatus().isSuccess()) { - List<Uri> dataItemUriList = new ArrayList<Uri>(); - for (final DataItem dataItem : result) { - dataItemUriList.add(dataItem.getUri()); - } - result.close(); - deleteDataItems(dataItemUriList); - } else { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Clear quiz: failed to get Data Items for deletion"); + try { + if (result.getStatus().isSuccess()) { + List<Uri> dataItemUriList = new ArrayList<Uri>(); + for (final DataItem dataItem : result) { + dataItemUriList.add(dataItem.getUri()); + } + deleteDataItems(dataItemUriList); + } else { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Clear quiz: failed to get Data Items for " + + "deletion"); + + } } + } finally { + result.release(); } - result.close(); } }); } else { diff --git a/samples/browseable/Quiz/Wearable/src/com.example.android.wearable.quiz/QuizListenerService.java b/samples/browseable/Quiz/Wearable/src/com.example.android.wearable.quiz/QuizListenerService.java index 95bedf0ac..5a4b90617 100644 --- a/samples/browseable/Quiz/Wearable/src/com.example.android.wearable.quiz/QuizListenerService.java +++ b/samples/browseable/Quiz/Wearable/src/com.example.android.wearable.quiz/QuizListenerService.java @@ -40,7 +40,6 @@ import android.util.Log; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.data.FreezableUtils; import com.google.android.gms.wearable.DataEvent; import com.google.android.gms.wearable.DataEventBuffer; import com.google.android.gms.wearable.DataItem; @@ -80,9 +79,6 @@ public class QuizListenerService extends WearableListenerService { @Override public void onDataChanged(DataEventBuffer dataEvents) { - final List<DataEvent> events = FreezableUtils.freezeIterable(dataEvents); - dataEvents.close(); - GoogleApiClient googleApiClient = new GoogleApiClient.Builder(this) .addApi(Wearable.API) .build(); @@ -94,7 +90,7 @@ public class QuizListenerService extends WearableListenerService { return; } - for (DataEvent event : events) { + for (DataEvent event : dataEvents) { if (event.getType() == DataEvent.TYPE_CHANGED) { DataItem dataItem = event.getDataItem(); DataMap dataMap = DataMapItem.fromDataItem(dataItem).getDataMap(); diff --git a/samples/browseable/RuntimePermissions/res/values/strings.xml b/samples/browseable/RuntimePermissions/res/values/strings.xml index 941f05900..82d7b719d 100644 --- a/samples/browseable/RuntimePermissions/res/values/strings.xml +++ b/samples/browseable/RuntimePermissions/res/values/strings.xml @@ -16,4 +16,6 @@ <string name="permision_available_camera">Camera Permission has been granted. Preview 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> -</resources>
\ No newline at end of file + <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> +</resources> 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 43436aa5f..5f38bad8d 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 @@ -54,6 +54,10 @@ import android.widget.ViewAnimator; * {@link Activity#requestPermissions(String[], int)} and the return value checked in {@link * Activity#onRequestPermissionsResult(int, String[], int[])}. * <p> + * Before requesting permissions, {@link Activity#shouldShowRequestPermissionRationale(String)} + * should be called to provide the user with additional context for the use of permissions if they + * have been denied previously. + * <p> * If this sample is executed on a device running a platform version below M, all permissions * declared * in the Android manifest file are always granted at install time and cannot be requested at run @@ -102,14 +106,26 @@ public class MainActivity extends SampleActivityBase { // BEGIN_INCLUDE(camera_permission) // Check if the Camera permission is already available. if (PermissionUtil.hasSelfPermission(this, Manifest.permission.CAMERA)) { + // Camera permissions is already available, show the camera preview. Log.i(TAG, "CAMERA permission has already been granted. Displaying camera preview."); - // Camera permissions is already available, show the camera preview. showCameraPreview(); } else { + // Camera permission has not been granted. Log.i(TAG, "CAMERA permission has NOT been granted. Requesting permission."); - // Camera permission has not been granted. Request it. - requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_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. + 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) @@ -128,7 +144,18 @@ public class MainActivity extends SampleActivityBase { // 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."); + + // 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(); + } + // contact permissions has not been granted (read and write contacts). Request them. requestPermissions(PERMISSIONS_CONTACT, REQUEST_CONTACTS); } diff --git a/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/camera/CameraPreviewFragment.java b/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/camera/CameraPreviewFragment.java index d0938f672..871cf757a 100644 --- a/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/camera/CameraPreviewFragment.java +++ b/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/camera/CameraPreviewFragment.java @@ -61,8 +61,13 @@ public class CameraPreviewFragment extends Fragment { // Open an instance of the first camera and retrieve its info. mCamera = getCameraInstance(CAMERA_ID); - Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); - Camera.getCameraInfo(CAMERA_ID, cameraInfo); + Camera.CameraInfo cameraInfo = null; + + if (mCamera != null) { + // Get camera info only if the camera is available + cameraInfo = new Camera.CameraInfo(); + Camera.getCameraInfo(CAMERA_ID, cameraInfo); + } if (mCamera == null || cameraInfo == null) { // Camera is not available, display error message diff --git a/samples/browseable/RuntimePermissionsBasic/src/com.example.android.basicpermissions/MainActivity.java b/samples/browseable/RuntimePermissionsBasic/src/com.example.android.basicpermissions/MainActivity.java index 36941ebb9..9fa646d7e 100644 --- a/samples/browseable/RuntimePermissionsBasic/src/com.example.android.basicpermissions/MainActivity.java +++ b/samples/browseable/RuntimePermissionsBasic/src/com.example.android.basicpermissions/MainActivity.java @@ -86,34 +86,41 @@ public class MainActivity extends Activity { private void showCameraPreview() { // BEGIN_INCLUDE(startCamera) - if (isMNC()) { - // On Android M and above, need to check if permission has been granted at runtime. - if (checkSelfPermission(Manifest.permission.CAMERA) - == PackageManager.PERMISSION_GRANTED) { - // Permission is available, start camera preview - startCamera(); - Toast.makeText(this, - "Camera permission has already been granted. Starting preview.", - Toast.LENGTH_SHORT).show(); - } else { - // Permission has not been granted and must be requested. - Toast.makeText(this, - "Permission is not available. Requesting camera permission.", - Toast.LENGTH_SHORT).show(); - requestPermissions(new String[]{Manifest.permission.CAMERA}, - PERMISSION_REQUEST_CAMERA); - } - } else { - /* - Below Android M all permissions have already been grated at install time and do not - need to verified or requested. - If a permission has been disabled in the system settings, the API will return - unavailable or empty data instead. */ + if (!isMNC()) { + // Below Android M there is no need to check for runtime permissions Toast.makeText(this, "Requested permissions are granted at install time below M and are always " + "available at runtime.", Toast.LENGTH_SHORT).show(); startCamera(); + return; + } + + // Check if the Camera permission has been granted + if (checkSelfPermission(Manifest.permission.CAMERA) + != PackageManager.PERMISSION_GRANTED) { + // Permission has not been granted and must be requested. + + if (shouldShowRequestPermissionRationale( + Manifest.permission.CAMERA)) { + // Provide an additional rationale to the user if the permission was not granted + // and the user would benefit from additional context for the use of the permission. + Toast.makeText(this, "Camera access is required to display a camera preview.", + Toast.LENGTH_SHORT).show(); + } + Toast.makeText(this, + "Permission is not available. Requesting camera permission.", + Toast.LENGTH_SHORT).show(); + + // Request the permission. The result will be received in onRequestPermissionResult() + requestPermissions(new String[]{Manifest.permission.CAMERA}, + PERMISSION_REQUEST_CAMERA); + } else { + // Permission is already available, start camera preview + startCamera(); + Toast.makeText(this, + "Camera permission is available. Starting preview.", + Toast.LENGTH_SHORT).show(); } // END_INCLUDE(startCamera) } diff --git a/samples/browseable/WatchFace/Wearable/res/values/color.xml b/samples/browseable/WatchFace/Wearable/res/values/color.xml index 0da08ed8b..1370192da 100644 --- a/samples/browseable/WatchFace/Wearable/res/values/color.xml +++ b/samples/browseable/WatchFace/Wearable/res/values/color.xml @@ -14,6 +14,7 @@ limitations under the License. --> <resources> + <color name="digital_date">#aaaaaa</color> <color name="digital_am_pm">#aaaaa0</color> <color name="digital_colons">#aaaaa0</color> <color name="config_activity_background">#ffffff</color> diff --git a/samples/browseable/WatchFace/Wearable/res/values/dimens.xml b/samples/browseable/WatchFace/Wearable/res/values/dimens.xml index 8f04e56de..aef847b08 100644 --- a/samples/browseable/WatchFace/Wearable/res/values/dimens.xml +++ b/samples/browseable/WatchFace/Wearable/res/values/dimens.xml @@ -16,11 +16,13 @@ <resources> <dimen name="digital_text_size">40dp</dimen> <dimen name="digital_text_size_round">45dp</dimen> + <dimen name="digital_date_text_size">20dp</dimen> <dimen name="digital_am_pm_size">25dp</dimen> <dimen name="digital_am_pm_size_round">30dp</dimen> <dimen name="digital_x_offset">15dp</dimen> <dimen name="digital_x_offset_round">25dp</dimen> - <dimen name="digital_y_offset">90dp</dimen> + <dimen name="digital_y_offset">80dp</dimen> + <dimen name="digital_line_height">25dp</dimen> <dimen name="digital_config_color_picker_item_margin">32dp</dimen> <dimen name="content_padding_start">12dp</dimen> </resources> diff --git a/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/AnalogWatchFaceService.java b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/AnalogWatchFaceService.java index 15c550f33..16194b151 100644 --- a/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/AnalogWatchFaceService.java +++ b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/AnalogWatchFaceService.java @@ -213,6 +213,17 @@ public class AnalogWatchFaceService extends CanvasWatchFaceService { } @Override + public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) { + if (mBackgroundScaledBitmap == null + || mBackgroundScaledBitmap.getWidth() != width + || mBackgroundScaledBitmap.getHeight() != height) { + mBackgroundScaledBitmap = Bitmap.createScaledBitmap(mBackgroundBitmap, + width, height, true /* filter */); + } + super.onSurfaceChanged(holder, format, width, height); + } + + @Override public void onDraw(Canvas canvas, Rect bounds) { mCalendar.setTimeInMillis(System.currentTimeMillis()); @@ -220,12 +231,6 @@ public class AnalogWatchFaceService extends CanvasWatchFaceService { int height = bounds.height(); // Draw the background, scaled to fit. - if (mBackgroundScaledBitmap == null - || mBackgroundScaledBitmap.getWidth() != width - || mBackgroundScaledBitmap.getHeight() != height) { - mBackgroundScaledBitmap = Bitmap.createScaledBitmap(mBackgroundBitmap, - width, height, true /* filter */); - } canvas.drawBitmap(mBackgroundScaledBitmap, 0, 0, null); // Find the center. Ignore the window insets so that, on round watches with a diff --git a/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/DigitalWatchFaceService.java b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/DigitalWatchFaceService.java index 0bc420d01..0a9eff2fe 100644 --- a/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/DigitalWatchFaceService.java +++ b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/DigitalWatchFaceService.java @@ -46,7 +46,10 @@ import com.google.android.gms.wearable.DataMap; import com.google.android.gms.wearable.DataMapItem; import com.google.android.gms.wearable.Wearable; +import java.text.SimpleDateFormat; import java.util.Calendar; +import java.util.Date; +import java.util.Locale; import java.util.TimeZone; import java.util.concurrent.TimeUnit; @@ -123,16 +126,26 @@ public class DigitalWatchFaceService extends CanvasWatchFaceService { .addApi(Wearable.API) .build(); - final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() { + /** + * Handles time zone and locale changes. + */ + final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { mCalendar.setTimeZone(TimeZone.getDefault()); + initFormats(); invalidate(); } }; - boolean mRegisteredTimeZoneReceiver = false; + + /** + * Unregistering an unregistered receiver throws an exception. Keep track of the + * registration state to prevent that. + */ + boolean mRegisteredReceiver = false; Paint mBackgroundPaint; + Paint mDatePaint; Paint mHourPaint; Paint mMinutePaint; Paint mSecondPaint; @@ -140,10 +153,16 @@ public class DigitalWatchFaceService extends CanvasWatchFaceService { Paint mColonPaint; float mColonWidth; boolean mMute; + Calendar mCalendar; + Date mDate; + SimpleDateFormat mDayOfWeekFormat; + java.text.DateFormat mDateFormat; + boolean mShouldDrawColons; float mXOffset; float mYOffset; + float mLineHeight; String mAmString; String mPmString; int mInteractiveBackgroundColor = @@ -175,11 +194,13 @@ public class DigitalWatchFaceService extends CanvasWatchFaceService { .build()); Resources resources = DigitalWatchFaceService.this.getResources(); mYOffset = resources.getDimension(R.dimen.digital_y_offset); + mLineHeight = resources.getDimension(R.dimen.digital_line_height); mAmString = resources.getString(R.string.digital_am); mPmString = resources.getString(R.string.digital_pm); mBackgroundPaint = new Paint(); mBackgroundPaint.setColor(mInteractiveBackgroundColor); + mDatePaint = createTextPaint(resources.getColor(R.color.digital_date)); mHourPaint = createTextPaint(mInteractiveHourDigitsColor, BOLD_TYPEFACE); mMinutePaint = createTextPaint(mInteractiveMinuteDigitsColor); mSecondPaint = createTextPaint(mInteractiveSecondDigitsColor); @@ -187,6 +208,8 @@ public class DigitalWatchFaceService extends CanvasWatchFaceService { mColonPaint = createTextPaint(resources.getColor(R.color.digital_colons)); mCalendar = Calendar.getInstance(); + mDate = new Date(); + initFormats(); } @Override @@ -219,8 +242,9 @@ public class DigitalWatchFaceService extends CanvasWatchFaceService { registerReceiver(); - // Update time zone in case it changed while we weren't visible. + // Update time zone and date formats, in case they changed while we weren't visible. mCalendar.setTimeZone(TimeZone.getDefault()); + initFormats(); } else { unregisterReceiver(); @@ -235,21 +259,29 @@ public class DigitalWatchFaceService extends CanvasWatchFaceService { updateTimer(); } + private void initFormats() { + mDayOfWeekFormat = new SimpleDateFormat("EEEE", Locale.getDefault()); + mDayOfWeekFormat.setCalendar(mCalendar); + mDateFormat = DateFormat.getDateFormat(DigitalWatchFaceService.this); + mDateFormat.setCalendar(mCalendar); + } + private void registerReceiver() { - if (mRegisteredTimeZoneReceiver) { + if (mRegisteredReceiver) { return; } - mRegisteredTimeZoneReceiver = true; + mRegisteredReceiver = true; IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED); - DigitalWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter); + filter.addAction(Intent.ACTION_LOCALE_CHANGED); + DigitalWatchFaceService.this.registerReceiver(mReceiver, filter); } private void unregisterReceiver() { - if (!mRegisteredTimeZoneReceiver) { + if (!mRegisteredReceiver) { return; } - mRegisteredTimeZoneReceiver = false; - DigitalWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver); + mRegisteredReceiver = false; + DigitalWatchFaceService.this.unregisterReceiver(mReceiver); } @Override @@ -269,6 +301,7 @@ public class DigitalWatchFaceService extends CanvasWatchFaceService { float amPmSize = resources.getDimension(isRound ? R.dimen.digital_am_pm_size_round : R.dimen.digital_am_pm_size); + mDatePaint.setTextSize(resources.getDimension(R.dimen.digital_date_text_size)); mHourPaint.setTextSize(textSize); mMinutePaint.setTextSize(textSize); mSecondPaint.setTextSize(textSize); @@ -321,6 +354,7 @@ public class DigitalWatchFaceService extends CanvasWatchFaceService { if (mLowBitAmbient) { boolean antiAlias = !inAmbientMode; + mDatePaint.setAntiAlias(antiAlias); mHourPaint.setAntiAlias(antiAlias); mMinutePaint.setAntiAlias(antiAlias); mSecondPaint.setAntiAlias(antiAlias); @@ -335,7 +369,7 @@ public class DigitalWatchFaceService extends CanvasWatchFaceService { } private void adjustPaintColorToCurrentMode(Paint paint, int interactiveColor, - int ambientColor) { + int ambientColor) { paint.setColor(isInAmbientMode() ? ambientColor : interactiveColor); } @@ -353,6 +387,7 @@ public class DigitalWatchFaceService extends CanvasWatchFaceService { if (mMute != inMuteMode) { mMute = inMuteMode; int alpha = inMuteMode ? MUTE_ALPHA : NORMAL_ALPHA; + mDatePaint.setAlpha(alpha); mHourPaint.setAlpha(alpha); mMinutePaint.setAlpha(alpha); mColonPaint.setAlpha(alpha); @@ -409,7 +444,9 @@ public class DigitalWatchFaceService extends CanvasWatchFaceService { @Override public void onDraw(Canvas canvas, Rect bounds) { - mCalendar.setTimeInMillis(System.currentTimeMillis()); + long now = System.currentTimeMillis(); + mCalendar.setTimeInMillis(now); + mDate.setTime(now); boolean is24Hour = DateFormat.is24HourFormat(DigitalWatchFaceService.this); // Show colons for the first half of each second so the colons blink on when the time @@ -430,9 +467,6 @@ public class DigitalWatchFaceService extends CanvasWatchFaceService { hour = 12; } hourString = String.valueOf(hour); - if (hour < 10) { - x += mHourPaint.measureText("0"); - } } canvas.drawText(hourString, x, mYOffset, mHourPaint); x += mHourPaint.measureText(hourString); @@ -463,6 +497,19 @@ public class DigitalWatchFaceService extends CanvasWatchFaceService { canvas.drawText(getAmPmString( mCalendar.get(Calendar.AM_PM)), x, mYOffset, mAmPmPaint); } + + // Only render the day of week and date if there is no peek card, so they do not bleed + // into each other in ambient mode. + if (getPeekCardPosition().isEmpty()) { + // Day of week + canvas.drawText( + mDayOfWeekFormat.format(mDate), + mXOffset, mYOffset + mLineHeight, mDatePaint); + // Date + canvas.drawText( + mDateFormat.format(mDate), + mXOffset, mYOffset + mLineHeight * 2, mDatePaint); + } } /** @@ -522,27 +569,23 @@ public class DigitalWatchFaceService extends CanvasWatchFaceService { @Override // DataApi.DataListener public void onDataChanged(DataEventBuffer dataEvents) { - try { - for (DataEvent dataEvent : dataEvents) { - if (dataEvent.getType() != DataEvent.TYPE_CHANGED) { - continue; - } + for (DataEvent dataEvent : dataEvents) { + if (dataEvent.getType() != DataEvent.TYPE_CHANGED) { + continue; + } - DataItem dataItem = dataEvent.getDataItem(); - if (!dataItem.getUri().getPath().equals( - DigitalWatchFaceUtil.PATH_WITH_FEATURE)) { - continue; - } + DataItem dataItem = dataEvent.getDataItem(); + if (!dataItem.getUri().getPath().equals( + DigitalWatchFaceUtil.PATH_WITH_FEATURE)) { + continue; + } - DataMapItem dataMapItem = DataMapItem.fromDataItem(dataItem); - DataMap config = dataMapItem.getDataMap(); - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Config DataItem updated:" + config); - } - updateUiForConfigDataMap(config); + DataMapItem dataMapItem = DataMapItem.fromDataItem(dataItem); + DataMap config = dataMapItem.getDataMap(); + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Config DataItem updated:" + config); } - } finally { - dataEvents.close(); + updateUiForConfigDataMap(config); } } diff --git a/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/SweepWatchFaceService.java b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/SweepWatchFaceService.java index 193f29a71..d547f1ce8 100644 --- a/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/SweepWatchFaceService.java +++ b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/SweepWatchFaceService.java @@ -171,6 +171,17 @@ public class SweepWatchFaceService extends CanvasWatchFaceService { } @Override + public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) { + if (mBackgroundScaledBitmap == null + || mBackgroundScaledBitmap.getWidth() != width + || mBackgroundScaledBitmap.getHeight() != height) { + mBackgroundScaledBitmap = Bitmap.createScaledBitmap(mBackgroundBitmap, + width, height, true /* filter */); + } + super.onSurfaceChanged(holder, format, width, height); + } + + @Override public void onDraw(Canvas canvas, Rect bounds) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "onDraw"); @@ -182,12 +193,6 @@ public class SweepWatchFaceService extends CanvasWatchFaceService { int height = bounds.height(); // Draw the background, scaled to fit. - if (mBackgroundScaledBitmap == null - || mBackgroundScaledBitmap.getWidth() != width - || mBackgroundScaledBitmap.getHeight() != height) { - mBackgroundScaledBitmap = Bitmap.createScaledBitmap(mBackgroundBitmap, - width, height, true /* filter */); - } canvas.drawBitmap(mBackgroundScaledBitmap, 0, 0, null); // Find the center. Ignore the window insets so that, on round watches with a diff --git a/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/TiltWatchFaceService.java b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/TiltWatchFaceService.java index ffb0b6c29..879473c92 100644 --- a/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/TiltWatchFaceService.java +++ b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/TiltWatchFaceService.java @@ -45,7 +45,7 @@ public class TiltWatchFaceService extends Gles2WatchFaceService { private static final long FPS = 60; /** Z distance from the camera to the watchface. */ - private static final float EYE_Z = 2.3f; + private static final float EYE_Z = -2.3f; /** How long each frame is displayed at expected frame rate. */ private static final long FRAME_PERIOD_MS = TimeUnit.SECONDS.toMillis(1) / FPS; diff --git a/samples/browseable/XYZTouristAttractions/Application/res/layout/fragment_detail.xml b/samples/browseable/XYZTouristAttractions/Application/res/layout/fragment_detail.xml index dffeb4edb..6e280fd17 100644 --- a/samples/browseable/XYZTouristAttractions/Application/res/layout/fragment_detail.xml +++ b/samples/browseable/XYZTouristAttractions/Application/res/layout/fragment_detail.xml @@ -16,67 +16,86 @@ limitations under the License. --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:orientation="vertical" +<android.support.design.widget.CoordinatorLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" - android:layout_height="match_parent" - android:weightSum="100"> - - <ImageView - android:id="@+id/imageView" - android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="50" - android:scaleType="centerCrop" - android:transitionName="image" /> + android:layout_height="match_parent"> <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="?colorPrimary" android:orientation="vertical" - android:paddingStart="@dimen/keyline2" - android:paddingEnd="@dimen/keyline3" - android:paddingTop="@dimen/standard_margin" - android:paddingBottom="@dimen/standard_margin" - android:elevation="2dp"> - - <TextView - android:id="@+id/nameTextView" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:weightSum="100"> + + <ImageView + android:id="@+id/imageView" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:maxLines="2" - android:ellipsize="end" - android:textIsSelectable="true" - style="@style/TextAppearance.AppCompat.Title.Inverse" - android:transitionName="title" /> - - <TextView - android:id="@+id/distanceTextView" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - style="@style/TextAppearance.AppCompat.Subhead.Inverse" /> + android:layout_height="0dp" + android:layout_weight="50" + android:scaleType="centerCrop" + android:transitionName="image" /> - </LinearLayout> - - <ScrollView - android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="50" - android:paddingTop="@dimen/standard_margin" - android:paddingBottom="@dimen/standard_margin" - android:scrollbarStyle="outsideOverlay" - android:clipToPadding="false"> - - <TextView - android:id="@+id/descriptionTextView" - android:layout_height="wrap_content" + <LinearLayout + android:id="@+id/textLayout" android:layout_width="match_parent" - android:textIsSelectable="true" - style="@style/TextAppearance.AppCompat.Body1" + android:layout_height="wrap_content" + android:background="?colorPrimary" + android:orientation="vertical" android:paddingStart="@dimen/keyline2" - android:paddingEnd="@dimen/keyline3" /> + android:paddingEnd="@dimen/keyline3" + android:paddingTop="@dimen/standard_margin" + android:paddingBottom="@dimen/standard_margin" + android:elevation="2dp"> + + <TextView + android:id="@+id/nameTextView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:maxLines="2" + android:ellipsize="end" + android:textIsSelectable="true" + style="@style/TextAppearance.AppCompat.Title.Inverse" /> + + <TextView + android:id="@+id/distanceTextView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="@style/TextAppearance.AppCompat.Subhead.Inverse" /> + + </LinearLayout> + + <ScrollView + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="50" + android:paddingTop="@dimen/standard_margin" + android:paddingBottom="@dimen/standard_margin" + android:scrollbarStyle="outsideOverlay" + android:clipToPadding="false"> - </ScrollView> + <TextView + android:id="@+id/descriptionTextView" + android:layout_height="wrap_content" + android:layout_width="match_parent" + android:textIsSelectable="true" + style="@style/TextAppearance.AppCompat.Body1" + android:paddingStart="@dimen/keyline2" + android:paddingEnd="@dimen/keyline3" /> + + </ScrollView> + + </LinearLayout> + + <android.support.design.widget.FloatingActionButton + android:id="@+id/mapFab" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:src="@drawable/ic_action_map" + app:layout_anchor="@id/textLayout" + app:layout_anchorGravity="bottom|start" + app:rippleColor="@color/colorFabRipple" + android:layout_marginStart="@dimen/fab_margin" + android:clickable="true" /> -</LinearLayout>
\ No newline at end of file +</android.support.design.widget.CoordinatorLayout>
\ No newline at end of file diff --git a/samples/browseable/XYZTouristAttractions/Application/res/layout/list_row.xml b/samples/browseable/XYZTouristAttractions/Application/res/layout/list_row.xml index 25f55d04c..b2cef57af 100644 --- a/samples/browseable/XYZTouristAttractions/Application/res/layout/list_row.xml +++ b/samples/browseable/XYZTouristAttractions/Application/res/layout/list_row.xml @@ -49,21 +49,20 @@ android:id="@android:id/text1" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_toRightOf="@android:id/icon" + android:layout_toEndOf="@android:id/icon" android:paddingTop="@dimen/small_margin" android:paddingLeft="@dimen/small_margin" android:paddingRight="@dimen/small_margin" android:maxLines="1" android:ellipsize="end" style="?android:textAppearanceMedium" - tools:text="Title 1" - android:transitionName="image" /> + tools:text="Title 1" /> <TextView android:id="@android:id/text2" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_toRightOf="@android:id/icon" + android:layout_toEndOf="@android:id/icon" android:layout_below="@android:id/text1" android:padding="@dimen/small_margin" android:ellipsize="end" diff --git a/samples/browseable/XYZTouristAttractions/Application/res/transition-v21/shared_move.xml b/samples/browseable/XYZTouristAttractions/Application/res/transition-v21/shared_move.xml new file mode 100644 index 000000000..3debfa039 --- /dev/null +++ b/samples/browseable/XYZTouristAttractions/Application/res/transition-v21/shared_move.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 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. +--> +<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" + android:startDelay="@android:integer/config_shortAnimTime"> + + <changeBounds> + <arcMotion /> + </changeBounds> + <changeTransform/> + <changeClipBounds/> + <changeImageTransform/> + +</transitionSet> diff --git a/samples/browseable/XYZTouristAttractions/Application/res/transition-v21/explode.xml b/samples/browseable/XYZTouristAttractions/Application/res/transition-v21/window_enter.xml index 5dfa7179d..619ffdf0e 100644 --- a/samples/browseable/XYZTouristAttractions/Application/res/transition-v21/explode.xml +++ b/samples/browseable/XYZTouristAttractions/Application/res/transition-v21/window_enter.xml @@ -14,7 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. --> -<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"> +<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" + android:transitionOrdering="sequential"> <explode> <targets> @@ -25,4 +26,11 @@ </targets> </explode> + <transition class="com.example.android.xyztouristattractions.ui.ScaleTransition" + android:interpolator="@android:interpolator/overshoot"> + <targets> + <target android:targetId="@id/mapFab" /> + </targets> + </transition> + </transitionSet>
\ No newline at end of file diff --git a/samples/browseable/XYZTouristAttractions/Application/res/transition-v21/window_return.xml b/samples/browseable/XYZTouristAttractions/Application/res/transition-v21/window_return.xml new file mode 100644 index 000000000..0dae377c4 --- /dev/null +++ b/samples/browseable/XYZTouristAttractions/Application/res/transition-v21/window_return.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2015 Google Inc. All rights reserved. + + 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. + --> +<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" + android:transitionOrdering="sequential"> + + <transition class="com.example.android.xyztouristattractions.ui.ScaleTransition" + android:interpolator="@android:interpolator/fast_out_linear_in" + android:duration="@android:integer/config_shortAnimTime"> + <targets> + <target android:targetId="@id/mapFab" /> + </targets> + </transition> + + <explode> + <targets> + <target android:targetClass="android.widget.TextView" /> + <target android:targetClass="android.widget.FrameLayout" /> + <target android:targetClass="android.widget.LinearLayout" /> + <target android:targetClass="android.widget.ImageView" /> + <target android:excludeId="@id/mapFab" /> + </targets> + </explode> + +</transitionSet>
\ No newline at end of file diff --git a/samples/browseable/XYZTouristAttractions/Application/res/menu/detail.xml b/samples/browseable/XYZTouristAttractions/Application/res/values-sw400dp-v21/dimens.xml index aeb5c9860..ce539e497 100644 --- a/samples/browseable/XYZTouristAttractions/Application/res/menu/detail.xml +++ b/samples/browseable/XYZTouristAttractions/Application/res/values-sw400dp-v21/dimens.xml @@ -1,3 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> <!-- Copyright 2015 Google Inc. All rights reserved. @@ -14,15 +15,8 @@ limitations under the License. --> -<menu xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - tools:context=".MainActivity" > +<resources> - <item android:id="@+id/map" - android:title="@string/action_map" - android:orderInCategory="100" - android:icon="@drawable/ic_action_map" - app:showAsAction="ifRoom" /> + <dimen name="fab_margin">12dp</dimen> -</menu> +</resources>
\ No newline at end of file diff --git a/samples/browseable/XYZTouristAttractions/Application/res/values-sw400dp/dimens.xml b/samples/browseable/XYZTouristAttractions/Application/res/values-sw400dp/dimens.xml index ad90eaaf8..701be7d64 100644 --- a/samples/browseable/XYZTouristAttractions/Application/res/values-sw400dp/dimens.xml +++ b/samples/browseable/XYZTouristAttractions/Application/res/values-sw400dp/dimens.xml @@ -20,5 +20,6 @@ <dimen name="small_margin">12dp</dimen> <dimen name="tiny_margin">8dp</dimen> <dimen name="image_size">140dp</dimen> + <dimen name="fab_margin">0dp</dimen> </resources> diff --git a/samples/browseable/XYZTouristAttractions/Application/res/values-v21/dimens.xml b/samples/browseable/XYZTouristAttractions/Application/res/values-v21/dimens.xml new file mode 100644 index 000000000..2704c4532 --- /dev/null +++ b/samples/browseable/XYZTouristAttractions/Application/res/values-v21/dimens.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2015 Google Inc. All rights reserved. + + 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. + --> + +<resources> + + <dimen name="fab_margin">8dp</dimen> + +</resources>
\ No newline at end of file diff --git a/samples/browseable/XYZTouristAttractions/Application/res/values-v21/styles.xml b/samples/browseable/XYZTouristAttractions/Application/res/values-v21/styles.xml index 319d66497..d1221c599 100644 --- a/samples/browseable/XYZTouristAttractions/Application/res/values-v21/styles.xml +++ b/samples/browseable/XYZTouristAttractions/Application/res/values-v21/styles.xml @@ -21,8 +21,10 @@ <item name="android:windowActivityTransitions">true</item> <item name="android:windowExitTransition">@transition/fade</item> <item name="android:windowReenterTransition">@transition/fade</item> - <item name="android:windowEnterTransition">@transition/explode</item> - <item name="android:windowReturnTransition">@transition/explode</item> + <item name="android:windowEnterTransition">@transition/window_enter</item> + <item name="android:windowReturnTransition">@transition/window_return</item> + <item name="android:windowSharedElementEnterTransition">@transition/shared_move</item> + <item name="android:windowSharedElementExitTransition">@transition/shared_move</item> <item name="android:windowAllowEnterTransitionOverlap">false</item> <item name="android:windowAllowReturnTransitionOverlap">false</item> <item name="android:windowSharedElementsUseOverlay">false</item> diff --git a/samples/browseable/XYZTouristAttractions/Application/res/values/dimens.xml b/samples/browseable/XYZTouristAttractions/Application/res/values/dimens.xml index 03acfeff1..9cfc82534 100644 --- a/samples/browseable/XYZTouristAttractions/Application/res/values/dimens.xml +++ b/samples/browseable/XYZTouristAttractions/Application/res/values/dimens.xml @@ -21,6 +21,7 @@ <dimen name="small_margin">8dp</dimen> <dimen name="tiny_margin">4dp</dimen> <dimen name="image_size">120dp</dimen> + <dimen name="fab_margin">-8dp</dimen> <item type="dimen" name="keyline1">@dimen/standard_margin</item> <dimen name="keyline2">72dp</dimen> <item type="dimen" name="keyline3">@dimen/standard_margin</item> diff --git a/samples/browseable/XYZTouristAttractions/Application/src/com.example.android.xyztouristattractions/ui/AttractionListFragment.java b/samples/browseable/XYZTouristAttractions/Application/src/com.example.android.xyztouristattractions/ui/AttractionListFragment.java index 0f1bc8bfa..28f912743 100644 --- a/samples/browseable/XYZTouristAttractions/Application/src/com.example.android.xyztouristattractions/ui/AttractionListFragment.java +++ b/samples/browseable/XYZTouristAttractions/Application/src/com.example.android.xyztouristattractions/ui/AttractionListFragment.java @@ -216,7 +216,7 @@ public class AttractionListFragment extends Fragment { @Override public void onClick(View v) { - mItemClickListener.onItemClick(v, getPosition()); + mItemClickListener.onItemClick(v, getAdapterPosition()); } } diff --git a/samples/browseable/XYZTouristAttractions/Application/src/com.example.android.xyztouristattractions/ui/DetailFragment.java b/samples/browseable/XYZTouristAttractions/Application/src/com.example.android.xyztouristattractions/ui/DetailFragment.java index 4d21009a3..1ab7326dd 100644 --- a/samples/browseable/XYZTouristAttractions/Application/src/com.example.android.xyztouristattractions/ui/DetailFragment.java +++ b/samples/browseable/XYZTouristAttractions/Application/src/com.example.android.xyztouristattractions/ui/DetailFragment.java @@ -21,12 +21,11 @@ import android.content.Intent; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.support.design.widget.FloatingActionButton; import android.support.v4.app.Fragment; import android.support.v4.app.NavUtils; import android.text.TextUtils; import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; @@ -83,6 +82,7 @@ public class DetailFragment extends Fragment { TextView descTextView = (TextView) view.findViewById(R.id.descriptionTextView); TextView distanceTextView = (TextView) view.findViewById(R.id.distanceTextView); ImageView imageView = (ImageView) view.findViewById(R.id.imageView); + FloatingActionButton mapFab = (FloatingActionButton) view.findViewById(R.id.mapFab); LatLng location = Utils.getLocation(getActivity()); String distance = Utils.formatDistanceBetween(location, mAttraction.location); @@ -102,13 +102,18 @@ public class DetailFragment extends Fragment { .placeholder(R.color.lighter_gray) .override(imageSize, imageSize) .into(imageView); - return view; - } - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.detail, menu); - super.onCreateOptionsMenu(menu, inflater); + mapFab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(Constants.MAPS_INTENT_URI + + Uri.encode(mAttraction.name + ", " + mAttraction.city))); + startActivity(intent); + } + }); + + return view; } @Override @@ -139,12 +144,6 @@ public class DetailFragment extends Fragment { // Otherwise let the system handle navigating "up" return false; - case R.id.map: - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(Constants.MAPS_INTENT_URI + - Uri.encode(mAttraction.name + ", " + mAttraction.city))); - startActivity(intent); - return true; } return super.onOptionsItemSelected(item); } diff --git a/samples/browseable/XYZTouristAttractions/Application/src/com.example.android.xyztouristattractions/ui/ScaleTransition.java b/samples/browseable/XYZTouristAttractions/Application/src/com.example.android.xyztouristattractions/ui/ScaleTransition.java new file mode 100644 index 000000000..a7e4b2e7e --- /dev/null +++ b/samples/browseable/XYZTouristAttractions/Application/src/com.example.android.xyztouristattractions/ui/ScaleTransition.java @@ -0,0 +1,46 @@ +package com.example.android.xyztouristattractions.ui; + +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.transition.TransitionValues; +import android.transition.Visibility; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +/** + * A simple scale transition class to allow an element to scale in or out. + * This is used by the floating action button on the attraction detail screen + * when it appears and disappears during the Activity transitions. + */ +@TargetApi(Build.VERSION_CODES.LOLLIPOP) +public class ScaleTransition extends Visibility { + + public ScaleTransition(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public Animator createAnimation(View view, float startScale, float endScale) { + view.setScaleX(startScale); + view.setScaleY(startScale); + PropertyValuesHolder holderX = PropertyValuesHolder.ofFloat("scaleX", startScale, endScale); + PropertyValuesHolder holderY = PropertyValuesHolder.ofFloat("scaleY", startScale, endScale); + return ObjectAnimator.ofPropertyValuesHolder(view, holderX, holderY); + } + + @Override + public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues, + TransitionValues endValues) { + return createAnimation(view, 0, 1); + } + + @Override + public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues, + TransitionValues endValues) { + return createAnimation(view, 1, 0); + } +}
\ No newline at end of file diff --git a/samples/browseable/XYZTouristAttractions/Shared/res/values/colors.xml b/samples/browseable/XYZTouristAttractions/Shared/res/values/colors.xml index 73c2b6c40..2260336da 100644 --- a/samples/browseable/XYZTouristAttractions/Shared/res/values/colors.xml +++ b/samples/browseable/XYZTouristAttractions/Shared/res/values/colors.xml @@ -20,5 +20,6 @@ <color name="colorPrimary">#4e6cef</color> <color name="colorPrimaryDark">#2a36b1</color> <color name="colorAccent">#ff7043</color> + <color name="colorFabRipple">#D84315</color> </resources>
\ No newline at end of file diff --git a/samples/browseable/XYZTouristAttractions/Wearable/AndroidManifest.xml b/samples/browseable/XYZTouristAttractions/Wearable/AndroidManifest.xml index 24328b36e..80d0c920f 100644 --- a/samples/browseable/XYZTouristAttractions/Wearable/AndroidManifest.xml +++ b/samples/browseable/XYZTouristAttractions/Wearable/AndroidManifest.xml @@ -22,7 +22,7 @@ <uses-sdk android:minSdkVersion="21" - android:targetSdkVersion="21" /> + android:targetSdkVersion="22" /> <application android:allowBackup="true" @@ -32,9 +32,6 @@ <activity android:name=".ui.AttractionsActivity" - android:exported="true" - android:allowEmbedded="true" - android:taskAffinity="" android:label="@string/app_name" /> <activity diff --git a/samples/browseable/XYZTouristAttractions/Wearable/src/com.example.android.xyztouristattractions/service/ListenerService.java b/samples/browseable/XYZTouristAttractions/Wearable/src/com.example.android.xyztouristattractions/service/ListenerService.java index 3908414d7..d2282511d 100644 --- a/samples/browseable/XYZTouristAttractions/Wearable/src/com.example.android.xyztouristattractions/service/ListenerService.java +++ b/samples/browseable/XYZTouristAttractions/Wearable/src/com.example.android.xyztouristattractions/service/ListenerService.java @@ -30,7 +30,6 @@ import com.example.android.xyztouristattractions.common.Utils; import com.example.android.xyztouristattractions.ui.AttractionsActivity; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.data.FreezableUtils; import com.google.android.gms.wearable.DataEvent; import com.google.android.gms.wearable.DataEventBuffer; import com.google.android.gms.wearable.DataMap; @@ -54,9 +53,7 @@ public class ListenerService extends WearableListenerService { public void onDataChanged(DataEventBuffer dataEvents) { Log.d(TAG, "onDataChanged: " + dataEvents); - final List<DataEvent> events = FreezableUtils.freezeIterable(dataEvents); - - for (DataEvent event : events) { + for (DataEvent event : dataEvents) { if (event.getType() == DataEvent.TYPE_CHANGED && event.getDataItem() != null && Constants.ATTRACTION_PATH.equals(event.getDataItem().getUri().getPath())) { |