Sync sample prebuilts for mnc-dev

Synced to //developers/samples/android commit 89d2da0f4b.

Change-Id: I904da8210517922531d1ac1ba1e747f1c7bf00b3
This commit is contained in:
Trevor Johns
2015-08-13 21:10:42 -07:00
parent 40bd1993ed
commit d95a687e5a
73 changed files with 1148 additions and 437 deletions

View File

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

View File

@@ -75,7 +75,6 @@ public class HomeListenerService extends WearableListenerService {
UpdateNotificationForDataItem(event.getDataItem());
}
}
dataEvents.close();
}
@Override

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 didnt complete the lock screen
// operation. Go to error/cancellation flow.

View File

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

View File

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

View File

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

View File

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

View File

@@ -95,7 +95,6 @@ public class SoundAlarmListenerService extends WearableListenerService {
}
}
}
dataEvents.close();
}
}

View File

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

View File

@@ -35,7 +35,9 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".SettingsActivity"
android:label="@string/action_settings" />
</application>
</manifest>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -79,7 +79,6 @@ public class HomeListenerService extends WearableListenerService {
postNotificationForGeofenceId(geofenceId, event.getDataItem().getUri());
}
}
dataEvents.close();
}
/**

View File

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

View File

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

View File

@@ -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 &quot;don\'t ask again&quot; 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>

View File

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

View File

@@ -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,18 +84,25 @@ 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.
*/
@@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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">
android:layout_width="match_parent"
android:layout_height="match_parent"
android:weightSum="100">
<TextView
android:id="@+id/nameTextView"
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="50"
android:scaleType="centerCrop"
android:transitionName="image" />
<LinearLayout
android:id="@+id/textLayout"
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" />
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/distanceTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/TextAppearance.AppCompat.Subhead.Inverse" />
<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">
<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>
<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"
android:layout_width="match_parent"
android:textIsSelectable="true"
style="@style/TextAppearance.AppCompat.Body1"
android:paddingStart="@dimen/keyline2"
android:paddingEnd="@dimen/keyline3" />
<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" />
</ScrollView>
</LinearLayout>
</android.support.design.widget.CoordinatorLayout>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -216,7 +216,7 @@ public class AttractionListFragment extends Fragment {
@Override
public void onClick(View v) {
mItemClickListener.onItemClick(v, getPosition());
mItemClickListener.onItemClick(v, getAdapterPosition());
}
}

View File

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

View File

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

View File

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

View File

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

View File

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