Sync sample prebuilts for mnc-dev
Synced to //developers/samples/android commit 89d2da0f4b. Change-Id: I904da8210517922531d1ac1ba1e747f1c7bf00b3
This commit is contained in:
@@ -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.ConnectionCallbacks;
|
||||||
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
|
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
|
||||||
import com.google.android.gms.common.api.ResultCallback;
|
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.DataApi;
|
||||||
import com.google.android.gms.wearable.DataItem;
|
import com.google.android.gms.wearable.DataItem;
|
||||||
import com.google.android.gms.wearable.DataItemBuffer;
|
import com.google.android.gms.wearable.DataItemBuffer;
|
||||||
@@ -96,28 +95,29 @@ public class MainActivity extends Activity implements NodeApi.NodeListener, Conn
|
|||||||
.setResultCallback(new ResultCallback<DataItemBuffer>() {
|
.setResultCallback(new ResultCallback<DataItemBuffer>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResult(DataItemBuffer result) {
|
public void onResult(DataItemBuffer result) {
|
||||||
if (result.getStatus().isSuccess()) {
|
try {
|
||||||
deleteDataItems(result);
|
if (result.getStatus().isSuccess()) {
|
||||||
} else {
|
deleteDataItems(result);
|
||||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
} else {
|
||||||
Log.d(TAG, "onDeleteEventsClicked(): failed to get Data Items");
|
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||||
|
Log.d(TAG,"onDeleteEventsClicked(): failed to get Data "
|
||||||
|
+ "Items");
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
result.release();
|
||||||
}
|
}
|
||||||
result.close();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "Failed to delete data items"
|
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()) {
|
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) {
|
for (final DataItem dataItem : dataItemList) {
|
||||||
final Uri dataItemUri = dataItem.getUri();
|
final Uri dataItemUri = dataItem.getUri();
|
||||||
// In a real calendar application, this might delete the corresponding calendar
|
// In a real calendar application, this might delete the corresponding calendar
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ public class HomeListenerService extends WearableListenerService {
|
|||||||
UpdateNotificationForDataItem(event.getDataItem());
|
UpdateNotificationForDataItem(event.getDataItem());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dataEvents.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -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;
|
package com.example.android.bluetoothadvertisements;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
|||||||
@@ -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;
|
package com.example.android.bluetoothadvertisements;
|
||||||
|
|
||||||
import android.os.ParcelUuid;
|
import android.os.ParcelUuid;
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2013 The Android Open Source Project
|
* Copyright (C) 2015 The Android Open Source Project
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.example.android.bluetoothadvertisements;
|
package com.example.android.bluetoothadvertisements;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
package com.example.android.bluetoothadvertisements;
|
||||||
|
|
||||||
import android.bluetooth.le.ScanResult;
|
import android.bluetooth.le.ScanResult;
|
||||||
|
|||||||
@@ -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;
|
package com.example.android.bluetoothadvertisements;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
|||||||
@@ -15,16 +15,14 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.example.android.camera2basic"
|
package="com.example.android.camera2basic">
|
||||||
android:versionCode="1"
|
|
||||||
android:versionName="1.0">
|
|
||||||
|
|
||||||
<!-- Min/target SDK versions (<uses-sdk>) managed by build.gradle -->
|
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<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"
|
<application android:allowBackup="true"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:icon="@drawable/ic_launcher"
|
android:icon="@drawable/ic_launcher"
|
||||||
|
|||||||
@@ -26,14 +26,14 @@
|
|||||||
android:layout_alignParentTop="true" />
|
android:layout_alignParentTop="true" />
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
|
android:id="@+id/control"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentBottom="true"
|
android:layout_alignParentBottom="true"
|
||||||
android:layout_alignParentEnd="true"
|
android:layout_alignParentEnd="true"
|
||||||
android:layout_alignParentTop="true"
|
android:layout_alignParentTop="true"
|
||||||
android:layout_below="@id/texture"
|
|
||||||
android:layout_toRightOf="@id/texture"
|
android:layout_toRightOf="@id/texture"
|
||||||
android:background="#4285f4"
|
android:background="@color/control_background"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -25,12 +25,12 @@
|
|||||||
android:layout_alignParentTop="true" />
|
android:layout_alignParentTop="true" />
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
|
android:id="@+id/control"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="112dp"
|
||||||
android:layout_alignParentBottom="true"
|
android:layout_alignParentBottom="true"
|
||||||
android:layout_alignParentStart="true"
|
android:layout_alignParentStart="true"
|
||||||
android:layout_below="@id/texture"
|
android:background="@color/control_background">
|
||||||
android:background="#4285f4">
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/picture"
|
android:id="@+id/picture"
|
||||||
|
|||||||
19
samples/browseable/Camera2Basic/res/values/colors.xml
Normal file
19
samples/browseable/Camera2Basic/res/values/colors.xml
Normal 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>
|
||||||
@@ -55,7 +55,6 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
@@ -264,14 +263,16 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case STATE_WAITING_LOCK: {
|
case STATE_WAITING_LOCK: {
|
||||||
int afState = result.get(CaptureResult.CONTROL_AF_STATE);
|
Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
|
||||||
if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState ||
|
if (afState == null) {
|
||||||
|
captureStillPicture();
|
||||||
|
} else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState ||
|
||||||
CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) {
|
CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) {
|
||||||
// CONTROL_AE_STATE can be null on some devices
|
// CONTROL_AE_STATE can be null on some devices
|
||||||
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
|
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
|
||||||
if (aeState == null ||
|
if (aeState == null ||
|
||||||
aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
|
aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
|
||||||
mState = STATE_WAITING_NON_PRECAPTURE;
|
mState = STATE_PICTURE_TAKEN;
|
||||||
captureStillPicture();
|
captureStillPicture();
|
||||||
} else {
|
} else {
|
||||||
runPrecaptureSequence();
|
runPrecaptureSequence();
|
||||||
@@ -636,6 +637,8 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
|
|||||||
(float) viewWidth / mPreviewSize.getWidth());
|
(float) viewWidth / mPreviewSize.getWidth());
|
||||||
matrix.postScale(scale, scale, centerX, centerY);
|
matrix.postScale(scale, scale, centerX, centerY);
|
||||||
matrix.postRotate(90 * (rotation - 2), centerX, centerY);
|
matrix.postRotate(90 * (rotation - 2), centerX, centerY);
|
||||||
|
} else if (Surface.ROTATION_180 == rotation) {
|
||||||
|
matrix.postRotate(180, centerX, centerY);
|
||||||
}
|
}
|
||||||
mTextureView.setTransform(matrix);
|
mTextureView.setTransform(matrix);
|
||||||
}
|
}
|
||||||
@@ -657,7 +660,7 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
|
|||||||
CameraMetadata.CONTROL_AF_TRIGGER_START);
|
CameraMetadata.CONTROL_AF_TRIGGER_START);
|
||||||
// Tell #mCaptureCallback to wait for the lock.
|
// Tell #mCaptureCallback to wait for the lock.
|
||||||
mState = STATE_WAITING_LOCK;
|
mState = STATE_WAITING_LOCK;
|
||||||
mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback,
|
mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
|
||||||
mBackgroundHandler);
|
mBackgroundHandler);
|
||||||
} catch (CameraAccessException e) {
|
} catch (CameraAccessException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -794,8 +797,6 @@ public class Camera2BasicFragment extends Fragment implements View.OnClickListen
|
|||||||
try {
|
try {
|
||||||
output = new FileOutputStream(mFile);
|
output = new FileOutputStream(mFile);
|
||||||
output.write(bytes);
|
output.write(bytes);
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ public class MainActivity extends Activity {
|
|||||||
* Tries to encrypt some data with the generated key in {@link #createKey} which is
|
* 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.
|
* only works if the user has just authenticated via device credentials.
|
||||||
*/
|
*/
|
||||||
private void tryEncrypt() {
|
private boolean tryEncrypt() {
|
||||||
try {
|
try {
|
||||||
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
|
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
|
||||||
keyStore.load(null);
|
keyStore.load(null);
|
||||||
@@ -111,15 +111,18 @@ public class MainActivity extends Activity {
|
|||||||
|
|
||||||
// If the user has recently authenticated, you will reach here.
|
// If the user has recently authenticated, you will reach here.
|
||||||
showAlreadyAuthenticated();
|
showAlreadyAuthenticated();
|
||||||
|
return true;
|
||||||
} catch (UserNotAuthenticatedException e) {
|
} catch (UserNotAuthenticatedException e) {
|
||||||
// User is not authenticated, let's authenticate with device credentials.
|
// User is not authenticated, let's authenticate with device credentials.
|
||||||
showAuthenticationScreen();
|
showAuthenticationScreen();
|
||||||
|
return false;
|
||||||
} catch (KeyPermanentlyInvalidatedException e) {
|
} catch (KeyPermanentlyInvalidatedException e) {
|
||||||
// This happens if the lock screen has been disabled or reset after the key was
|
// This happens if the lock screen has been disabled or reset after the key was
|
||||||
// generated after the key was generated.
|
// generated after the key was generated.
|
||||||
Toast.makeText(this, "Keys are invalidated after created. Retry the purchase\n"
|
Toast.makeText(this, "Keys are invalidated after created. Retry the purchase\n"
|
||||||
+ e.getMessage(),
|
+ e.getMessage(),
|
||||||
Toast.LENGTH_LONG).show();
|
Toast.LENGTH_LONG).show();
|
||||||
|
return false;
|
||||||
} catch (BadPaddingException | IllegalBlockSizeException | KeyStoreException |
|
} catch (BadPaddingException | IllegalBlockSizeException | KeyStoreException |
|
||||||
CertificateException | UnrecoverableKeyException | IOException
|
CertificateException | UnrecoverableKeyException | IOException
|
||||||
| NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
|
| NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
|
||||||
@@ -172,7 +175,9 @@ public class MainActivity extends Activity {
|
|||||||
if (requestCode == REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) {
|
if (requestCode == REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) {
|
||||||
// Challenge completed, proceed with using cipher
|
// Challenge completed, proceed with using cipher
|
||||||
if (resultCode == RESULT_OK) {
|
if (resultCode == RESULT_OK) {
|
||||||
showPurchaseConfirmation();
|
if (tryEncrypt()) {
|
||||||
|
showPurchaseConfirmation();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// The user canceled or didn’t complete the lock screen
|
// The user canceled or didn’t complete the lock screen
|
||||||
// operation. Go to error/cancellation flow.
|
// operation. Go to error/cancellation flow.
|
||||||
|
|||||||
@@ -216,8 +216,8 @@ public class MainActivity extends Activity implements DataApi.DataListener,
|
|||||||
@Override //DataListener
|
@Override //DataListener
|
||||||
public void onDataChanged(DataEventBuffer dataEvents) {
|
public void onDataChanged(DataEventBuffer dataEvents) {
|
||||||
LOGD(TAG, "onDataChanged: " + 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);
|
final List<DataEvent> events = FreezableUtils.freezeIterable(dataEvents);
|
||||||
dataEvents.close();
|
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import android.util.Log;
|
|||||||
|
|
||||||
import com.google.android.gms.common.ConnectionResult;
|
import com.google.android.gms.common.ConnectionResult;
|
||||||
import com.google.android.gms.common.api.GoogleApiClient;
|
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.DataEvent;
|
||||||
import com.google.android.gms.wearable.DataEventBuffer;
|
import com.google.android.gms.wearable.DataEventBuffer;
|
||||||
import com.google.android.gms.wearable.MessageEvent;
|
import com.google.android.gms.wearable.MessageEvent;
|
||||||
@@ -59,8 +58,6 @@ public class DataLayerListenerService extends WearableListenerService {
|
|||||||
@Override
|
@Override
|
||||||
public void onDataChanged(DataEventBuffer dataEvents) {
|
public void onDataChanged(DataEventBuffer dataEvents) {
|
||||||
LOGD(TAG, "onDataChanged: " + dataEvents);
|
LOGD(TAG, "onDataChanged: " + dataEvents);
|
||||||
final List<DataEvent> events = FreezableUtils.freezeIterable(dataEvents);
|
|
||||||
dataEvents.close();
|
|
||||||
if (!mGoogleApiClient.isConnected() || !mGoogleApiClient.isConnecting()) {
|
if (!mGoogleApiClient.isConnected() || !mGoogleApiClient.isConnecting()) {
|
||||||
ConnectionResult connectionResult = mGoogleApiClient
|
ConnectionResult connectionResult = mGoogleApiClient
|
||||||
.blockingConnect(30, TimeUnit.SECONDS);
|
.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.
|
// 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();
|
Uri uri = event.getDataItem().getUri();
|
||||||
String path = uri.getPath();
|
String path = uri.getPath();
|
||||||
if (COUNT_PATH.equals(path)) {
|
if (COUNT_PATH.equals(path)) {
|
||||||
|
|||||||
@@ -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.ConnectionCallbacks;
|
||||||
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
|
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
|
||||||
import com.google.android.gms.common.api.ResultCallback;
|
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.Asset;
|
||||||
import com.google.android.gms.wearable.CapabilityApi;
|
import com.google.android.gms.wearable.CapabilityApi;
|
||||||
import com.google.android.gms.wearable.CapabilityInfo;
|
import com.google.android.gms.wearable.CapabilityInfo;
|
||||||
@@ -151,9 +150,7 @@ public class MainActivity extends Activity implements ConnectionCallbacks,
|
|||||||
public void onDataChanged(DataEventBuffer dataEvents) {
|
public void onDataChanged(DataEventBuffer dataEvents) {
|
||||||
LOGD(TAG, "onDataChanged(): " + dataEvents);
|
LOGD(TAG, "onDataChanged(): " + dataEvents);
|
||||||
|
|
||||||
final List<DataEvent> events = FreezableUtils.freezeIterable(dataEvents);
|
for (DataEvent event : dataEvents) {
|
||||||
dataEvents.close();
|
|
||||||
for (DataEvent event : events) {
|
|
||||||
if (event.getType() == DataEvent.TYPE_CHANGED) {
|
if (event.getType() == DataEvent.TYPE_CHANGED) {
|
||||||
String path = event.getDataItem().getUri().getPath();
|
String path = event.getDataItem().getUri().getPath();
|
||||||
if (DataLayerListenerService.IMAGE_PATH.equals(path)) {
|
if (DataLayerListenerService.IMAGE_PATH.equals(path)) {
|
||||||
|
|||||||
@@ -55,11 +55,12 @@ public class MainActivity extends Activity {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
mHistoryView = (TextView) findViewById(R.id.history);
|
mHistoryView = (TextView) findViewById(R.id.history);
|
||||||
startResponderService();
|
startResponderService(ResponderService.ACTION_INCOMING);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startResponderService() {
|
private void startResponderService(String action) {
|
||||||
Intent serviceIntent = new Intent(ResponderService.ACTION_INCOMING);
|
Intent serviceIntent = new Intent(this, ResponderService.class);
|
||||||
|
serviceIntent.setAction(action);
|
||||||
startService(serviceIntent);
|
startService(serviceIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,9 +70,7 @@ public class MainActivity extends Activity {
|
|||||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver,
|
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver,
|
||||||
new IntentFilter(ACTION_NOTIFY));
|
new IntentFilter(ACTION_NOTIFY));
|
||||||
mHistoryView.setText("");
|
mHistoryView.setText("");
|
||||||
Intent serviceIntent = new Intent(ACTION_GET_CONVERSATION);
|
startResponderService(ACTION_GET_CONVERSATION);
|
||||||
startService(serviceIntent);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -95,7 +95,6 @@ public class SoundAlarmListenerService extends WearableListenerService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dataEvents.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,19 +73,22 @@ public class FindPhoneService extends IntentService implements GoogleApiClient.C
|
|||||||
if (intent.getAction().equals(ACTION_TOGGLE_ALARM)) {
|
if (intent.getAction().equals(ACTION_TOGGLE_ALARM)) {
|
||||||
// Get current state of the alarm.
|
// Get current state of the alarm.
|
||||||
DataItemBuffer result = Wearable.DataApi.getDataItems(mGoogleApiClient).await();
|
DataItemBuffer result = Wearable.DataApi.getDataItems(mGoogleApiClient).await();
|
||||||
if (result.getStatus().isSuccess()) {
|
try {
|
||||||
if (result.getCount() == 1) {
|
if (result.getStatus().isSuccess()) {
|
||||||
alarmOn = DataMap.fromByteArray(result.get(0).getData())
|
if (result.getCount() == 1) {
|
||||||
.getBoolean(FIELD_ALARM_ON, false);
|
alarmOn = DataMap.fromByteArray(result.get(0).getData())
|
||||||
} else {
|
.getBoolean(FIELD_ALARM_ON, false);
|
||||||
Log.e(TAG, "Unexpected number of DataItems found.\n"
|
} else {
|
||||||
+ "\tExpected: 1\n"
|
Log.e(TAG, "Unexpected number of DataItems found.\n"
|
||||||
+ "\tActual: " + result.getCount());
|
+ "\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)) {
|
} finally {
|
||||||
Log.d(TAG, "onHandleIntent: failed to get current alarm state");
|
result.release();
|
||||||
}
|
}
|
||||||
result.close();
|
|
||||||
// Toggle alarm.
|
// Toggle alarm.
|
||||||
alarmOn = !alarmOn;
|
alarmOn = !alarmOn;
|
||||||
// Change notification text based on new value of alarmOn.
|
// Change notification text based on new value of alarmOn.
|
||||||
|
|||||||
@@ -35,7 +35,9 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".SettingsActivity"
|
||||||
|
android:label="@string/action_settings" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -16,21 +16,37 @@
|
|||||||
-->
|
-->
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/backup_container"
|
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:paddingTop="16dp"
|
||||||
android:paddingBottom="8dp">
|
android:paddingBottom="8dp">
|
||||||
|
|
||||||
<TextView
|
<FrameLayout
|
||||||
|
android:id="@+id/description"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="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_alignParentTop="true"
|
||||||
android:layout_alignParentStart="true"
|
android:layout_alignParentStart="true"
|
||||||
android:layout_marginStart="24dp"
|
android:layout_marginStart="24dp"
|
||||||
android:layout_marginEnd="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
|
<EditText
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@@ -46,4 +62,17 @@
|
|||||||
android:layout_marginEnd="20dp"
|
android:layout_marginEnd="20dp"
|
||||||
android:layout_alignParentStart="true" />
|
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>
|
</RelativeLayout>
|
||||||
@@ -19,8 +19,8 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:paddingBottom="8dp"
|
android:paddingBottom="8dp"
|
||||||
android:paddingLeft="24dp"
|
android:paddingStart="24dp"
|
||||||
android:paddingRight="24dp"
|
android:paddingEnd="24dp"
|
||||||
android:paddingTop="16dp">
|
android:paddingTop="16dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignBottom="@+id/fingerprint_icon"
|
android:layout_alignBottom="@+id/fingerprint_icon"
|
||||||
android:layout_alignTop="@+id/fingerprint_icon"
|
android:layout_alignTop="@+id/fingerprint_icon"
|
||||||
android:layout_marginLeft="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_toEndOf="@+id/fingerprint_icon"
|
android:layout_toEndOf="@+id/fingerprint_icon"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:text="@string/fingerprint_hint"
|
android:text="@string/fingerprint_hint"
|
||||||
|
|||||||
21
samples/browseable/FingerprintDialog/res/menu/menu_main.xml
Normal file
21
samples/browseable/FingerprintDialog/res/menu/menu_main.xml
Normal 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>
|
||||||
@@ -31,4 +31,8 @@
|
|||||||
<string name="item_price">$62.68</string>
|
<string name="item_price">$62.68</string>
|
||||||
<string name="item_description">Mesh backpack in white. Black textile trim throughout.</string>
|
<string name="item_description">Mesh backpack in white. Black textile trim throughout.</string>
|
||||||
<string name="purchase_done">Purchase successful</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>
|
</resources>
|
||||||
|
|||||||
23
samples/browseable/FingerprintDialog/res/xml/preferences.xml
Normal file
23
samples/browseable/FingerprintDialog/res/xml/preferences.xml
Normal 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>
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
package com.example.android.fingerprintdialog;
|
package com.example.android.fingerprintdialog;
|
||||||
|
|
||||||
import android.app.DialogFragment;
|
import android.app.DialogFragment;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.hardware.fingerprint.FingerprintManager;
|
import android.hardware.fingerprint.FingerprintManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
@@ -26,6 +27,7 @@ import android.view.ViewGroup;
|
|||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
import android.widget.CheckBox;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
@@ -44,6 +46,9 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment
|
|||||||
private View mFingerprintContent;
|
private View mFingerprintContent;
|
||||||
private View mBackupContent;
|
private View mBackupContent;
|
||||||
private EditText mPassword;
|
private EditText mPassword;
|
||||||
|
private CheckBox mUseFingerprintFutureCheckBox;
|
||||||
|
private TextView mPasswordDescriptionTextView;
|
||||||
|
private TextView mNewFingerprintEnrolledTextView;
|
||||||
|
|
||||||
private Stage mStage = Stage.FINGERPRINT;
|
private Stage mStage = Stage.FINGERPRINT;
|
||||||
|
|
||||||
@@ -52,6 +57,7 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment
|
|||||||
|
|
||||||
@Inject FingerprintUiHelper.FingerprintUiHelperBuilder mFingerprintUiHelperBuilder;
|
@Inject FingerprintUiHelper.FingerprintUiHelperBuilder mFingerprintUiHelperBuilder;
|
||||||
@Inject InputMethodManager mInputMethodManager;
|
@Inject InputMethodManager mInputMethodManager;
|
||||||
|
@Inject SharedPreferences mSharedPreferences;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public FingerprintAuthenticationDialogFragment() {}
|
public FingerprintAuthenticationDialogFragment() {}
|
||||||
@@ -93,6 +99,11 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment
|
|||||||
mBackupContent = v.findViewById(R.id.backup_container);
|
mBackupContent = v.findViewById(R.id.backup_container);
|
||||||
mPassword = (EditText) v.findViewById(R.id.password);
|
mPassword = (EditText) v.findViewById(R.id.password);
|
||||||
mPassword.setOnEditorActionListener(this);
|
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(
|
mFingerprintUiHelper = mFingerprintUiHelperBuilder.build(
|
||||||
(ImageView) v.findViewById(R.id.fingerprint_icon),
|
(ImageView) v.findViewById(R.id.fingerprint_icon),
|
||||||
(TextView) v.findViewById(R.id.fingerprint_status), this);
|
(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
|
@Override
|
||||||
public void onPause() {
|
public void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
@@ -149,12 +164,25 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment
|
|||||||
* let's the activity know about the result.
|
* let's the activity know about the result.
|
||||||
*/
|
*/
|
||||||
private void verifyPassword() {
|
private void verifyPassword() {
|
||||||
if (checkPassword(mPassword.getText().toString())) {
|
if (!checkPassword(mPassword.getText().toString())) {
|
||||||
((MainActivity) getActivity()).onPurchased(false /* without Fingerprint */);
|
return;
|
||||||
dismiss();
|
|
||||||
} else {
|
|
||||||
// assume the password is always correct.
|
|
||||||
}
|
}
|
||||||
|
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);
|
mFingerprintContent.setVisibility(View.VISIBLE);
|
||||||
mBackupContent.setVisibility(View.GONE);
|
mBackupContent.setVisibility(View.GONE);
|
||||||
break;
|
break;
|
||||||
|
case NEW_FINGERPRINT_ENROLLED:
|
||||||
|
// Intentional fall through
|
||||||
case PASSWORD:
|
case PASSWORD:
|
||||||
mCancelButton.setText(R.string.cancel);
|
mCancelButton.setText(R.string.cancel);
|
||||||
mSecondDialogButton.setText(R.string.ok);
|
mSecondDialogButton.setText(R.string.ok);
|
||||||
mFingerprintContent.setVisibility(View.GONE);
|
mFingerprintContent.setVisibility(View.GONE);
|
||||||
mBackupContent.setVisibility(View.VISIBLE);
|
mBackupContent.setVisibility(View.VISIBLE);
|
||||||
|
if (mStage == Stage.NEW_FINGERPRINT_ENROLLED) {
|
||||||
|
mPasswordDescriptionTextView.setVisibility(View.GONE);
|
||||||
|
mNewFingerprintEnrolledTextView.setVisibility(View.VISIBLE);
|
||||||
|
mUseFingerprintFutureCheckBox.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,8 +250,9 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment
|
|||||||
/**
|
/**
|
||||||
* Enumeration to indicate which authentication method the user is trying to authenticate with.
|
* Enumeration to indicate which authentication method the user is trying to authenticate with.
|
||||||
*/
|
*/
|
||||||
private enum Stage {
|
public enum Stage {
|
||||||
FINGERPRINT,
|
FINGERPRINT,
|
||||||
|
NEW_FINGERPRINT_ENROLLED,
|
||||||
PASSWORD
|
PASSWORD
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,10 @@ package com.example.android.fingerprintdialog;
|
|||||||
|
|
||||||
import android.app.KeyguardManager;
|
import android.app.KeyguardManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.hardware.fingerprint.FingerprintManager;
|
import android.hardware.fingerprint.FingerprintManager;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
import android.security.keystore.KeyProperties;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
@@ -75,7 +78,7 @@ public class FingerprintModule {
|
|||||||
@Provides
|
@Provides
|
||||||
public KeyGenerator providesKeyGenerator() {
|
public KeyGenerator providesKeyGenerator() {
|
||||||
try {
|
try {
|
||||||
return KeyGenerator.getInstance("AES", "AndroidKeyStore");
|
return KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
|
||||||
} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
|
} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
|
||||||
throw new RuntimeException("Failed to get an instance of KeyGenerator", e);
|
throw new RuntimeException("Failed to get an instance of KeyGenerator", e);
|
||||||
}
|
}
|
||||||
@@ -84,7 +87,9 @@ public class FingerprintModule {
|
|||||||
@Provides
|
@Provides
|
||||||
public Cipher providesCipher(KeyStore keyStore) {
|
public Cipher providesCipher(KeyStore keyStore) {
|
||||||
try {
|
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) {
|
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
|
||||||
throw new RuntimeException("Failed to get an instance of Cipher", e);
|
throw new RuntimeException("Failed to get an instance of Cipher", e);
|
||||||
}
|
}
|
||||||
@@ -94,4 +99,9 @@ public class FingerprintModule {
|
|||||||
public InputMethodManager providesInputMethodManager(Context context) {
|
public InputMethodManager providesInputMethodManager(Context context) {
|
||||||
return (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
|
return (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
public SharedPreferences providesSharedPreferences(Context context) {
|
||||||
|
return PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,8 @@ public class FingerprintUiHelper extends FingerprintManager.AuthenticationCallba
|
|||||||
}
|
}
|
||||||
mCancellationSignal = new CancellationSignal();
|
mCancellationSignal = new CancellationSignal();
|
||||||
mSelfCancelled = false;
|
mSelfCancelled = false;
|
||||||
mFingerprintManager.authenticate(cryptoObject, mCancellationSignal, this, 0 /* flags */);
|
mFingerprintManager
|
||||||
|
.authenticate(cryptoObject, mCancellationSignal, 0 /* flags */, this, null);
|
||||||
mIcon.setImageResource(R.drawable.ic_fp_40px);
|
mIcon.setImageResource(R.drawable.ic_fp_40px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ package com.example.android.fingerprintdialog;
|
|||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.KeyguardManager;
|
import android.app.KeyguardManager;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.hardware.fingerprint.FingerprintManager;
|
import android.hardware.fingerprint.FingerprintManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -27,6 +29,8 @@ import android.security.keystore.KeyPermanentlyInvalidatedException;
|
|||||||
import android.security.keystore.KeyProperties;
|
import android.security.keystore.KeyProperties;
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
@@ -60,23 +64,29 @@ public class MainActivity extends Activity {
|
|||||||
/** Alias for our key in the Android Key Store */
|
/** Alias for our key in the Android Key Store */
|
||||||
private static final String KEY_NAME = "my_key";
|
private static final String KEY_NAME = "my_key";
|
||||||
|
|
||||||
|
private static final int FINGERPRINT_PERMISSION_REQUEST_CODE = 0;
|
||||||
|
|
||||||
@Inject KeyguardManager mKeyguardManager;
|
@Inject KeyguardManager mKeyguardManager;
|
||||||
|
@Inject FingerprintManager mFingerprintManager;
|
||||||
@Inject FingerprintAuthenticationDialogFragment mFragment;
|
@Inject FingerprintAuthenticationDialogFragment mFragment;
|
||||||
@Inject KeyStore mKeyStore;
|
@Inject KeyStore mKeyStore;
|
||||||
@Inject KeyGenerator mKeyGenerator;
|
@Inject KeyGenerator mKeyGenerator;
|
||||||
@Inject Cipher mCipher;
|
@Inject Cipher mCipher;
|
||||||
|
@Inject SharedPreferences mSharedPreferences;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
((InjectedApplication) getApplication()).inject(this);
|
((InjectedApplication) getApplication()).inject(this);
|
||||||
|
|
||||||
requestPermissions(new String[]{Manifest.permission.USE_FINGERPRINT}, 0);
|
requestPermissions(new String[]{Manifest.permission.USE_FINGERPRINT},
|
||||||
|
FINGERPRINT_PERMISSION_REQUEST_CODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] state) {
|
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);
|
setContentView(R.layout.activity_main);
|
||||||
Button purchaseButton = (Button) findViewById(R.id.purchase_button);
|
Button purchaseButton = (Button) findViewById(R.id.purchase_button);
|
||||||
if (!mKeyguardManager.isKeyguardSecure()) {
|
if (!mKeyguardManager.isKeyguardSecure()) {
|
||||||
@@ -88,35 +98,71 @@ public class MainActivity extends Activity {
|
|||||||
purchaseButton.setEnabled(false);
|
purchaseButton.setEnabled(false);
|
||||||
return;
|
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();
|
createKey();
|
||||||
|
purchaseButton.setEnabled(true);
|
||||||
purchaseButton.setOnClickListener(new View.OnClickListener() {
|
purchaseButton.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
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
|
// Set up the crypto object for later. The object will be authenticated by use
|
||||||
// crypto, or you can fall back to using a server-side verified password.
|
// of the fingerprint.
|
||||||
mFragment.setCryptoObject(new FingerprintManager.CryptoObject(mCipher));
|
if (initCipher()) {
|
||||||
mFragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
|
|
||||||
|
// 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 {
|
try {
|
||||||
mKeyStore.load(null);
|
mKeyStore.load(null);
|
||||||
SecretKey key = (SecretKey) mKeyStore.getKey(KEY_NAME, null);
|
SecretKey key = (SecretKey) mKeyStore.getKey(KEY_NAME, null);
|
||||||
mCipher.init(Cipher.ENCRYPT_MODE, key);
|
mCipher.init(Cipher.ENCRYPT_MODE, key);
|
||||||
|
return true;
|
||||||
} catch (KeyPermanentlyInvalidatedException e) {
|
} catch (KeyPermanentlyInvalidatedException e) {
|
||||||
// This happens if the lock screen has been disabled or reset after the key was
|
return false;
|
||||||
// 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();
|
|
||||||
} catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException
|
} catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException
|
||||||
| NoSuchAlgorithmException | InvalidKeyException e) {
|
| NoSuchAlgorithmException | InvalidKeyException e) {
|
||||||
throw new RuntimeException("Failed to init Cipher", e);
|
throw new RuntimeException("Failed to init Cipher", e);
|
||||||
@@ -124,7 +170,6 @@ public class MainActivity extends Activity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void onPurchased(boolean withFingerprint) {
|
public void onPurchased(boolean withFingerprint) {
|
||||||
findViewById(R.id.purchase_button).setVisibility(View.GONE);
|
|
||||||
if (withFingerprint) {
|
if (withFingerprint) {
|
||||||
// If the user has authenticated with fingerprint, verify that using cryptography and
|
// If the user has authenticated with fingerprint, verify that using cryptography and
|
||||||
// then show the confirmation message.
|
// 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
|
* Creates a symmetric key in the Android Key Store which can only be used after the user has
|
||||||
* authenticated with fingerprint.
|
* 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
|
// 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
|
// for your flow. Use of keys is necessary if you need to know if the set of
|
||||||
// enrolled fingerprints has changed.
|
// enrolled fingerprints has changed.
|
||||||
@@ -187,4 +232,22 @@ public class MainActivity extends Activity {
|
|||||||
throw new RuntimeException(e);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -79,7 +79,6 @@ public class HomeListenerService extends WearableListenerService {
|
|||||||
postNotificationForGeofenceId(geofenceId, event.getDataItem().getUri());
|
postNotificationForGeofenceId(geofenceId, event.getDataItem().getUri());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dataEvents.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -20,10 +20,6 @@
|
|||||||
android:versionCode="1"
|
android:versionCode="1"
|
||||||
android:versionName="1.0">
|
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"/>
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.camera.front"
|
android:name="android.hardware.camera.front"
|
||||||
@@ -31,8 +27,6 @@
|
|||||||
|
|
||||||
<uses-permission android:name="android.permission.CAMERA"/>
|
<uses-permission android:name="android.permission.CAMERA"/>
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
<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
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
@@ -43,7 +37,8 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".HdrViewfinderActivity"
|
android:name=".HdrViewfinderActivity"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:screenOrientation="landscape">
|
android:screenOrientation="landscape"
|
||||||
|
android:theme="@style/Theme.AppCompat.Light">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
|||||||
@@ -15,13 +15,14 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/panels"
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:custom="http://schemas.android.com/apk/res-auto"
|
xmlns:custom="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/panels"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="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
|
<com.example.android.hdrviewfinder.FixedAspectSurfaceView
|
||||||
android:id="@+id/preview"
|
android:id="@+id/preview"
|
||||||
@@ -36,7 +37,8 @@
|
|||||||
android:layout_width="0px"
|
android:layout_width="0px"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical"
|
||||||
|
android:layout_margin="5dp">
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/help_button"
|
android:id="@+id/help_button"
|
||||||
|
|||||||
@@ -50,10 +50,18 @@
|
|||||||
|
|
||||||
<string name="info">Info</string>
|
<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_no_good">No back-facing sufficiently capable camera available!</string>
|
||||||
<string name="camera_disabled">Camera is disabled by device policy</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_disconnected">Camera was disconnected before it was opened</string>
|
||||||
<string name="camera_error">Camera service reported an error</string>
|
<string name="camera_error">Camera service reported an error</string>
|
||||||
<string name="camera_unknown">Unknown camera error: %s</string>
|
<string name="camera_unknown">Unknown camera error: %s</string>
|
||||||
|
<string name="camera_permission_denied_explanation">You\'ve denied a permission that the app
|
||||||
|
needs for core functionality. If you selected "don\'t ask again" in the past then
|
||||||
|
you need to use Settings to re-enable the permission.</string>
|
||||||
|
|
||||||
|
<string name="ok">OK</string>
|
||||||
|
<string name="settings">Settings</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -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.
|
* Displays a dialog if it cannot open a camera.
|
||||||
*/
|
*/
|
||||||
public void openCamera(final String cameraId) {
|
public void openCamera(final String cameraId) {
|
||||||
|
|||||||
@@ -16,7 +16,9 @@
|
|||||||
|
|
||||||
package com.example.android.hdrviewfinder;
|
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.CameraAccessException;
|
||||||
import android.hardware.camera2.CameraCaptureSession;
|
import android.hardware.camera2.CameraCaptureSession;
|
||||||
import android.hardware.camera2.CameraCharacteristics;
|
import android.hardware.camera2.CameraCharacteristics;
|
||||||
@@ -26,10 +28,16 @@ import android.hardware.camera2.CaptureRequest;
|
|||||||
import android.hardware.camera2.CaptureResult;
|
import android.hardware.camera2.CaptureResult;
|
||||||
import android.hardware.camera2.TotalCaptureResult;
|
import android.hardware.camera2.TotalCaptureResult;
|
||||||
import android.hardware.camera2.params.StreamConfigurationMap;
|
import android.hardware.camera2.params.StreamConfigurationMap;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
|
import android.provider.Settings;
|
||||||
import android.renderscript.RenderScript;
|
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.Log;
|
||||||
import android.util.Size;
|
import android.util.Size;
|
||||||
import android.view.GestureDetector;
|
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
|
* Android {@link android.view.Surface} class, which allows for zero-copy transport of large
|
||||||
* buffers between processes and subsystems.</p>
|
* buffers between processes and subsystems.</p>
|
||||||
*/
|
*/
|
||||||
public class HdrViewfinderActivity extends Activity implements
|
public class HdrViewfinderActivity extends AppCompatActivity implements
|
||||||
SurfaceHolder.Callback, CameraOps.ErrorDisplayer, CameraOps.CameraReadyListener {
|
SurfaceHolder.Callback, CameraOps.ErrorDisplayer, CameraOps.CameraReadyListener {
|
||||||
|
|
||||||
private static final String TAG = "HdrViewfinderDemo";
|
private static final String TAG = "HdrViewfinderDemo";
|
||||||
|
|
||||||
private static final String FRAGMENT_DIALOG = "dialog";
|
private static final String FRAGMENT_DIALOG = "dialog";
|
||||||
|
|
||||||
|
private static final int REQUEST_PERMISSIONS_REQUEST_CODE = 34;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View for the camera preview.
|
* View for the camera preview.
|
||||||
*/
|
*/
|
||||||
private FixedAspectSurfaceView mPreviewView;
|
private FixedAspectSurfaceView mPreviewView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Root view of this activity.
|
||||||
|
*/
|
||||||
|
private View rootView;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This shows the current mode of the app.
|
* This shows the current mode of the app.
|
||||||
*/
|
*/
|
||||||
@@ -132,6 +147,8 @@ public class HdrViewfinderActivity extends Activity implements
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.main);
|
setContentView(R.layout.main);
|
||||||
|
|
||||||
|
rootView = findViewById(R.id.panels);
|
||||||
|
|
||||||
mPreviewView = (FixedAspectSurfaceView) findViewById(R.id.preview);
|
mPreviewView = (FixedAspectSurfaceView) findViewById(R.id.preview);
|
||||||
mPreviewView.getHolder().addCallback(this);
|
mPreviewView.getHolder().addCallback(this);
|
||||||
mPreviewView.setGestureListener(this, mViewListener);
|
mPreviewView.setGestureListener(this, mViewListener);
|
||||||
@@ -146,23 +163,20 @@ public class HdrViewfinderActivity extends Activity implements
|
|||||||
|
|
||||||
mUiHandler = new Handler(Looper.getMainLooper());
|
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);
|
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
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
findAndOpenCamera();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -170,7 +184,10 @@ public class HdrViewfinderActivity extends Activity implements
|
|||||||
super.onPause();
|
super.onPause();
|
||||||
|
|
||||||
// Wait until camera is closed to ensure the next application can open it
|
// Wait until camera is closed to ensure the next application can open it
|
||||||
mCameraOps.closeCameraAndWait();
|
if (mCameraOps != null) {
|
||||||
|
mCameraOps.closeCameraAndWait();
|
||||||
|
mCameraOps = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -232,7 +249,9 @@ public class HdrViewfinderActivity extends Activity implements
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Show help dialog
|
/**
|
||||||
|
* Show help dialogs.
|
||||||
|
*/
|
||||||
private View.OnClickListener mHelpButtonListener = new View.OnClickListener() {
|
private View.OnClickListener mHelpButtonListener = new View.OnClickListener() {
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
MessageDialogFragment.newInstance(R.string.help_text)
|
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() {
|
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";
|
int level = info.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
|
||||||
boolean foundCamera = false;
|
boolean hasFullLevel
|
||||||
try {
|
= (level
|
||||||
// Find first back-facing camera that has necessary capability
|
== CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL);
|
||||||
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);
|
int[] capabilities = info
|
||||||
boolean hasFullLevel
|
.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
|
||||||
= (level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL);
|
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);
|
// All these are guaranteed by
|
||||||
int syncLatency = info.get(CameraCharacteristics.SYNC_MAX_LATENCY);
|
// CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, but checking
|
||||||
boolean hasManualControl = hasCapability(capabilities,
|
// for only the things we care about expands range of devices we can run on.
|
||||||
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR);
|
// We want:
|
||||||
boolean hasEnoughCapability = hasManualControl &&
|
// - Back-facing camera
|
||||||
syncLatency == CameraCharacteristics.SYNC_MAX_LATENCY_PER_FRAME_CONTROL;
|
// - Manual sensor control
|
||||||
|
// - Per-frame synchronization (so that exposure can be changed every frame)
|
||||||
// All these are guaranteed by
|
if (facing == CameraCharacteristics.LENS_FACING_BACK &&
|
||||||
// CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, but checking for only
|
(hasFullLevel || hasEnoughCapability)) {
|
||||||
// the things we care about expands range of devices we can run on
|
// Found suitable camera - get info, open, and set up outputs
|
||||||
// We want:
|
mCameraInfo = info;
|
||||||
// - Back-facing camera
|
mCameraOps.openCamera(id);
|
||||||
// - Manual sensor control
|
configureSurfaces();
|
||||||
// - Per-frame synchronization (so that exposure can be changed every frame)
|
foundCamera = true;
|
||||||
if (facing == CameraCharacteristics.LENS_FACING_BACK &&
|
break;
|
||||||
(hasFullLevel || hasEnoughCapability)) {
|
}
|
||||||
// Found suitable camera - get info, open, and set up outputs
|
}
|
||||||
mCameraInfo = info;
|
if (!foundCamera) {
|
||||||
mCameraOps.openCamera(id);
|
errorMessage = getString(R.string.camera_no_good);
|
||||||
configureSurfaces();
|
}
|
||||||
foundCamera = true;
|
} catch (CameraAccessException e) {
|
||||||
break;
|
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) {
|
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) {
|
if (mProcessor != null) {
|
||||||
mProcessor.setRenderMode(mRenderMode);
|
mProcessor.setRenderMode(mRenderMode);
|
||||||
}
|
}
|
||||||
if (mRenderMode == ViewfinderProcessor.MODE_NORMAL) {
|
if (mRenderMode == ViewfinderProcessor.MODE_NORMAL) {
|
||||||
mCameraOps.setRepeatingRequest(mPreviewRequest,
|
mCameraOps.setRepeatingRequest(mPreviewRequest,
|
||||||
mCaptureCallback, mUiHandler);
|
mCaptureCallback, mUiHandler);
|
||||||
} else {
|
} else {
|
||||||
setHdrBurst();
|
setHdrBurst();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure the surfaceview and RS processing
|
* Configure the surfaceview and RS processing.
|
||||||
*/
|
*/
|
||||||
private void configureSurfaces() {
|
private void configureSurfaces() {
|
||||||
// Find a good size for output - largest 16:9 aspect ratio that's less than 720p
|
// Find a good size for output - largest 16:9 aspect ratio that's less than 720p
|
||||||
|
|||||||
@@ -37,13 +37,17 @@
|
|||||||
<service android:name=".MessagingService">
|
<service android:name=".MessagingService">
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<receiver android:name=".MessageReadReceiver">
|
<receiver
|
||||||
|
android:name=".MessageReadReceiver"
|
||||||
|
android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.example.android.messagingservice.ACTION_MESSAGE_READ"/>
|
<action android:name="com.example.android.messagingservice.ACTION_MESSAGE_READ"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<receiver android:name=".MessageReplyReceiver">
|
<receiver
|
||||||
|
android:name=".MessageReplyReceiver"
|
||||||
|
android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.example.android.messagingservice.ACTION_MESSAGE_REPLY"/>
|
<action android:name="com.example.android.messagingservice.ACTION_MESSAGE_REPLY"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|||||||
@@ -21,7 +21,8 @@
|
|||||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||||
android:paddingRight="@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
|
<LinearLayout
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
|
|||||||
@@ -16,8 +16,6 @@
|
|||||||
-->
|
-->
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Messaging Sample</string>
|
<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="notification_reply">Reply by Voice</string>
|
||||||
<string name="send_2_conversations">Send 2 conversations with 1 message</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>
|
<string name="send_1_conversation">Send 1 conversation with 1 message</string>
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
package com.example.android.messagingservice;
|
package com.example.android.messagingservice;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package com.example.android.messagingservice;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
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
|
* and replies. Don't use this in a real world application. This logger is only
|
||||||
* used for displaying the messages in the text view.
|
* 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 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 LOG_KEY = "message_data";
|
||||||
public static final String LINE_BREAKS = "\n\n";
|
|
||||||
|
|
||||||
public static void logMessage(Context context, String message) {
|
public static void logMessage(Context context, String message) {
|
||||||
SharedPreferences prefs = getPrefs(context);
|
SharedPreferences prefs = getPrefs(context);
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package com.example.android.messagingservice;
|
package com.example.android.messagingservice;
|
||||||
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -35,8 +34,8 @@ public class MessageReadReceiver extends BroadcastReceiver {
|
|||||||
if (conversationId != -1) {
|
if (conversationId != -1) {
|
||||||
Log.d(TAG, "Conversation " + conversationId + " was read");
|
Log.d(TAG, "Conversation " + conversationId + " was read");
|
||||||
MessageLogger.logMessage(context, "Conversation " + conversationId + " was read.");
|
MessageLogger.logMessage(context, "Conversation " + conversationId + " was read.");
|
||||||
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
|
NotificationManagerCompat.from(context)
|
||||||
notificationManager.cancel(conversationId);
|
.cancel(conversationId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ public class MessagingFragment extends Fragment implements View.OnClickListener
|
|||||||
private Messenger mService;
|
private Messenger mService;
|
||||||
private boolean mBound;
|
private boolean mBound;
|
||||||
|
|
||||||
private ServiceConnection mConnection = new ServiceConnection() {
|
private final ServiceConnection mConnection = new ServiceConnection() {
|
||||||
@Override
|
@Override
|
||||||
public void onServiceConnected(ComponentName componentName, IBinder service) {
|
public void onServiceConnected(ComponentName componentName, IBinder service) {
|
||||||
mService = new Messenger(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() {
|
new SharedPreferences.OnSharedPreferenceChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||||
|
|||||||
@@ -31,41 +31,24 @@ import android.support.v4.app.NotificationManagerCompat;
|
|||||||
import android.support.v4.app.RemoteInput;
|
import android.support.v4.app.RemoteInput;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
public class MessagingService extends Service {
|
public class MessagingService extends Service {
|
||||||
private static final String TAG = MessagingService.class.getSimpleName();
|
private static final String TAG = MessagingService.class.getSimpleName();
|
||||||
|
private static final String EOL = "\n";
|
||||||
public static final String READ_ACTION =
|
private static final String READ_ACTION =
|
||||||
"com.example.android.messagingservice.ACTION_MESSAGE_READ";
|
"com.example.android.messagingservice.ACTION_MESSAGE_READ";
|
||||||
|
|
||||||
public static final String REPLY_ACTION =
|
public static final String REPLY_ACTION =
|
||||||
"com.example.android.messagingservice.ACTION_MESSAGE_REPLY";
|
"com.example.android.messagingservice.ACTION_MESSAGE_REPLY";
|
||||||
public static final String CONVERSATION_ID = "conversation_id";
|
public static final String CONVERSATION_ID = "conversation_id";
|
||||||
public static final String EXTRA_VOICE_REPLY = "extra_voice_reply";
|
public static final String EXTRA_VOICE_REPLY = "extra_voice_reply";
|
||||||
public static final int MSG_SEND_NOTIFICATION = 1;
|
public static final int MSG_SEND_NOTIFICATION = 1;
|
||||||
public static final String EOL = "\n";
|
|
||||||
|
|
||||||
private NotificationManagerCompat mNotificationManager;
|
private NotificationManagerCompat mNotificationManager;
|
||||||
|
|
||||||
private final Messenger mMessenger = new Messenger(new IncomingHandler());
|
private final Messenger mMessenger = new Messenger(new IncomingHandler(this));
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
@@ -79,18 +62,6 @@ public class MessagingService extends Service {
|
|||||||
return mMessenger.getBinder();
|
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.
|
// Creates an intent that will be triggered when a message is marked as read.
|
||||||
private Intent getMessageReadIntent(int id) {
|
private Intent getMessageReadIntent(int id) {
|
||||||
return new Intent()
|
return new Intent()
|
||||||
@@ -171,4 +142,31 @@ public class MessagingService extends Service {
|
|||||||
|
|
||||||
mNotificationManager.notify(conversation.getConversationId(), builder.build());
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -325,8 +325,8 @@ public class MainActivity extends Activity implements DataApi.DataListener,
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDataChanged(DataEventBuffer dataEvents) {
|
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);
|
final List<DataEvent> events = FreezableUtils.freezeIterable(dataEvents);
|
||||||
dataEvents.close();
|
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@@ -445,16 +445,17 @@ public class MainActivity extends Activity implements DataApi.DataListener,
|
|||||||
.setResultCallback(new ResultCallback<DataItemBuffer>() {
|
.setResultCallback(new ResultCallback<DataItemBuffer>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResult(DataItemBuffer result) {
|
public void onResult(DataItemBuffer result) {
|
||||||
if (result.getStatus().isSuccess()) {
|
try {
|
||||||
List<DataItem> dataItemList = FreezableUtils.freezeIterable(result);
|
if (result.getStatus().isSuccess()) {
|
||||||
result.close();
|
resetDataItems(result);
|
||||||
resetDataItems(dataItemList);
|
} else {
|
||||||
} else {
|
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
Log.d(TAG, "Reset quiz: failed to get Data Items to reset");
|
||||||
Log.d(TAG, "Reset quiz: failed to get Data Items to reset");
|
}
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
result.release();
|
||||||
}
|
}
|
||||||
result.close();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -467,7 +468,7 @@ public class MainActivity extends Activity implements DataApi.DataListener,
|
|||||||
mNumSkipped = 0;
|
mNumSkipped = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetDataItems(List<DataItem> dataItemList) {
|
private void resetDataItems(DataItemBuffer dataItemList) {
|
||||||
if (mGoogleApiClient.isConnected()) {
|
if (mGoogleApiClient.isConnected()) {
|
||||||
for (final DataItem dataItem : dataItemList) {
|
for (final DataItem dataItem : dataItemList) {
|
||||||
final Uri dataItemUri = dataItem.getUri();
|
final Uri dataItemUri = dataItem.getUri();
|
||||||
@@ -521,19 +522,23 @@ public class MainActivity extends Activity implements DataApi.DataListener,
|
|||||||
.setResultCallback(new ResultCallback<DataItemBuffer>() {
|
.setResultCallback(new ResultCallback<DataItemBuffer>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResult(DataItemBuffer result) {
|
public void onResult(DataItemBuffer result) {
|
||||||
if (result.getStatus().isSuccess()) {
|
try {
|
||||||
List<Uri> dataItemUriList = new ArrayList<Uri>();
|
if (result.getStatus().isSuccess()) {
|
||||||
for (final DataItem dataItem : result) {
|
List<Uri> dataItemUriList = new ArrayList<Uri>();
|
||||||
dataItemUriList.add(dataItem.getUri());
|
for (final DataItem dataItem : result) {
|
||||||
}
|
dataItemUriList.add(dataItem.getUri());
|
||||||
result.close();
|
}
|
||||||
deleteDataItems(dataItemUriList);
|
deleteDataItems(dataItemUriList);
|
||||||
} else {
|
} else {
|
||||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||||
Log.d(TAG, "Clear quiz: failed to get Data Items for deletion");
|
Log.d(TAG, "Clear quiz: failed to get Data Items for "
|
||||||
|
+ "deletion");
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
result.release();
|
||||||
}
|
}
|
||||||
result.close();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ import android.util.Log;
|
|||||||
|
|
||||||
import com.google.android.gms.common.ConnectionResult;
|
import com.google.android.gms.common.ConnectionResult;
|
||||||
import com.google.android.gms.common.api.GoogleApiClient;
|
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.DataEvent;
|
||||||
import com.google.android.gms.wearable.DataEventBuffer;
|
import com.google.android.gms.wearable.DataEventBuffer;
|
||||||
import com.google.android.gms.wearable.DataItem;
|
import com.google.android.gms.wearable.DataItem;
|
||||||
@@ -80,9 +79,6 @@ public class QuizListenerService extends WearableListenerService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDataChanged(DataEventBuffer dataEvents) {
|
public void onDataChanged(DataEventBuffer dataEvents) {
|
||||||
final List<DataEvent> events = FreezableUtils.freezeIterable(dataEvents);
|
|
||||||
dataEvents.close();
|
|
||||||
|
|
||||||
GoogleApiClient googleApiClient = new GoogleApiClient.Builder(this)
|
GoogleApiClient googleApiClient = new GoogleApiClient.Builder(this)
|
||||||
.addApi(Wearable.API)
|
.addApi(Wearable.API)
|
||||||
.build();
|
.build();
|
||||||
@@ -94,7 +90,7 @@ public class QuizListenerService extends WearableListenerService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (DataEvent event : events) {
|
for (DataEvent event : dataEvents) {
|
||||||
if (event.getType() == DataEvent.TYPE_CHANGED) {
|
if (event.getType() == DataEvent.TYPE_CHANGED) {
|
||||||
DataItem dataItem = event.getDataItem();
|
DataItem dataItem = event.getDataItem();
|
||||||
DataMap dataMap = DataMapItem.fromDataItem(dataItem).getDataMap();
|
DataMap dataMap = DataMapItem.fromDataItem(dataItem).getDataMap();
|
||||||
|
|||||||
@@ -16,4 +16,6 @@
|
|||||||
<string name="permision_available_camera">Camera Permission has been granted. Preview can now be opened.</string>
|
<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="permision_available_contacts">Contacts Permissions have been granted. Contacts screen can now be opened.</string>
|
||||||
<string name="permissions_not_granted">Permissions were not granted.</string>
|
<string name="permissions_not_granted">Permissions were not granted.</string>
|
||||||
</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>
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ import android.widget.ViewAnimator;
|
|||||||
* {@link Activity#requestPermissions(String[], int)} and the return value checked in {@link
|
* {@link Activity#requestPermissions(String[], int)} and the return value checked in {@link
|
||||||
* Activity#onRequestPermissionsResult(int, String[], int[])}.
|
* Activity#onRequestPermissionsResult(int, String[], int[])}.
|
||||||
* <p>
|
* <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
|
* If this sample is executed on a device running a platform version below M, all permissions
|
||||||
* declared
|
* declared
|
||||||
* in the Android manifest file are always granted at install time and cannot be requested at run
|
* 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)
|
// BEGIN_INCLUDE(camera_permission)
|
||||||
// Check if the Camera permission is already available.
|
// Check if the Camera permission is already available.
|
||||||
if (PermissionUtil.hasSelfPermission(this, Manifest.permission.CAMERA)) {
|
if (PermissionUtil.hasSelfPermission(this, Manifest.permission.CAMERA)) {
|
||||||
|
// Camera permissions is already available, show the camera preview.
|
||||||
Log.i(TAG,
|
Log.i(TAG,
|
||||||
"CAMERA permission has already been granted. Displaying camera preview.");
|
"CAMERA permission has already been granted. Displaying camera preview.");
|
||||||
// Camera permissions is already available, show the camera preview.
|
|
||||||
showCameraPreview();
|
showCameraPreview();
|
||||||
} else {
|
} else {
|
||||||
|
// Camera permission has not been granted.
|
||||||
Log.i(TAG, "CAMERA permission has NOT been granted. Requesting permission.");
|
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)
|
// END_INCLUDE(camera_permission)
|
||||||
|
|
||||||
@@ -128,7 +144,18 @@ public class MainActivity extends SampleActivityBase {
|
|||||||
// Contact permissions have been granted. Show the contacts fragment.
|
// Contact permissions have been granted. Show the contacts fragment.
|
||||||
showContactDetails();
|
showContactDetails();
|
||||||
} else {
|
} else {
|
||||||
|
// Contacts permissions have not been granted.
|
||||||
Log.i(TAG, "Contact permissions has NOT been granted. Requesting permission.");
|
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.
|
// contact permissions has not been granted (read and write contacts). Request them.
|
||||||
requestPermissions(PERMISSIONS_CONTACT, REQUEST_CONTACTS);
|
requestPermissions(PERMISSIONS_CONTACT, REQUEST_CONTACTS);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,8 +61,13 @@ public class CameraPreviewFragment extends Fragment {
|
|||||||
|
|
||||||
// Open an instance of the first camera and retrieve its info.
|
// Open an instance of the first camera and retrieve its info.
|
||||||
mCamera = getCameraInstance(CAMERA_ID);
|
mCamera = getCameraInstance(CAMERA_ID);
|
||||||
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
|
Camera.CameraInfo cameraInfo = null;
|
||||||
Camera.getCameraInfo(CAMERA_ID, cameraInfo);
|
|
||||||
|
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) {
|
if (mCamera == null || cameraInfo == null) {
|
||||||
// Camera is not available, display error message
|
// Camera is not available, display error message
|
||||||
|
|||||||
@@ -86,34 +86,41 @@ public class MainActivity extends Activity {
|
|||||||
|
|
||||||
private void showCameraPreview() {
|
private void showCameraPreview() {
|
||||||
// BEGIN_INCLUDE(startCamera)
|
// BEGIN_INCLUDE(startCamera)
|
||||||
if (isMNC()) {
|
if (!isMNC()) {
|
||||||
// On Android M and above, need to check if permission has been granted at runtime.
|
// Below Android M there is no need to check for runtime permissions
|
||||||
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. */
|
|
||||||
Toast.makeText(this,
|
Toast.makeText(this,
|
||||||
"Requested permissions are granted at install time below M and are always "
|
"Requested permissions are granted at install time below M and are always "
|
||||||
+ "available at runtime.",
|
+ "available at runtime.",
|
||||||
Toast.LENGTH_SHORT).show();
|
Toast.LENGTH_SHORT).show();
|
||||||
startCamera();
|
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)
|
// END_INCLUDE(startCamera)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
<resources>
|
<resources>
|
||||||
|
<color name="digital_date">#aaaaaa</color>
|
||||||
<color name="digital_am_pm">#aaaaa0</color>
|
<color name="digital_am_pm">#aaaaa0</color>
|
||||||
<color name="digital_colons">#aaaaa0</color>
|
<color name="digital_colons">#aaaaa0</color>
|
||||||
<color name="config_activity_background">#ffffff</color>
|
<color name="config_activity_background">#ffffff</color>
|
||||||
|
|||||||
@@ -16,11 +16,13 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<dimen name="digital_text_size">40dp</dimen>
|
<dimen name="digital_text_size">40dp</dimen>
|
||||||
<dimen name="digital_text_size_round">45dp</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">25dp</dimen>
|
||||||
<dimen name="digital_am_pm_size_round">30dp</dimen>
|
<dimen name="digital_am_pm_size_round">30dp</dimen>
|
||||||
<dimen name="digital_x_offset">15dp</dimen>
|
<dimen name="digital_x_offset">15dp</dimen>
|
||||||
<dimen name="digital_x_offset_round">25dp</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="digital_config_color_picker_item_margin">32dp</dimen>
|
||||||
<dimen name="content_padding_start">12dp</dimen>
|
<dimen name="content_padding_start">12dp</dimen>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -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
|
@Override
|
||||||
public void onDraw(Canvas canvas, Rect bounds) {
|
public void onDraw(Canvas canvas, Rect bounds) {
|
||||||
mCalendar.setTimeInMillis(System.currentTimeMillis());
|
mCalendar.setTimeInMillis(System.currentTimeMillis());
|
||||||
@@ -220,12 +231,6 @@ public class AnalogWatchFaceService extends CanvasWatchFaceService {
|
|||||||
int height = bounds.height();
|
int height = bounds.height();
|
||||||
|
|
||||||
// Draw the background, scaled to fit.
|
// 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);
|
canvas.drawBitmap(mBackgroundScaledBitmap, 0, 0, null);
|
||||||
|
|
||||||
// Find the center. Ignore the window insets so that, on round watches with a
|
// Find the center. Ignore the window insets so that, on round watches with a
|
||||||
|
|||||||
@@ -46,7 +46,10 @@ import com.google.android.gms.wearable.DataMap;
|
|||||||
import com.google.android.gms.wearable.DataMapItem;
|
import com.google.android.gms.wearable.DataMapItem;
|
||||||
import com.google.android.gms.wearable.Wearable;
|
import com.google.android.gms.wearable.Wearable;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@@ -123,16 +126,26 @@ public class DigitalWatchFaceService extends CanvasWatchFaceService {
|
|||||||
.addApi(Wearable.API)
|
.addApi(Wearable.API)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
|
/**
|
||||||
|
* Handles time zone and locale changes.
|
||||||
|
*/
|
||||||
|
final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
mCalendar.setTimeZone(TimeZone.getDefault());
|
mCalendar.setTimeZone(TimeZone.getDefault());
|
||||||
|
initFormats();
|
||||||
invalidate();
|
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 mBackgroundPaint;
|
||||||
|
Paint mDatePaint;
|
||||||
Paint mHourPaint;
|
Paint mHourPaint;
|
||||||
Paint mMinutePaint;
|
Paint mMinutePaint;
|
||||||
Paint mSecondPaint;
|
Paint mSecondPaint;
|
||||||
@@ -140,10 +153,16 @@ public class DigitalWatchFaceService extends CanvasWatchFaceService {
|
|||||||
Paint mColonPaint;
|
Paint mColonPaint;
|
||||||
float mColonWidth;
|
float mColonWidth;
|
||||||
boolean mMute;
|
boolean mMute;
|
||||||
|
|
||||||
Calendar mCalendar;
|
Calendar mCalendar;
|
||||||
|
Date mDate;
|
||||||
|
SimpleDateFormat mDayOfWeekFormat;
|
||||||
|
java.text.DateFormat mDateFormat;
|
||||||
|
|
||||||
boolean mShouldDrawColons;
|
boolean mShouldDrawColons;
|
||||||
float mXOffset;
|
float mXOffset;
|
||||||
float mYOffset;
|
float mYOffset;
|
||||||
|
float mLineHeight;
|
||||||
String mAmString;
|
String mAmString;
|
||||||
String mPmString;
|
String mPmString;
|
||||||
int mInteractiveBackgroundColor =
|
int mInteractiveBackgroundColor =
|
||||||
@@ -175,11 +194,13 @@ public class DigitalWatchFaceService extends CanvasWatchFaceService {
|
|||||||
.build());
|
.build());
|
||||||
Resources resources = DigitalWatchFaceService.this.getResources();
|
Resources resources = DigitalWatchFaceService.this.getResources();
|
||||||
mYOffset = resources.getDimension(R.dimen.digital_y_offset);
|
mYOffset = resources.getDimension(R.dimen.digital_y_offset);
|
||||||
|
mLineHeight = resources.getDimension(R.dimen.digital_line_height);
|
||||||
mAmString = resources.getString(R.string.digital_am);
|
mAmString = resources.getString(R.string.digital_am);
|
||||||
mPmString = resources.getString(R.string.digital_pm);
|
mPmString = resources.getString(R.string.digital_pm);
|
||||||
|
|
||||||
mBackgroundPaint = new Paint();
|
mBackgroundPaint = new Paint();
|
||||||
mBackgroundPaint.setColor(mInteractiveBackgroundColor);
|
mBackgroundPaint.setColor(mInteractiveBackgroundColor);
|
||||||
|
mDatePaint = createTextPaint(resources.getColor(R.color.digital_date));
|
||||||
mHourPaint = createTextPaint(mInteractiveHourDigitsColor, BOLD_TYPEFACE);
|
mHourPaint = createTextPaint(mInteractiveHourDigitsColor, BOLD_TYPEFACE);
|
||||||
mMinutePaint = createTextPaint(mInteractiveMinuteDigitsColor);
|
mMinutePaint = createTextPaint(mInteractiveMinuteDigitsColor);
|
||||||
mSecondPaint = createTextPaint(mInteractiveSecondDigitsColor);
|
mSecondPaint = createTextPaint(mInteractiveSecondDigitsColor);
|
||||||
@@ -187,6 +208,8 @@ public class DigitalWatchFaceService extends CanvasWatchFaceService {
|
|||||||
mColonPaint = createTextPaint(resources.getColor(R.color.digital_colons));
|
mColonPaint = createTextPaint(resources.getColor(R.color.digital_colons));
|
||||||
|
|
||||||
mCalendar = Calendar.getInstance();
|
mCalendar = Calendar.getInstance();
|
||||||
|
mDate = new Date();
|
||||||
|
initFormats();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -219,8 +242,9 @@ public class DigitalWatchFaceService extends CanvasWatchFaceService {
|
|||||||
|
|
||||||
registerReceiver();
|
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());
|
mCalendar.setTimeZone(TimeZone.getDefault());
|
||||||
|
initFormats();
|
||||||
} else {
|
} else {
|
||||||
unregisterReceiver();
|
unregisterReceiver();
|
||||||
|
|
||||||
@@ -235,21 +259,29 @@ public class DigitalWatchFaceService extends CanvasWatchFaceService {
|
|||||||
updateTimer();
|
updateTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initFormats() {
|
||||||
|
mDayOfWeekFormat = new SimpleDateFormat("EEEE", Locale.getDefault());
|
||||||
|
mDayOfWeekFormat.setCalendar(mCalendar);
|
||||||
|
mDateFormat = DateFormat.getDateFormat(DigitalWatchFaceService.this);
|
||||||
|
mDateFormat.setCalendar(mCalendar);
|
||||||
|
}
|
||||||
|
|
||||||
private void registerReceiver() {
|
private void registerReceiver() {
|
||||||
if (mRegisteredTimeZoneReceiver) {
|
if (mRegisteredReceiver) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mRegisteredTimeZoneReceiver = true;
|
mRegisteredReceiver = true;
|
||||||
IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
|
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() {
|
private void unregisterReceiver() {
|
||||||
if (!mRegisteredTimeZoneReceiver) {
|
if (!mRegisteredReceiver) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mRegisteredTimeZoneReceiver = false;
|
mRegisteredReceiver = false;
|
||||||
DigitalWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver);
|
DigitalWatchFaceService.this.unregisterReceiver(mReceiver);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -269,6 +301,7 @@ public class DigitalWatchFaceService extends CanvasWatchFaceService {
|
|||||||
float amPmSize = resources.getDimension(isRound
|
float amPmSize = resources.getDimension(isRound
|
||||||
? R.dimen.digital_am_pm_size_round : R.dimen.digital_am_pm_size);
|
? 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);
|
mHourPaint.setTextSize(textSize);
|
||||||
mMinutePaint.setTextSize(textSize);
|
mMinutePaint.setTextSize(textSize);
|
||||||
mSecondPaint.setTextSize(textSize);
|
mSecondPaint.setTextSize(textSize);
|
||||||
@@ -321,6 +354,7 @@ public class DigitalWatchFaceService extends CanvasWatchFaceService {
|
|||||||
|
|
||||||
if (mLowBitAmbient) {
|
if (mLowBitAmbient) {
|
||||||
boolean antiAlias = !inAmbientMode;
|
boolean antiAlias = !inAmbientMode;
|
||||||
|
mDatePaint.setAntiAlias(antiAlias);
|
||||||
mHourPaint.setAntiAlias(antiAlias);
|
mHourPaint.setAntiAlias(antiAlias);
|
||||||
mMinutePaint.setAntiAlias(antiAlias);
|
mMinutePaint.setAntiAlias(antiAlias);
|
||||||
mSecondPaint.setAntiAlias(antiAlias);
|
mSecondPaint.setAntiAlias(antiAlias);
|
||||||
@@ -335,7 +369,7 @@ public class DigitalWatchFaceService extends CanvasWatchFaceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void adjustPaintColorToCurrentMode(Paint paint, int interactiveColor,
|
private void adjustPaintColorToCurrentMode(Paint paint, int interactiveColor,
|
||||||
int ambientColor) {
|
int ambientColor) {
|
||||||
paint.setColor(isInAmbientMode() ? ambientColor : interactiveColor);
|
paint.setColor(isInAmbientMode() ? ambientColor : interactiveColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,6 +387,7 @@ public class DigitalWatchFaceService extends CanvasWatchFaceService {
|
|||||||
if (mMute != inMuteMode) {
|
if (mMute != inMuteMode) {
|
||||||
mMute = inMuteMode;
|
mMute = inMuteMode;
|
||||||
int alpha = inMuteMode ? MUTE_ALPHA : NORMAL_ALPHA;
|
int alpha = inMuteMode ? MUTE_ALPHA : NORMAL_ALPHA;
|
||||||
|
mDatePaint.setAlpha(alpha);
|
||||||
mHourPaint.setAlpha(alpha);
|
mHourPaint.setAlpha(alpha);
|
||||||
mMinutePaint.setAlpha(alpha);
|
mMinutePaint.setAlpha(alpha);
|
||||||
mColonPaint.setAlpha(alpha);
|
mColonPaint.setAlpha(alpha);
|
||||||
@@ -409,7 +444,9 @@ public class DigitalWatchFaceService extends CanvasWatchFaceService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDraw(Canvas canvas, Rect bounds) {
|
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);
|
boolean is24Hour = DateFormat.is24HourFormat(DigitalWatchFaceService.this);
|
||||||
|
|
||||||
// Show colons for the first half of each second so the colons blink on when the time
|
// 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;
|
hour = 12;
|
||||||
}
|
}
|
||||||
hourString = String.valueOf(hour);
|
hourString = String.valueOf(hour);
|
||||||
if (hour < 10) {
|
|
||||||
x += mHourPaint.measureText("0");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
canvas.drawText(hourString, x, mYOffset, mHourPaint);
|
canvas.drawText(hourString, x, mYOffset, mHourPaint);
|
||||||
x += mHourPaint.measureText(hourString);
|
x += mHourPaint.measureText(hourString);
|
||||||
@@ -463,6 +497,19 @@ public class DigitalWatchFaceService extends CanvasWatchFaceService {
|
|||||||
canvas.drawText(getAmPmString(
|
canvas.drawText(getAmPmString(
|
||||||
mCalendar.get(Calendar.AM_PM)), x, mYOffset, mAmPmPaint);
|
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
|
@Override // DataApi.DataListener
|
||||||
public void onDataChanged(DataEventBuffer dataEvents) {
|
public void onDataChanged(DataEventBuffer dataEvents) {
|
||||||
try {
|
for (DataEvent dataEvent : dataEvents) {
|
||||||
for (DataEvent dataEvent : dataEvents) {
|
if (dataEvent.getType() != DataEvent.TYPE_CHANGED) {
|
||||||
if (dataEvent.getType() != DataEvent.TYPE_CHANGED) {
|
continue;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
} 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
@Override
|
||||||
public void onDraw(Canvas canvas, Rect bounds) {
|
public void onDraw(Canvas canvas, Rect bounds) {
|
||||||
if (Log.isLoggable(TAG, Log.VERBOSE)) {
|
if (Log.isLoggable(TAG, Log.VERBOSE)) {
|
||||||
@@ -182,12 +193,6 @@ public class SweepWatchFaceService extends CanvasWatchFaceService {
|
|||||||
int height = bounds.height();
|
int height = bounds.height();
|
||||||
|
|
||||||
// Draw the background, scaled to fit.
|
// 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);
|
canvas.drawBitmap(mBackgroundScaledBitmap, 0, 0, null);
|
||||||
|
|
||||||
// Find the center. Ignore the window insets so that, on round watches with a
|
// Find the center. Ignore the window insets so that, on round watches with a
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ public class TiltWatchFaceService extends Gles2WatchFaceService {
|
|||||||
private static final long FPS = 60;
|
private static final long FPS = 60;
|
||||||
|
|
||||||
/** Z distance from the camera to the watchface. */
|
/** 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. */
|
/** How long each frame is displayed at expected frame rate. */
|
||||||
private static final long FRAME_PERIOD_MS = TimeUnit.SECONDS.toMillis(1) / FPS;
|
private static final long FRAME_PERIOD_MS = TimeUnit.SECONDS.toMillis(1) / FPS;
|
||||||
|
|||||||
@@ -16,67 +16,86 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<android.support.design.widget.CoordinatorLayout
|
||||||
android:orientation="vertical"
|
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_width="match_parent"
|
||||||
android:layout_height="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" />
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="?colorPrimary"
|
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingStart="@dimen/keyline2"
|
android:layout_width="match_parent"
|
||||||
android:paddingEnd="@dimen/keyline3"
|
android:layout_height="match_parent"
|
||||||
android:paddingTop="@dimen/standard_margin"
|
android:weightSum="100">
|
||||||
android:paddingBottom="@dimen/standard_margin"
|
|
||||||
android:elevation="2dp">
|
|
||||||
|
|
||||||
<TextView
|
<ImageView
|
||||||
android:id="@+id/nameTextView"
|
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_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:maxLines="2"
|
android:background="?colorPrimary"
|
||||||
android:ellipsize="end"
|
android:orientation="vertical"
|
||||||
android:textIsSelectable="true"
|
android:paddingStart="@dimen/keyline2"
|
||||||
style="@style/TextAppearance.AppCompat.Title.Inverse"
|
android:paddingEnd="@dimen/keyline3"
|
||||||
android:transitionName="title" />
|
android:paddingTop="@dimen/standard_margin"
|
||||||
|
android:paddingBottom="@dimen/standard_margin"
|
||||||
|
android:elevation="2dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/distanceTextView"
|
android:id="@+id/nameTextView"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
style="@style/TextAppearance.AppCompat.Subhead.Inverse" />
|
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>
|
</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.support.design.widget.FloatingActionButton
|
||||||
android:id="@+id/descriptionTextView"
|
android:id="@+id/mapFab"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:textIsSelectable="true"
|
android:src="@drawable/ic_action_map"
|
||||||
style="@style/TextAppearance.AppCompat.Body1"
|
app:layout_anchor="@id/textLayout"
|
||||||
android:paddingStart="@dimen/keyline2"
|
app:layout_anchorGravity="bottom|start"
|
||||||
android:paddingEnd="@dimen/keyline3" />
|
app:rippleColor="@color/colorFabRipple"
|
||||||
|
android:layout_marginStart="@dimen/fab_margin"
|
||||||
|
android:clickable="true" />
|
||||||
|
|
||||||
</ScrollView>
|
</android.support.design.widget.CoordinatorLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -49,21 +49,20 @@
|
|||||||
android:id="@android:id/text1"
|
android:id="@android:id/text1"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_toRightOf="@android:id/icon"
|
android:layout_toEndOf="@android:id/icon"
|
||||||
android:paddingTop="@dimen/small_margin"
|
android:paddingTop="@dimen/small_margin"
|
||||||
android:paddingLeft="@dimen/small_margin"
|
android:paddingLeft="@dimen/small_margin"
|
||||||
android:paddingRight="@dimen/small_margin"
|
android:paddingRight="@dimen/small_margin"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
style="?android:textAppearanceMedium"
|
style="?android:textAppearanceMedium"
|
||||||
tools:text="Title 1"
|
tools:text="Title 1" />
|
||||||
android:transitionName="image" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@android:id/text2"
|
android:id="@android:id/text2"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_toRightOf="@android:id/icon"
|
android:layout_toEndOf="@android:id/icon"
|
||||||
android:layout_below="@android:id/text1"
|
android:layout_below="@android:id/text1"
|
||||||
android:padding="@dimen/small_margin"
|
android:padding="@dimen/small_margin"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -14,7 +14,8 @@
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
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>
|
<explode>
|
||||||
<targets>
|
<targets>
|
||||||
@@ -25,4 +26,11 @@
|
|||||||
</targets>
|
</targets>
|
||||||
</explode>
|
</explode>
|
||||||
|
|
||||||
|
<transition class="com.example.android.xyztouristattractions.ui.ScaleTransition"
|
||||||
|
android:interpolator="@android:interpolator/overshoot">
|
||||||
|
<targets>
|
||||||
|
<target android:targetId="@id/mapFab" />
|
||||||
|
</targets>
|
||||||
|
</transition>
|
||||||
|
|
||||||
</transitionSet>
|
</transitionSet>
|
||||||
@@ -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>
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!--
|
<!--
|
||||||
Copyright 2015 Google Inc. All rights reserved.
|
Copyright 2015 Google Inc. All rights reserved.
|
||||||
|
|
||||||
@@ -14,15 +15,8 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<resources>
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
tools:context=".MainActivity" >
|
|
||||||
|
|
||||||
<item android:id="@+id/map"
|
<dimen name="fab_margin">12dp</dimen>
|
||||||
android:title="@string/action_map"
|
|
||||||
android:orderInCategory="100"
|
|
||||||
android:icon="@drawable/ic_action_map"
|
|
||||||
app:showAsAction="ifRoom" />
|
|
||||||
|
|
||||||
</menu>
|
</resources>
|
||||||
@@ -20,5 +20,6 @@
|
|||||||
<dimen name="small_margin">12dp</dimen>
|
<dimen name="small_margin">12dp</dimen>
|
||||||
<dimen name="tiny_margin">8dp</dimen>
|
<dimen name="tiny_margin">8dp</dimen>
|
||||||
<dimen name="image_size">140dp</dimen>
|
<dimen name="image_size">140dp</dimen>
|
||||||
|
<dimen name="fab_margin">0dp</dimen>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -21,8 +21,10 @@
|
|||||||
<item name="android:windowActivityTransitions">true</item>
|
<item name="android:windowActivityTransitions">true</item>
|
||||||
<item name="android:windowExitTransition">@transition/fade</item>
|
<item name="android:windowExitTransition">@transition/fade</item>
|
||||||
<item name="android:windowReenterTransition">@transition/fade</item>
|
<item name="android:windowReenterTransition">@transition/fade</item>
|
||||||
<item name="android:windowEnterTransition">@transition/explode</item>
|
<item name="android:windowEnterTransition">@transition/window_enter</item>
|
||||||
<item name="android:windowReturnTransition">@transition/explode</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:windowAllowEnterTransitionOverlap">false</item>
|
||||||
<item name="android:windowAllowReturnTransitionOverlap">false</item>
|
<item name="android:windowAllowReturnTransitionOverlap">false</item>
|
||||||
<item name="android:windowSharedElementsUseOverlay">false</item>
|
<item name="android:windowSharedElementsUseOverlay">false</item>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
<dimen name="small_margin">8dp</dimen>
|
<dimen name="small_margin">8dp</dimen>
|
||||||
<dimen name="tiny_margin">4dp</dimen>
|
<dimen name="tiny_margin">4dp</dimen>
|
||||||
<dimen name="image_size">120dp</dimen>
|
<dimen name="image_size">120dp</dimen>
|
||||||
|
<dimen name="fab_margin">-8dp</dimen>
|
||||||
<item type="dimen" name="keyline1">@dimen/standard_margin</item>
|
<item type="dimen" name="keyline1">@dimen/standard_margin</item>
|
||||||
<dimen name="keyline2">72dp</dimen>
|
<dimen name="keyline2">72dp</dimen>
|
||||||
<item type="dimen" name="keyline3">@dimen/standard_margin</item>
|
<item type="dimen" name="keyline3">@dimen/standard_margin</item>
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ public class AttractionListFragment extends Fragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
mItemClickListener.onItemClick(v, getPosition());
|
mItemClickListener.onItemClick(v, getAdapterPosition());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,12 +21,11 @@ import android.content.Intent;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.design.widget.FloatingActionButton;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.support.v4.app.NavUtils;
|
import android.support.v4.app.NavUtils;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -83,6 +82,7 @@ public class DetailFragment extends Fragment {
|
|||||||
TextView descTextView = (TextView) view.findViewById(R.id.descriptionTextView);
|
TextView descTextView = (TextView) view.findViewById(R.id.descriptionTextView);
|
||||||
TextView distanceTextView = (TextView) view.findViewById(R.id.distanceTextView);
|
TextView distanceTextView = (TextView) view.findViewById(R.id.distanceTextView);
|
||||||
ImageView imageView = (ImageView) view.findViewById(R.id.imageView);
|
ImageView imageView = (ImageView) view.findViewById(R.id.imageView);
|
||||||
|
FloatingActionButton mapFab = (FloatingActionButton) view.findViewById(R.id.mapFab);
|
||||||
|
|
||||||
LatLng location = Utils.getLocation(getActivity());
|
LatLng location = Utils.getLocation(getActivity());
|
||||||
String distance = Utils.formatDistanceBetween(location, mAttraction.location);
|
String distance = Utils.formatDistanceBetween(location, mAttraction.location);
|
||||||
@@ -102,13 +102,18 @@ public class DetailFragment extends Fragment {
|
|||||||
.placeholder(R.color.lighter_gray)
|
.placeholder(R.color.lighter_gray)
|
||||||
.override(imageSize, imageSize)
|
.override(imageSize, imageSize)
|
||||||
.into(imageView);
|
.into(imageView);
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
mapFab.setOnClickListener(new View.OnClickListener() {
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
@Override
|
||||||
inflater.inflate(R.menu.detail, menu);
|
public void onClick(View v) {
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
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
|
@Override
|
||||||
@@ -139,12 +144,6 @@ public class DetailFragment extends Fragment {
|
|||||||
|
|
||||||
// Otherwise let the system handle navigating "up"
|
// Otherwise let the system handle navigating "up"
|
||||||
return false;
|
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);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,5 +20,6 @@
|
|||||||
<color name="colorPrimary">#4e6cef</color>
|
<color name="colorPrimary">#4e6cef</color>
|
||||||
<color name="colorPrimaryDark">#2a36b1</color>
|
<color name="colorPrimaryDark">#2a36b1</color>
|
||||||
<color name="colorAccent">#ff7043</color>
|
<color name="colorAccent">#ff7043</color>
|
||||||
|
<color name="colorFabRipple">#D84315</color>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
<uses-sdk
|
<uses-sdk
|
||||||
android:minSdkVersion="21"
|
android:minSdkVersion="21"
|
||||||
android:targetSdkVersion="21" />
|
android:targetSdkVersion="22" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
@@ -32,9 +32,6 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.AttractionsActivity"
|
android:name=".ui.AttractionsActivity"
|
||||||
android:exported="true"
|
|
||||||
android:allowEmbedded="true"
|
|
||||||
android:taskAffinity=""
|
|
||||||
android:label="@string/app_name" />
|
android:label="@string/app_name" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import com.example.android.xyztouristattractions.common.Utils;
|
|||||||
import com.example.android.xyztouristattractions.ui.AttractionsActivity;
|
import com.example.android.xyztouristattractions.ui.AttractionsActivity;
|
||||||
import com.google.android.gms.common.ConnectionResult;
|
import com.google.android.gms.common.ConnectionResult;
|
||||||
import com.google.android.gms.common.api.GoogleApiClient;
|
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.DataEvent;
|
||||||
import com.google.android.gms.wearable.DataEventBuffer;
|
import com.google.android.gms.wearable.DataEventBuffer;
|
||||||
import com.google.android.gms.wearable.DataMap;
|
import com.google.android.gms.wearable.DataMap;
|
||||||
@@ -54,9 +53,7 @@ public class ListenerService extends WearableListenerService {
|
|||||||
public void onDataChanged(DataEventBuffer dataEvents) {
|
public void onDataChanged(DataEventBuffer dataEvents) {
|
||||||
Log.d(TAG, "onDataChanged: " + dataEvents);
|
Log.d(TAG, "onDataChanged: " + dataEvents);
|
||||||
|
|
||||||
final List<DataEvent> events = FreezableUtils.freezeIterable(dataEvents);
|
for (DataEvent event : dataEvents) {
|
||||||
|
|
||||||
for (DataEvent event : events) {
|
|
||||||
if (event.getType() == DataEvent.TYPE_CHANGED
|
if (event.getType() == DataEvent.TYPE_CHANGED
|
||||||
&& event.getDataItem() != null
|
&& event.getDataItem() != null
|
||||||
&& Constants.ATTRACTION_PATH.equals(event.getDataItem().getUri().getPath())) {
|
&& Constants.ATTRACTION_PATH.equals(event.getDataItem().getUri().getPath())) {
|
||||||
|
|||||||
Reference in New Issue
Block a user