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() {
@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 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.
-->
-
-
+ package="com.example.android.camera2basic">
-
+
+
+
+ android:background="@color/control_background">
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 @@
-->
-
+ >
+
+
+
+
+
+
\ 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">
+
+
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 @@
$62.68Mesh backpack in white. Black textile trim throughout.Purchase successful
+ A new fingerprint was added to this device, so your password is required.
+ Use fingerprint in the future
+ Use fingerprint to authenticate
+ use_fingerprint_to_authenticate_key
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 @@
+
+
+
+
+
\ 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) {
+ findViewById(R.id.confirmation_message).setVisibility(View.GONE);
+ findViewById(R.id.encrypted_message).setVisibility(View.GONE);
- // 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);
+ // 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">
-
-
-
-
+ android:screenOrientation="landscape"
+ android:theme="@style/Theme.AppCompat.Light">
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.
-->
+ android:orientation="horizontal"
+ tools:context="com.example.android.hdrviewfinder.HdrViewfinderActivity">
+ android:orientation="vertical"
+ android:layout_margin="5dp">
*/
-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.
*/
@@ -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
}
};
+ /**
+ * 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;
+ }
+ }
+
+ /**
+ * 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();
+ }
+ }
+ }
+
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);
- 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 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;
- 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;
+ // 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);
}
}
- 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 @@
-
+
-
+
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">
Messaging Sample
- Settings
- Messaging SampleReply by VoiceSend 2 conversations with 1 messageSend 1 conversation with 1 message
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 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 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() {
@Override
public void onResult(DataItemBuffer result) {
- if (result.getStatus().isSuccess()) {
- List 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 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() {
@Override
public void onResult(DataItemBuffer result) {
- if (result.getStatus().isSuccess()) {
- List dataItemUriList = new ArrayList();
- 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 dataItemUriList = new ArrayList();
+ 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 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 @@
Camera Permission has been granted. Preview can now be opened.Contacts Permissions have been granted. Contacts screen can now be opened.Permissions were not granted.
-
\ No newline at end of file
+ Camera permission is needed to show the camera preview.
+ Contacts permissions are needed to demonstrate access to the contacts database.
+
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[])}.
*
+ * 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.
+ *
* 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.
-->
+ #aaaaaa#aaaaa0#aaaaa0#ffffff
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 @@
40dp45dp
+ 20dp25dp30dp15dp25dp
- 90dp
+ 80dp
+ 25dp32dp12dp
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
@@ -212,6 +212,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;
- }
-
- 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);
+ for (DataEvent dataEvent : dataEvents) {
+ if (dataEvent.getType() != DataEvent.TYPE_CHANGED) {
+ continue;
}
- } finally {
- dataEvents.close();
+
+ 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);
}
}
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
@@ -170,6 +170,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)) {
@@ -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.
-->
-
-
-
+ android:layout_height="match_parent">
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:weightSum="100">
-
+
+
+ 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">
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
-
-
-
\ No newline at end of file
+
\ 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" />
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/browseable/XYZTouristAttractions/Application/res/transition-v21/explode.xml b/samples/browseable/XYZTouristAttractions/Application/res/transition-v21/window_enter.xml
similarity index 77%
rename from samples/browseable/XYZTouristAttractions/Application/res/transition-v21/explode.xml
rename to 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.
-->
-
+
@@ -25,4 +26,11 @@
+
+
+
+
+
+
\ 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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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
similarity index 59%
rename from samples/browseable/XYZTouristAttractions/Application/res/menu/detail.xml
rename to 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 @@
+
-
+
\ 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 @@
12dp8dp140dp
+ 0dp
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 @@
+
+
+
+
+
+ 8dp
+
+
\ 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 @@
true@transition/fade@transition/fade
- @transition/explode
- @transition/explode
+ @transition/window_enter
+ @transition/window_return
+ @transition/shared_move
+ @transition/shared_movefalsefalsefalse
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 @@
8dp4dp120dp
+ -8dp@dimen/standard_margin72dp@dimen/standard_margin
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 @@
#4e6cef#2a36b1#ff7043
+ #D84315
\ 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 @@
+ android:targetSdkVersion="22" />
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())) {