Update samples prebuilts for lmp-mr1-ub-docs
Synced to developers/samples/android commit 54bab34b386e343e9d0ea75a5fb8d13db2c71eb5. Change-Id: I1c9d9d2c1f53a051d7b4d85303d5c01ab6f16e68
45
samples/browseable/AlwaysOn/AndroidManifest.xml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?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.
|
||||||
|
-->
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.android.wearable.wear.alwayson">
|
||||||
|
|
||||||
|
<uses-sdk android:minSdkVersion="20" android:targetSdkVersion="22" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
<uses-permission android:name="com.android.alarm.permission.SET_ALARM"/>
|
||||||
|
|
||||||
|
<uses-feature android:name="android.hardware.type.watch" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="false"
|
||||||
|
android:label="@string/app_name">
|
||||||
|
|
||||||
|
<!--If you want your app to run on pre-22, then set required to false -->
|
||||||
|
<uses-library android:name="com.google.android.wearable" android:required="false" />
|
||||||
|
|
||||||
|
<activity android:name="com.example.android.wearable.wear.alwayson.MainActivity"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:launchMode="singleInstance"
|
||||||
|
android:configChanges="orientation|keyboardHidden"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
9
samples/browseable/AlwaysOn/_index.jd
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
page.tags="AlwaysOn"
|
||||||
|
sample.group=Wearable
|
||||||
|
@jd:body
|
||||||
|
|
||||||
|
<p>
|
||||||
|
|
||||||
|
Demonstrates a native Android Wear app using ambient screen support.
|
||||||
|
>
|
||||||
|
</p>
|
||||||
28
samples/browseable/AlwaysOn/res/layout/activity_main.xml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?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.
|
||||||
|
-->
|
||||||
|
<android.support.wearable.view.WatchViewStub
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/watch_view_stub"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:rectLayout="@layout/rect_activity_main"
|
||||||
|
app:roundLayout="@layout/round_activity_main"
|
||||||
|
tools:context=".MainActivity"
|
||||||
|
tools:deviceIds="wear">
|
||||||
|
</android.support.wearable.view.WatchViewStub>
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<?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.
|
||||||
|
-->
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:paddingTop="@dimen/square_top_margin"
|
||||||
|
android:paddingLeft="@dimen/square_left_margin"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
tools:context=".MainActivity"
|
||||||
|
tools:deviceIds="wear_square">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/time"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:text="Hello, time!"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/time_stamp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Hello, timestamp!"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/state"
|
||||||
|
android:textColor="@color/green"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Hello, state!"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/update_rate"
|
||||||
|
android:textColor="@color/green"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Hello, update rate!"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/draw_count"
|
||||||
|
android:textColor="@color/green"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Hello, draw count!"/>
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<?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.
|
||||||
|
-->
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:paddingTop="@dimen/round_top_margin"
|
||||||
|
android:paddingLeft="@dimen/round_left_margin"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".MainActivity"
|
||||||
|
tools:deviceIds="wear_round">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/time"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:text="Hello, time!"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/time_stamp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Hello, timestamp!"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/state"
|
||||||
|
android:textColor="@color/green"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Hello, state!"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/update_rate"
|
||||||
|
android:textColor="@color/green"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Hello, update rate!"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/draw_count"
|
||||||
|
android:textColor="@color/green"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Hello, draw count!"/>
|
||||||
|
</LinearLayout>
|
||||||
23
samples/browseable/AlwaysOn/res/values/dimens.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.
|
||||||
|
-->
|
||||||
|
<resources>
|
||||||
|
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||||
|
<dimen name="square_top_margin">24dp</dimen>
|
||||||
|
<dimen name="square_left_margin">16dp</dimen>
|
||||||
|
|
||||||
|
<dimen name="round_top_margin">34dp</dimen>
|
||||||
|
<dimen name="round_left_margin">34dp</dimen>
|
||||||
|
</resources>
|
||||||
23
samples/browseable/AlwaysOn/res/values/strings.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.
|
||||||
|
-->
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">Always On Example</string>
|
||||||
|
<string name="timestamp_label">Timestamp: %1$d</string>
|
||||||
|
<string name="mode_active_label">Active Mode (Handler)</string>
|
||||||
|
<string name="mode_ambient_label">Ambient Mode (Alarm)</string>
|
||||||
|
<string name="update_rate_label">Update rate: %1$d sec</string>
|
||||||
|
<string name="draw_count_label">Draw count: %1$d</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,332 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 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.
|
||||||
|
*/
|
||||||
|
package com.example.android.wearable.wear.alwayson;
|
||||||
|
|
||||||
|
import android.app.AlarmManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.support.wearable.activity.WearableActivity;
|
||||||
|
import android.support.wearable.view.WatchViewStub;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demonstrates support for Ambient screens by extending WearableActivity and overriding
|
||||||
|
* onEnterAmbient, onUpdateAmbient, and onExitAmbient.
|
||||||
|
*
|
||||||
|
* There are two modes (Active and Ambient). To trigger future updates (data/screen), we use a
|
||||||
|
* custom Handler for the "Active" mode and an Alarm for the "Ambient" mode.
|
||||||
|
*
|
||||||
|
* Why don't we use just one? Handlers are generally less battery intensive and can be triggered
|
||||||
|
* every second. However, they can not wake up the processor (common in Ambient mode).
|
||||||
|
*
|
||||||
|
* Alarms can wake up the processor (what we need for Ambient), but they struggle with quick updates
|
||||||
|
* (less than one second) and are much less efficient compared to Handlers.
|
||||||
|
*
|
||||||
|
* Therefore, we use Handlers for "Active" mode (can trigger every second and are better on the
|
||||||
|
* battery), and we use Alarms for "Ambient" mode (only need to update once every 20 seconds and
|
||||||
|
* they can wake up a sleeping processor).
|
||||||
|
*
|
||||||
|
* Again, the Activity waits 20 seconds between doing any processing (getting data, updating screen
|
||||||
|
* etc.) while in ambient mode to conserving battery life (processor allowed to sleep). If you can
|
||||||
|
* hold off on updates for a full minute, you can throw away all the Alarm code and just use
|
||||||
|
* onUpdateAmbient() to save even more battery life.
|
||||||
|
*
|
||||||
|
* As always, you will still want to apply the performance guidelines outlined in the Watch Faces
|
||||||
|
* documention to your app.
|
||||||
|
*
|
||||||
|
* Finally, in ambient mode, this Activity follows the same best practices outlined in the
|
||||||
|
* Watch Faces API documentation, e.g., keep most pixels black, avoid large blocks of white pixels,
|
||||||
|
* use only black and white, and disable anti-aliasing.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class MainActivity extends WearableActivity {
|
||||||
|
|
||||||
|
private static final String TAG = "MainActivity";
|
||||||
|
|
||||||
|
/** Custom 'what' for Message sent to Handler. */
|
||||||
|
private static final int MSG_UPDATE_SCREEN = 0;
|
||||||
|
|
||||||
|
/** Milliseconds between updates based on state. */
|
||||||
|
private static final long ACTIVE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(1);
|
||||||
|
private static final long AMBIENT_INTERVAL_MS = TimeUnit.SECONDS.toMillis(20);
|
||||||
|
|
||||||
|
/** Tracks latest ambient details, such as burnin offsets, etc. */
|
||||||
|
private Bundle mAmbientDetails;
|
||||||
|
|
||||||
|
private TextView mTimeTextView;
|
||||||
|
private TextView mTimeStampTextView;
|
||||||
|
private TextView mStateTextView;
|
||||||
|
private TextView mUpdateRateTextView;
|
||||||
|
private TextView mDrawCountTextView;
|
||||||
|
|
||||||
|
private final SimpleDateFormat sDateFormat =
|
||||||
|
new SimpleDateFormat("HH:mm:ss", Locale.US);
|
||||||
|
|
||||||
|
private volatile int mDrawCount = 0;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Since the handler (used in active mode) can't wake up the processor when the device is in
|
||||||
|
* ambient mode and undocked, we use an Alarm to cover ambient mode updates when we need them
|
||||||
|
* more frequently than every minute. Remember, if getting updates once a minute in ambient
|
||||||
|
* mode is enough, you can do away with the Alarm code and just rely on the onUpdateAmbient()
|
||||||
|
* callback.
|
||||||
|
*/
|
||||||
|
private AlarmManager mAmbientStateAlarmManager;
|
||||||
|
private PendingIntent mAmbientStatePendingIntent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This custom handler is used for updates in "Active" mode. We use a separate static class to
|
||||||
|
* help us avoid memory leaks.
|
||||||
|
*/
|
||||||
|
private final Handler mActiveModeUpdateHandler = new UpdateHandler(this);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
Log.d(TAG, "onCreate()");
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_main);
|
||||||
|
|
||||||
|
setAmbientEnabled();
|
||||||
|
|
||||||
|
mAmbientStateAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
|
||||||
|
Intent ambientStateIntent = new Intent(getApplicationContext(), MainActivity.class);
|
||||||
|
|
||||||
|
mAmbientStatePendingIntent = PendingIntent.getActivity(
|
||||||
|
getApplicationContext(),
|
||||||
|
0 /* requestCode */,
|
||||||
|
ambientStateIntent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
|
||||||
|
|
||||||
|
/** Determines whether watch is round or square and applies proper view. **/
|
||||||
|
final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub);
|
||||||
|
stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
|
||||||
|
@Override
|
||||||
|
public void onLayoutInflated(WatchViewStub stub) {
|
||||||
|
|
||||||
|
mTimeTextView = (TextView) stub.findViewById(R.id.time);
|
||||||
|
mTimeStampTextView = (TextView) stub.findViewById(R.id.time_stamp);
|
||||||
|
mStateTextView = (TextView) stub.findViewById(R.id.state);
|
||||||
|
mUpdateRateTextView = (TextView) stub.findViewById(R.id.update_rate);
|
||||||
|
mDrawCountTextView = (TextView) stub.findViewById(R.id.draw_count);
|
||||||
|
|
||||||
|
refreshDisplayAndSetNextUpdate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is mostly triggered by the Alarms we set in Ambient mode and informs us we need to
|
||||||
|
* update the screen (and process any data).
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onNewIntent(Intent intent) {
|
||||||
|
Log.d(TAG, "onNewIntent(): " + intent);
|
||||||
|
super.onNewIntent(intent);
|
||||||
|
|
||||||
|
setIntent(intent);
|
||||||
|
|
||||||
|
refreshDisplayAndSetNextUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
Log.d(TAG, "onDestroy()");
|
||||||
|
|
||||||
|
mActiveModeUpdateHandler.removeMessages(MSG_UPDATE_SCREEN);
|
||||||
|
mAmbientStateAlarmManager.cancel(mAmbientStatePendingIntent);
|
||||||
|
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares UI for Ambient view.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onEnterAmbient(Bundle ambientDetails) {
|
||||||
|
Log.d(TAG, "onEnterAmbient()");
|
||||||
|
super.onEnterAmbient(ambientDetails);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In this sample, we aren't using the ambient details bundle (EXTRA_BURN_IN_PROTECTION or
|
||||||
|
* EXTRA_LOWBIT_AMBIENT), but if you need them, you can pull them from the local variable
|
||||||
|
* set here.
|
||||||
|
*/
|
||||||
|
mAmbientDetails = ambientDetails;
|
||||||
|
|
||||||
|
/** Clears Handler queue (only needed for updates in active mode). */
|
||||||
|
mActiveModeUpdateHandler.removeMessages(MSG_UPDATE_SCREEN);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Following best practices outlined in WatchFaces API (keeping most pixels black,
|
||||||
|
* avoiding large blocks of white pixels, using only black and white,
|
||||||
|
* and disabling anti-aliasing anti-aliasing, etc.)
|
||||||
|
*/
|
||||||
|
mStateTextView.setTextColor(Color.WHITE);
|
||||||
|
mUpdateRateTextView.setTextColor(Color.WHITE);
|
||||||
|
mDrawCountTextView.setTextColor(Color.WHITE);
|
||||||
|
|
||||||
|
mTimeTextView.getPaint().setAntiAlias(false);
|
||||||
|
mTimeStampTextView.getPaint().setAntiAlias(false);
|
||||||
|
mStateTextView.getPaint().setAntiAlias(false);
|
||||||
|
mUpdateRateTextView.getPaint().setAntiAlias(false);
|
||||||
|
mDrawCountTextView.getPaint().setAntiAlias(false);
|
||||||
|
|
||||||
|
refreshDisplayAndSetNextUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates UI in Ambient view (once a minute). Because we need to update UI sooner than that
|
||||||
|
* (every ~20 seconds), we also use an Alarm. However, since the processor is awake for this
|
||||||
|
* callback, we might as well call refreshDisplayAndSetNextUpdate() to update screen and reset
|
||||||
|
* the Alarm.
|
||||||
|
*
|
||||||
|
* If you are happy with just updating the screen once a minute in Ambient Mode (which will be
|
||||||
|
* the case a majority of the time), then you can just use this method and remove all
|
||||||
|
* references/code regarding Alarms.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onUpdateAmbient() {
|
||||||
|
Log.d(TAG, "onUpdateAmbient()");
|
||||||
|
super.onUpdateAmbient();
|
||||||
|
|
||||||
|
refreshDisplayAndSetNextUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares UI for Active view (non-Ambient).
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onExitAmbient() {
|
||||||
|
Log.d(TAG, "onExitAmbient()");
|
||||||
|
super.onExitAmbient();
|
||||||
|
|
||||||
|
/** Clears out Alarms since they are only used in ambient mode. */
|
||||||
|
mAmbientStateAlarmManager.cancel(mAmbientStatePendingIntent);
|
||||||
|
|
||||||
|
mStateTextView.setTextColor(Color.GREEN);
|
||||||
|
mUpdateRateTextView.setTextColor(Color.GREEN);
|
||||||
|
mDrawCountTextView.setTextColor(Color.GREEN);
|
||||||
|
|
||||||
|
mTimeTextView.getPaint().setAntiAlias(true);
|
||||||
|
mTimeStampTextView.getPaint().setAntiAlias(true);
|
||||||
|
mStateTextView.getPaint().setAntiAlias(true);
|
||||||
|
mUpdateRateTextView.getPaint().setAntiAlias(true);
|
||||||
|
mDrawCountTextView.getPaint().setAntiAlias(true);
|
||||||
|
|
||||||
|
refreshDisplayAndSetNextUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads data/updates screen (via method), but most importantly, sets up the next refresh
|
||||||
|
* (active mode = Handler and ambient mode = Alarm).
|
||||||
|
*/
|
||||||
|
private void refreshDisplayAndSetNextUpdate() {
|
||||||
|
|
||||||
|
loadDataAndUpdateScreen();
|
||||||
|
|
||||||
|
long timeMs = System.currentTimeMillis();
|
||||||
|
|
||||||
|
if (isAmbient()) {
|
||||||
|
/** Prevents time drift while calculating trigger time (based on state). */
|
||||||
|
long delayMs = AMBIENT_INTERVAL_MS - (timeMs % AMBIENT_INTERVAL_MS);
|
||||||
|
long triggerTimeMs = timeMs + delayMs;
|
||||||
|
|
||||||
|
mAmbientStateAlarmManager.cancel(mAmbientStatePendingIntent);
|
||||||
|
mAmbientStateAlarmManager.setExact(
|
||||||
|
AlarmManager.RTC_WAKEUP,
|
||||||
|
triggerTimeMs,
|
||||||
|
mAmbientStatePendingIntent);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
/** Prevents time drift. */
|
||||||
|
long delayMs = ACTIVE_INTERVAL_MS - (timeMs % ACTIVE_INTERVAL_MS);
|
||||||
|
|
||||||
|
mActiveModeUpdateHandler.removeMessages(MSG_UPDATE_SCREEN);
|
||||||
|
mActiveModeUpdateHandler.sendEmptyMessageDelayed(MSG_UPDATE_SCREEN, delayMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates display based on Ambient state. If you need to pull data, you should do it here.
|
||||||
|
*/
|
||||||
|
private void loadDataAndUpdateScreen() {
|
||||||
|
|
||||||
|
mDrawCount += 1;
|
||||||
|
long currentTimeMs = System.currentTimeMillis();
|
||||||
|
Log.d(TAG, "loadDataAndUpdateScreen(): " + currentTimeMs + "(" + isAmbient() + ")");
|
||||||
|
|
||||||
|
if (isAmbient()) {
|
||||||
|
|
||||||
|
mTimeTextView.setText(sDateFormat.format(new Date()));
|
||||||
|
mTimeStampTextView.setText(getString(R.string.timestamp_label, currentTimeMs));
|
||||||
|
|
||||||
|
mStateTextView.setText(getString(R.string.mode_ambient_label));
|
||||||
|
mUpdateRateTextView.setText(
|
||||||
|
getString(R.string.update_rate_label, (AMBIENT_INTERVAL_MS / 1000)));
|
||||||
|
|
||||||
|
mDrawCountTextView.setText(getString(R.string.draw_count_label, mDrawCount));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
mTimeTextView.setText(sDateFormat.format(new Date()));
|
||||||
|
mTimeStampTextView.setText(getString(R.string.timestamp_label, currentTimeMs));
|
||||||
|
|
||||||
|
mStateTextView.setText(getString(R.string.mode_active_label));
|
||||||
|
mUpdateRateTextView.setText(
|
||||||
|
getString(R.string.update_rate_label, (ACTIVE_INTERVAL_MS / 1000)));
|
||||||
|
|
||||||
|
mDrawCountTextView.setText(getString(R.string.draw_count_label, mDrawCount));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler separated into static class to avoid memory leaks.
|
||||||
|
*/
|
||||||
|
private static class UpdateHandler extends Handler {
|
||||||
|
private final WeakReference<MainActivity> mMainActivityWeakReference;
|
||||||
|
|
||||||
|
public UpdateHandler(MainActivity reference) {
|
||||||
|
mMainActivityWeakReference = new WeakReference<MainActivity>(reference);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message message) {
|
||||||
|
MainActivity mainActivity = mMainActivityWeakReference.get();
|
||||||
|
|
||||||
|
if (mainActivity != null) {
|
||||||
|
switch (message.what) {
|
||||||
|
case MSG_UPDATE_SCREEN:
|
||||||
|
mainActivity.refreshDisplayAndSetNextUpdate();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2013 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.android.bluetoothadvertisements"
|
||||||
|
android:versionCode="1"
|
||||||
|
android:versionName="1.0">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH"/>
|
||||||
|
|
||||||
|
<application android:allowBackup="true"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:icon="@drawable/ic_launcher"
|
||||||
|
android:theme="@style/AppTheme">
|
||||||
|
|
||||||
|
<activity android:name=".MainActivity"
|
||||||
|
android:label="@string/app_name">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
10
samples/browseable/BluetoothAdvertisements/_index.jd
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
page.tags="BluetoothAdvertisements"
|
||||||
|
sample.group=Connectivity
|
||||||
|
@jd:body
|
||||||
|
|
||||||
|
<p>
|
||||||
|
|
||||||
|
This samples demonstrates how to use the Bluetooth Low Power Advertisements API
|
||||||
|
with a single device acting as both scanner and advertiser.
|
||||||
|
|
||||||
|
</p>
|
||||||
|
After Width: | Height: | Size: 663 B |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 196 B |
|
After Width: | Height: | Size: 508 B |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 895 B |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 11 KiB |
55
samples/browseable/BluetoothAdvertisements/res/layout/activity_main.xml
Executable file
@@ -0,0 +1,55 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2013 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:id="@+id/sample_main_layout">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/scanner_fragment_container"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0px" >
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/error_textview"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:freezesText="true" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/advertiser_fragment_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<!--<fragment-->
|
||||||
|
<!--android:name="com.example.android.bluetoothadvertisements.ScannerFragment"-->
|
||||||
|
<!--android:id="@+id/scanner_fragment"-->
|
||||||
|
<!--android:layout_width="match_parent"-->
|
||||||
|
<!--android:layout_height="wrap_content" />-->
|
||||||
|
|
||||||
|
<!--<fragment-->
|
||||||
|
<!--android:name="com.example.android.bluetoothadvertisements.AdvertiserFragment"-->
|
||||||
|
<!--android:id="@+id/advertiser_fragment"-->
|
||||||
|
<!--android:layout_width="match_parent"-->
|
||||||
|
<!--android:layout_height="wrap_content" />-->
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2013 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:context="com.example.android.bluetoothadvertisements.AdvertiserFragment">
|
||||||
|
|
||||||
|
<!-- Horizontal Divider -->
|
||||||
|
<View
|
||||||
|
android:layout_width="250dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:background="@android:color/darker_gray"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:paddingTop="20dp"
|
||||||
|
android:paddingBottom="20dp" >
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:text="@string/broadcast_device"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:minWidth="100dp"
|
||||||
|
android:padding="5dp"/>
|
||||||
|
|
||||||
|
<Switch
|
||||||
|
android:id="@+id/advertise_switch"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:switchMinWidth="80dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2013 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingTop="20dp"
|
||||||
|
android:paddingLeft="100dp"
|
||||||
|
android:paddingRight="100dp">
|
||||||
|
<TextView android:id="@+id/device_name"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="16dp"/>
|
||||||
|
|
||||||
|
<TextView android:id="@+id/device_address"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="12dp"/>
|
||||||
|
<TextView android:id="@+id/last_seen"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="12dp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</RelativeLayout>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:id="@+id/refresh"
|
||||||
|
android:title="@string/refresh"
|
||||||
|
android:showAsAction="always"
|
||||||
|
android:icon="@drawable/ic_action_refresh"
|
||||||
|
/>
|
||||||
|
</menu>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<!--
|
||||||
|
Copyright 2013 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Semantic definitions -->
|
||||||
|
|
||||||
|
<dimen name="horizontal_page_margin">@dimen/margin_huge</dimen>
|
||||||
|
<dimen name="vertical_page_margin">@dimen/margin_medium</dimen>
|
||||||
|
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<!--
|
||||||
|
Copyright 2013 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<style name="Widget.SampleMessage">
|
||||||
|
<item name="android:textAppearance">?android:textAppearanceLarge</item>
|
||||||
|
<item name="android:lineSpacingMultiplier">1.2</item>
|
||||||
|
<item name="android:shadowDy">-6.5</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<!--
|
||||||
|
Copyright 2013 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Activity themes -->
|
||||||
|
<style name="Theme.Base" parent="android:Theme.Holo.Light" />
|
||||||
|
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2013 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<color name="colorPrimary">#434AB3</color>
|
||||||
|
<color name="colorPrimaryDark">#34379D</color>
|
||||||
|
<color name="textColorPrimary">#FFFFFF</color>
|
||||||
|
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2013 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Activity themes -->
|
||||||
|
<style name="Theme.Base" parent="android:Theme.Material.Light">
|
||||||
|
<item name="android:colorPrimary">@color/colorPrimary</item>
|
||||||
|
<item name="android:colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||||
|
<item name="android:textColorPrimary">@color/textColorPrimary</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2013 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">BluetoothAdvertisements</string>
|
||||||
|
<string name="intro_message">
|
||||||
|
<![CDATA[
|
||||||
|
|
||||||
|
|
||||||
|
This samples demonstrates how to use the Bluetooth Low Power Advertisements API
|
||||||
|
with a single device acting as both scanner and advertiser.
|
||||||
|
|
||||||
|
|
||||||
|
]]>
|
||||||
|
</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="bt_not_enabled_leaving">User declined to enable Bluetooth, exiting Bluetooth Advertisements.</string>
|
||||||
|
<string name="activity_main_title">Nearby Devices</string>
|
||||||
|
<string name="broadcast_device">Broadcast Device</string>
|
||||||
|
|
||||||
|
<string name="bt_not_supported">Bluetooth is not supported on this device.</string>
|
||||||
|
<string name="bt_ads_not_supported">Bluetooth Advertisements are not supported on this device.</string>
|
||||||
|
|
||||||
|
<string name="refresh">Refresh</string>
|
||||||
|
<string name="start_error_prefix">Start Advertising failed: </string>
|
||||||
|
<string name="start_error_already_started">already started.</string>
|
||||||
|
<string name="start_error_too_large">data packet exceeded 31 Byte limit.</string>
|
||||||
|
<string name="start_error_unsupported">not supported on this device.</string>
|
||||||
|
<string name="start_error_internal">internal error.</string>
|
||||||
|
<string name="start_error_too_many">too many advertisers.</string>
|
||||||
|
<string name="bt_null">Error: Bluetooth object null</string>
|
||||||
|
<string name="last_seen">Last Seen:</string>
|
||||||
|
<string name="just_now">just now</string>
|
||||||
|
<string name="minute_ago">minute ago</string>
|
||||||
|
<string name="hour_ago">hour ago</string>
|
||||||
|
<string name="seconds_ago">seconds ago</string>
|
||||||
|
<string name="minutes_ago">minutes ago</string>
|
||||||
|
<string name="hours_ago">hours ago</string>
|
||||||
|
<string name="empty_list">No devices found - refresh to try again.</string>
|
||||||
|
<string name="seconds">seconds.</string>
|
||||||
|
<string name="scan_start_toast">Scanning for</string>
|
||||||
|
<string name="already_scanning">Scanning already started.</string>
|
||||||
|
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<!--
|
||||||
|
Copyright 2013 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Define standard dimensions to comply with Holo-style grids and rhythm. -->
|
||||||
|
|
||||||
|
<dimen name="margin_tiny">4dp</dimen>
|
||||||
|
<dimen name="margin_small">8dp</dimen>
|
||||||
|
<dimen name="margin_medium">16dp</dimen>
|
||||||
|
<dimen name="margin_large">32dp</dimen>
|
||||||
|
<dimen name="margin_huge">64dp</dimen>
|
||||||
|
|
||||||
|
<!-- Semantic definitions -->
|
||||||
|
|
||||||
|
<dimen name="horizontal_page_margin">@dimen/margin_medium</dimen>
|
||||||
|
<dimen name="vertical_page_margin">@dimen/margin_medium</dimen>
|
||||||
|
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<!--
|
||||||
|
Copyright 2013 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Activity themes -->
|
||||||
|
|
||||||
|
<style name="Theme.Base" parent="android:Theme.Light" />
|
||||||
|
|
||||||
|
<style name="Theme.Sample" parent="Theme.Base" />
|
||||||
|
|
||||||
|
<style name="AppTheme" parent="Theme.Sample" />
|
||||||
|
<!-- Widget styling -->
|
||||||
|
|
||||||
|
<style name="Widget" />
|
||||||
|
|
||||||
|
<style name="Widget.SampleMessage">
|
||||||
|
<item name="android:textAppearance">?android:textAppearanceMedium</item>
|
||||||
|
<item name="android:lineSpacingMultiplier">1.1</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Widget.SampleMessageTile">
|
||||||
|
<item name="android:background">@drawable/tile</item>
|
||||||
|
<item name="android:shadowColor">#7F000000</item>
|
||||||
|
<item name="android:shadowDy">-3.5</item>
|
||||||
|
<item name="android:shadowRadius">2</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
package com.example.android.bluetoothadvertisements;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
import android.bluetooth.le.AdvertiseCallback;
|
||||||
|
import android.bluetooth.le.AdvertiseData;
|
||||||
|
import android.bluetooth.le.AdvertiseSettings;
|
||||||
|
import android.bluetooth.le.BluetoothLeAdvertiser;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Switch;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows user to start & stop Bluetooth LE Advertising of their device.
|
||||||
|
*/
|
||||||
|
public class AdvertiserFragment extends Fragment {
|
||||||
|
|
||||||
|
private BluetoothAdapter mBluetoothAdapter;
|
||||||
|
|
||||||
|
private BluetoothLeAdvertiser mBluetoothLeAdvertiser;
|
||||||
|
|
||||||
|
private AdvertiseCallback mAdvertiseCallback;
|
||||||
|
|
||||||
|
private Switch mSwitch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Must be called after object creation by MainActivity.
|
||||||
|
*
|
||||||
|
* @param btAdapter the local BluetoothAdapter
|
||||||
|
*/
|
||||||
|
public void setBluetoothAdapter(BluetoothAdapter btAdapter) {
|
||||||
|
this.mBluetoothAdapter = btAdapter;
|
||||||
|
mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setRetainInstance(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
|
||||||
|
View view = inflater.inflate(R.layout.fragment_advertiser, container, false);
|
||||||
|
|
||||||
|
mSwitch = (Switch) view.findViewById(R.id.advertise_switch);
|
||||||
|
mSwitch.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
onSwitchClicked(v);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
|
||||||
|
if(mAdvertiseCallback != null){
|
||||||
|
stopAdvertising();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when switch is toggled - starts or stops advertising.
|
||||||
|
*
|
||||||
|
* @param view is the Switch View object
|
||||||
|
*/
|
||||||
|
public void onSwitchClicked(View view) {
|
||||||
|
|
||||||
|
// Is the toggle on?
|
||||||
|
boolean on = ((Switch) view).isChecked();
|
||||||
|
|
||||||
|
if (on) {
|
||||||
|
startAdvertising();
|
||||||
|
} else {
|
||||||
|
stopAdvertising();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts BLE Advertising.
|
||||||
|
*/
|
||||||
|
private void startAdvertising() {
|
||||||
|
|
||||||
|
mAdvertiseCallback = new SampleAdvertiseCallback();
|
||||||
|
|
||||||
|
if (mBluetoothLeAdvertiser != null) {
|
||||||
|
mBluetoothLeAdvertiser.startAdvertising(buildAdvertiseSettings(), buildAdvertiseData(),
|
||||||
|
mAdvertiseCallback);
|
||||||
|
} else {
|
||||||
|
mSwitch.setChecked(false);
|
||||||
|
Toast.makeText(getActivity(), getString(R.string.bt_null), Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops BLE Advertising.
|
||||||
|
*/
|
||||||
|
private void stopAdvertising() {
|
||||||
|
|
||||||
|
if (mBluetoothLeAdvertiser != null) {
|
||||||
|
|
||||||
|
mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback);
|
||||||
|
mAdvertiseCallback = null;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
mSwitch.setChecked(false);
|
||||||
|
Toast.makeText(getActivity(), getString(R.string.bt_null), Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an AdvertiseData object which includes the Service UUID and Device Name.
|
||||||
|
*/
|
||||||
|
private AdvertiseData buildAdvertiseData() {
|
||||||
|
|
||||||
|
// Note: There is a strict limit of 31 Bytes on packets sent over BLE Advertisements.
|
||||||
|
// This includes everything put into AdvertiseData including UUIDs, device info, &
|
||||||
|
// arbitrary service or manufacturer data.
|
||||||
|
// Attempting to send packets over this limit will result in a failure with error code
|
||||||
|
// AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE. Catch this error in the
|
||||||
|
// onStartFailure() method of an AdvertiseCallback implementation.
|
||||||
|
|
||||||
|
AdvertiseData.Builder dataBuilder = new AdvertiseData.Builder();
|
||||||
|
dataBuilder.addServiceUuid(Constants.Service_UUID);
|
||||||
|
dataBuilder.setIncludeDeviceName(true);
|
||||||
|
|
||||||
|
return dataBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an AdvertiseSettings object set to use low power (to help preserve battery life).
|
||||||
|
*/
|
||||||
|
private AdvertiseSettings buildAdvertiseSettings() {
|
||||||
|
AdvertiseSettings.Builder settingsBuilder = new AdvertiseSettings.Builder();
|
||||||
|
settingsBuilder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_POWER);
|
||||||
|
|
||||||
|
return settingsBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom callback after Advertising succeeds or fails to start.
|
||||||
|
*/
|
||||||
|
private class SampleAdvertiseCallback extends AdvertiseCallback {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartFailure(int errorCode) {
|
||||||
|
super.onStartFailure(errorCode);
|
||||||
|
|
||||||
|
mSwitch.setChecked(false);
|
||||||
|
|
||||||
|
String errorMessage = getString(R.string.start_error_prefix);
|
||||||
|
switch (errorCode) {
|
||||||
|
case AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED:
|
||||||
|
errorMessage += " " + getString(R.string.start_error_already_started);
|
||||||
|
break;
|
||||||
|
case AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE:
|
||||||
|
errorMessage += " " + getString(R.string.start_error_too_large);
|
||||||
|
break;
|
||||||
|
case AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED:
|
||||||
|
errorMessage += " " + getString(R.string.start_error_unsupported);
|
||||||
|
break;
|
||||||
|
case AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR:
|
||||||
|
errorMessage += " " + getString(R.string.start_error_internal);
|
||||||
|
break;
|
||||||
|
case AdvertiseCallback.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS:
|
||||||
|
errorMessage += " " + getString(R.string.start_error_too_many);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Toast.makeText(getActivity(), errorMessage, Toast.LENGTH_LONG).show();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
|
||||||
|
super.onStartSuccess(settingsInEffect);
|
||||||
|
// Don't need to do anything here, advertising successfully started.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.example.android.bluetoothadvertisements;
|
||||||
|
|
||||||
|
import android.os.ParcelUuid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constants for use in the Bluetooth Advertisements sample
|
||||||
|
*/
|
||||||
|
public class Constants {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UUID identified with this app - set as Service UUID for BLE Advertisements.
|
||||||
|
*
|
||||||
|
* Bluetooth requires a certain format for UUIDs associated with Services.
|
||||||
|
* The official specification can be found here:
|
||||||
|
* {@link https://www.bluetooth.org/en-us/specification/assigned-numbers/service-discovery}
|
||||||
|
*/
|
||||||
|
public static final ParcelUuid Service_UUID = ParcelUuid
|
||||||
|
.fromString("0000b81d-0000-1000-8000-00805f9b34fb");
|
||||||
|
|
||||||
|
public static final int REQUEST_ENABLE_BT = 1;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2013 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.bluetoothadvertisements;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
import android.bluetooth.BluetoothManager;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.FragmentActivity;
|
||||||
|
import android.support.v4.app.FragmentTransaction;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup display fragments and ensure the device supports Bluetooth.
|
||||||
|
*/
|
||||||
|
public class MainActivity extends FragmentActivity {
|
||||||
|
|
||||||
|
private BluetoothAdapter mBluetoothAdapter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_main);
|
||||||
|
setTitle(R.string.activity_main_title);
|
||||||
|
|
||||||
|
if (savedInstanceState == null ) {
|
||||||
|
|
||||||
|
mBluetoothAdapter = ((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE))
|
||||||
|
.getAdapter();
|
||||||
|
|
||||||
|
// Is Bluetooth supported on this device?
|
||||||
|
if (mBluetoothAdapter != null) {
|
||||||
|
|
||||||
|
// Is Bluetooth turned on?
|
||||||
|
if (mBluetoothAdapter.isEnabled()) {
|
||||||
|
|
||||||
|
// Are Bluetooth Advertisements supported on this device?
|
||||||
|
if (mBluetoothAdapter.isMultipleAdvertisementSupported()) {
|
||||||
|
|
||||||
|
// Everything is supported and enabled, load the fragments.
|
||||||
|
setupFragments();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Bluetooth Advertisements are not supported.
|
||||||
|
showErrorText(R.string.bt_ads_not_supported);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Prompt user to turn on Bluetooth (logic continues in onActivityResult()).
|
||||||
|
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
|
||||||
|
startActivityForResult(enableBtIntent, Constants.REQUEST_ENABLE_BT);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Bluetooth is not supported.
|
||||||
|
showErrorText(R.string.bt_not_supported);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
switch (requestCode) {
|
||||||
|
case Constants.REQUEST_ENABLE_BT:
|
||||||
|
|
||||||
|
if (resultCode == RESULT_OK) {
|
||||||
|
|
||||||
|
// Bluetooth is now Enabled, are Bluetooth Advertisements supported on
|
||||||
|
// this device?
|
||||||
|
if (mBluetoothAdapter.isMultipleAdvertisementSupported()) {
|
||||||
|
|
||||||
|
// Everything is supported and enabled, load the fragments.
|
||||||
|
setupFragments();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Bluetooth Advertisements are not supported.
|
||||||
|
showErrorText(R.string.bt_ads_not_supported);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// User declined to enable Bluetooth, exit the app.
|
||||||
|
Toast.makeText(this, R.string.bt_not_enabled_leaving,
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupFragments() {
|
||||||
|
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||||
|
|
||||||
|
ScannerFragment scannerFragment = new ScannerFragment();
|
||||||
|
scannerFragment.setBluetoothAdapter(mBluetoothAdapter);
|
||||||
|
transaction.replace(R.id.scanner_fragment_container, scannerFragment);
|
||||||
|
|
||||||
|
AdvertiserFragment advertiserFragment = new AdvertiserFragment();
|
||||||
|
advertiserFragment.setBluetoothAdapter(mBluetoothAdapter);
|
||||||
|
transaction.replace(R.id.advertiser_fragment_container, advertiserFragment);
|
||||||
|
|
||||||
|
transaction.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showErrorText(int messageId) {
|
||||||
|
|
||||||
|
TextView view = (TextView) findViewById(R.id.error_textview);
|
||||||
|
view.setText(getString(messageId));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
package com.example.android.bluetoothadvertisements;
|
||||||
|
|
||||||
|
import android.bluetooth.le.ScanResult;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.BaseAdapter;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds and displays {@link ScanResult}s, used by {@link ScannerFragment}.
|
||||||
|
*/
|
||||||
|
public class ScanResultAdapter extends BaseAdapter {
|
||||||
|
|
||||||
|
private ArrayList<ScanResult> mArrayList;
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
|
||||||
|
private LayoutInflater mInflater;
|
||||||
|
|
||||||
|
ScanResultAdapter(Context context, LayoutInflater inflater) {
|
||||||
|
super();
|
||||||
|
mContext = context;
|
||||||
|
mInflater = inflater;
|
||||||
|
mArrayList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return mArrayList.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getItem(int position) {
|
||||||
|
return mArrayList.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(int position) {
|
||||||
|
return mArrayList.get(position).getDevice().getAddress().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View view, ViewGroup parent) {
|
||||||
|
|
||||||
|
// Reuse an old view if we can, otherwise create a new one.
|
||||||
|
if (view == null) {
|
||||||
|
view = mInflater.inflate(R.layout.listitem_scanresult, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
TextView deviceNameView = (TextView) view.findViewById(R.id.device_name);
|
||||||
|
TextView deviceAddressView = (TextView) view.findViewById(R.id.device_address);
|
||||||
|
TextView lastSeenView = (TextView) view.findViewById(R.id.last_seen);
|
||||||
|
|
||||||
|
ScanResult scanResult = mArrayList.get(position);
|
||||||
|
|
||||||
|
deviceNameView.setText(scanResult.getDevice().getName());
|
||||||
|
deviceAddressView.setText(scanResult.getDevice().getAddress());
|
||||||
|
lastSeenView.setText(getTimeSinceString(mContext, scanResult.getTimestampNanos()));
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search the adapter for an existing device address and return it, otherwise return -1.
|
||||||
|
*/
|
||||||
|
private int getPosition(String address) {
|
||||||
|
int position = -1;
|
||||||
|
for (int i = 0; i < mArrayList.size(); i++) {
|
||||||
|
if (mArrayList.get(i).getDevice().getAddress().equals(address)) {
|
||||||
|
position = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a ScanResult item to the adapter if a result from that device isn't already present.
|
||||||
|
* Otherwise updates the existing position with the new ScanResult.
|
||||||
|
*/
|
||||||
|
public void add(ScanResult scanResult) {
|
||||||
|
|
||||||
|
int existingPosition = getPosition(scanResult.getDevice().getAddress());
|
||||||
|
|
||||||
|
if (existingPosition >= 0) {
|
||||||
|
// Device is already in list, update its record.
|
||||||
|
mArrayList.set(existingPosition, scanResult);
|
||||||
|
} else {
|
||||||
|
// Add new Device's ScanResult to list.
|
||||||
|
mArrayList.add(scanResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear out the adapter.
|
||||||
|
*/
|
||||||
|
public void clear() {
|
||||||
|
mArrayList.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes in a number of nanoseconds and returns a human-readable string giving a vague
|
||||||
|
* description of how long ago that was.
|
||||||
|
*/
|
||||||
|
public static String getTimeSinceString(Context context, long timeNanoseconds) {
|
||||||
|
String lastSeenText = context.getResources().getString(R.string.last_seen) + " ";
|
||||||
|
|
||||||
|
long timeSince = SystemClock.elapsedRealtimeNanos() - timeNanoseconds;
|
||||||
|
long secondsSince = TimeUnit.SECONDS.convert(timeSince, TimeUnit.NANOSECONDS);
|
||||||
|
|
||||||
|
if (secondsSince < 5) {
|
||||||
|
lastSeenText += context.getResources().getString(R.string.just_now);
|
||||||
|
} else if (secondsSince < 60) {
|
||||||
|
lastSeenText += secondsSince + " " + context.getResources()
|
||||||
|
.getString(R.string.seconds_ago);
|
||||||
|
} else {
|
||||||
|
long minutesSince = TimeUnit.MINUTES.convert(secondsSince, TimeUnit.SECONDS);
|
||||||
|
if (minutesSince < 60) {
|
||||||
|
if (minutesSince == 1) {
|
||||||
|
lastSeenText += minutesSince + " " + context.getResources()
|
||||||
|
.getString(R.string.minute_ago);
|
||||||
|
} else {
|
||||||
|
lastSeenText += minutesSince + " " + context.getResources()
|
||||||
|
.getString(R.string.minutes_ago);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
long hoursSince = TimeUnit.HOURS.convert(minutesSince, TimeUnit.MINUTES);
|
||||||
|
if (hoursSince == 1) {
|
||||||
|
lastSeenText += hoursSince + " " + context.getResources()
|
||||||
|
.getString(R.string.hour_ago);
|
||||||
|
} else {
|
||||||
|
lastSeenText += hoursSince + " " + context.getResources()
|
||||||
|
.getString(R.string.hours_ago);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastSeenText;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
package com.example.android.bluetoothadvertisements;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
import android.bluetooth.le.BluetoothLeScanner;
|
||||||
|
import android.bluetooth.le.ScanCallback;
|
||||||
|
import android.bluetooth.le.ScanFilter;
|
||||||
|
import android.bluetooth.le.ScanResult;
|
||||||
|
import android.bluetooth.le.ScanSettings;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.support.v4.app.ListFragment;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans for Bluetooth Low Energy Advertisements matching a filter and displays them to the user.
|
||||||
|
*/
|
||||||
|
public class ScannerFragment extends ListFragment {
|
||||||
|
|
||||||
|
private static final String TAG = ScannerFragment.class.getSimpleName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops scanning after 5 seconds.
|
||||||
|
*/
|
||||||
|
private static final long SCAN_PERIOD = 5000;
|
||||||
|
|
||||||
|
private BluetoothAdapter mBluetoothAdapter;
|
||||||
|
|
||||||
|
private BluetoothLeScanner mBluetoothLeScanner;
|
||||||
|
|
||||||
|
private ScanCallback mScanCallback;
|
||||||
|
|
||||||
|
private ScanResultAdapter mAdapter;
|
||||||
|
|
||||||
|
private Handler mHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Must be called after object creation by MainActivity.
|
||||||
|
*
|
||||||
|
* @param btAdapter the local BluetoothAdapter
|
||||||
|
*/
|
||||||
|
public void setBluetoothAdapter(BluetoothAdapter btAdapter) {
|
||||||
|
this.mBluetoothAdapter = btAdapter;
|
||||||
|
mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
setRetainInstance(true);
|
||||||
|
|
||||||
|
// Use getActivity().getApplicationContext() instead of just getActivity() because this
|
||||||
|
// object lives in a fragment and needs to be kept separate from the Activity lifecycle.
|
||||||
|
//
|
||||||
|
// We could get a LayoutInflater from the ApplicationContext but it messes with the
|
||||||
|
// default theme, so generate it from getActivity() and pass it in separately.
|
||||||
|
mAdapter = new ScanResultAdapter(getActivity().getApplicationContext(),
|
||||||
|
LayoutInflater.from(getActivity()));
|
||||||
|
mHandler = new Handler();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
|
||||||
|
final View view = super.onCreateView(inflater, container, savedInstanceState);
|
||||||
|
|
||||||
|
setListAdapter(mAdapter);
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
|
getListView().setDivider(null);
|
||||||
|
getListView().setDividerHeight(0);
|
||||||
|
|
||||||
|
setEmptyText(getString(R.string.empty_list));
|
||||||
|
|
||||||
|
// Trigger refresh on app's 1st load
|
||||||
|
startScanning();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
|
inflater.inflate(R.menu.scanner_menu, menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.refresh:
|
||||||
|
startScanning();
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start scanning for BLE Advertisements (& set it up to stop after a set period of time).
|
||||||
|
*/
|
||||||
|
public void startScanning() {
|
||||||
|
if (mScanCallback == null) {
|
||||||
|
Log.d(TAG, "Starting Scanning");
|
||||||
|
|
||||||
|
// Will stop the scanning after a set time.
|
||||||
|
mHandler.postDelayed(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
stopScanning();
|
||||||
|
}
|
||||||
|
}, SCAN_PERIOD);
|
||||||
|
|
||||||
|
// Kick off a new scan.
|
||||||
|
mScanCallback = new SampleScanCallback();
|
||||||
|
mBluetoothLeScanner.startScan(buildScanFilters(), buildScanSettings(), mScanCallback);
|
||||||
|
|
||||||
|
String toastText = getString(R.string.scan_start_toast) + " "
|
||||||
|
+ TimeUnit.SECONDS.convert(SCAN_PERIOD, TimeUnit.MILLISECONDS) + " "
|
||||||
|
+ getString(R.string.seconds);
|
||||||
|
Toast.makeText(getActivity(), toastText, Toast.LENGTH_LONG).show();
|
||||||
|
} else {
|
||||||
|
Toast.makeText(getActivity(), R.string.already_scanning, Toast.LENGTH_SHORT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop scanning for BLE Advertisements.
|
||||||
|
*/
|
||||||
|
public void stopScanning() {
|
||||||
|
Log.d(TAG, "Stopping Scanning");
|
||||||
|
|
||||||
|
// Stop the scan, wipe the callback.
|
||||||
|
mBluetoothLeScanner.stopScan(mScanCallback);
|
||||||
|
mScanCallback = null;
|
||||||
|
|
||||||
|
// Even if no new results, update 'last seen' times.
|
||||||
|
mAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a List of {@link ScanFilter} objects to filter by Service UUID.
|
||||||
|
*/
|
||||||
|
private List<ScanFilter> buildScanFilters() {
|
||||||
|
List<ScanFilter> scanFilters = new ArrayList<>();
|
||||||
|
|
||||||
|
ScanFilter.Builder builder = new ScanFilter.Builder();
|
||||||
|
builder.setServiceUuid(Constants.Service_UUID);
|
||||||
|
scanFilters.add(builder.build());
|
||||||
|
|
||||||
|
return scanFilters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a {@link ScanSettings} object set to use low power (to preserve battery life).
|
||||||
|
*/
|
||||||
|
private ScanSettings buildScanSettings() {
|
||||||
|
ScanSettings.Builder builder = new ScanSettings.Builder();
|
||||||
|
builder.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom ScanCallback object - adds to adapter on success, displays error on failure.
|
||||||
|
*/
|
||||||
|
private class SampleScanCallback extends ScanCallback {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBatchScanResults(List<ScanResult> results) {
|
||||||
|
super.onBatchScanResults(results);
|
||||||
|
|
||||||
|
for (ScanResult result : results) {
|
||||||
|
mAdapter.add(result);
|
||||||
|
}
|
||||||
|
mAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScanResult(int callbackType, ScanResult result) {
|
||||||
|
super.onScanResult(callbackType, result);
|
||||||
|
|
||||||
|
mAdapter.add(result);
|
||||||
|
mAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScanFailed(int errorCode) {
|
||||||
|
super.onScanFailed(errorCode);
|
||||||
|
Toast.makeText(getActivity(), "Scan failed with error: " + errorCode, Toast.LENGTH_LONG)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,7 +37,7 @@ import android.util.Log;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.google.android.gms.common.ConnectionResult;
|
import com.google.android.gms.common.ConnectionResult;
|
||||||
import com.google.android.gms.common.GooglePlayServicesClient.ConnectionCallbacks;
|
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
|
||||||
import com.google.android.gms.common.GooglePlayServicesUtil;
|
import com.google.android.gms.common.GooglePlayServicesUtil;
|
||||||
import com.google.android.gms.common.api.GoogleApiClient;
|
import com.google.android.gms.common.api.GoogleApiClient;
|
||||||
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
|
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
|
||||||
@@ -48,7 +48,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class MainActivity extends Activity implements ConnectionCallbacks,
|
public class MainActivity extends Activity implements ConnectionCallbacks,
|
||||||
OnConnectionFailedListener, GoogleApiClient.ConnectionCallbacks {
|
OnConnectionFailedListener {
|
||||||
|
|
||||||
// Internal List of Geofence objects. In a real app, these might be provided by an API based on
|
// Internal List of Geofence objects. In a real app, these might be provided by an API based on
|
||||||
// locations within the user's proximity.
|
// locations within the user's proximity.
|
||||||
@@ -143,10 +143,6 @@ public class MainActivity extends Activity implements ConnectionCallbacks,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDisconnected() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Once the connection is available, send a request to add the Geofences.
|
* Once the connection is available, send a request to add the Geofences.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -58,6 +58,10 @@
|
|||||||
<meta-data android:name="com.google.android.gms.version"
|
<meta-data android:name="com.google.android.gms.version"
|
||||||
android:value="@integer/google_play_services_version" />
|
android:value="@integer/google_play_services_version" />
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="com.example.android.xyztouristattractions.config.GlideConfiguration"
|
||||||
|
android:value="GlideModule"/>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -17,10 +17,6 @@
|
|||||||
|
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<color name="colorPrimary">#4e6cef</color>
|
|
||||||
<color name="colorPrimaryDark">#2a36b1</color>
|
|
||||||
<color name="colorAccent">#ff7043</color>
|
|
||||||
|
|
||||||
<color name="text_background">#90000000</color>
|
<color name="text_background">#90000000</color>
|
||||||
<color name="transparent_actionbar_background">#22000000</color>
|
<color name="transparent_actionbar_background">#22000000</color>
|
||||||
<color name="lighter_gray">#ddd</color>
|
<color name="lighter_gray">#ddd</color>
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.xyztouristattractions.config;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.bumptech.glide.GlideBuilder;
|
||||||
|
import com.bumptech.glide.load.DecodeFormat;
|
||||||
|
import com.bumptech.glide.module.GlideModule;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This allows global overriding of some default Glide configuration values.
|
||||||
|
* For additional information see the Glide docs:
|
||||||
|
* https://github.com/bumptech/glide/wiki/Configuration
|
||||||
|
*/
|
||||||
|
public class GlideConfiguration implements GlideModule {
|
||||||
|
@Override
|
||||||
|
public void applyOptions(Context context, GlideBuilder builder) {
|
||||||
|
// Set Glide decode format to the higher quality ARGB_8888 format
|
||||||
|
builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerComponents(Context context, Glide glide) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,9 +16,9 @@
|
|||||||
|
|
||||||
package com.example.android.xyztouristattractions.ui;
|
package com.example.android.xyztouristattractions.ui;
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v7.app.ActionBarActivity;
|
import android.support.v7.app.AlertDialog;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
@@ -31,7 +31,7 @@ import com.example.android.xyztouristattractions.service.UtilityService;
|
|||||||
* The main tourist attraction activity screen which contains a list of
|
* The main tourist attraction activity screen which contains a list of
|
||||||
* attractions sorted by distance.
|
* attractions sorted by distance.
|
||||||
*/
|
*/
|
||||||
public class AttractionListActivity extends ActionBarActivity {
|
public class AttractionListActivity extends AppCompatActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import android.os.Build;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.ActivityCompat;
|
import android.support.v4.app.ActivityCompat;
|
||||||
import android.support.v4.app.ActivityOptionsCompat;
|
import android.support.v4.app.ActivityOptionsCompat;
|
||||||
import android.support.v7.app.ActionBarActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import com.example.android.xyztouristattractions.R;
|
import com.example.android.xyztouristattractions.R;
|
||||||
@@ -33,7 +33,7 @@ import com.example.android.xyztouristattractions.R;
|
|||||||
* The tourist attraction detail activity screen which contains the details of
|
* The tourist attraction detail activity screen which contains the details of
|
||||||
* a single attraction.
|
* a single attraction.
|
||||||
*/
|
*/
|
||||||
public class DetailActivity extends ActionBarActivity {
|
public class DetailActivity extends AppCompatActivity {
|
||||||
|
|
||||||
private static final String EXTRA_ATTRACTION = "attraction";
|
private static final String EXTRA_ATTRACTION = "attraction";
|
||||||
|
|
||||||
|
|||||||
@@ -15,11 +15,10 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
<resources>
|
||||||
|
|
||||||
<item android:state_pressed="true"
|
<color name="colorPrimary">#4e6cef</color>
|
||||||
android:color="#ee3c4b90" /> <!-- pressed -->
|
<color name="colorPrimaryDark">#2a36b1</color>
|
||||||
|
<color name="colorAccent">#ff7043</color>
|
||||||
|
|
||||||
<item android:color="#ee5c6bc0" /> <!-- default -->
|
</resources>
|
||||||
|
|
||||||
</selector>
|
|
||||||
|
Before Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 15 KiB |
@@ -41,9 +41,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
app:dotFadeWhenIdle="false"
|
app:dotFadeWhenIdle="false"
|
||||||
app:dotFadeInDuration="0"
|
|
||||||
app:dotFadeOutDuration="0"
|
|
||||||
app:dotFadeOutDelay="0"
|
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
<android.support.wearable.view.DismissOverlayView
|
<android.support.wearable.view.DismissOverlayView
|
||||||
|
|||||||
@@ -16,11 +16,14 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<android.support.wearable.view.WatchViewStub
|
<android.support.wearable.view.ActionPage
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/actionpage"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:rectLayout="@layout/gridpager_action_square"
|
android:src="@drawable/ic_full_openonphone"
|
||||||
app:roundLayout="@layout/gridpager_action_round"
|
android:text="@string/action_open"
|
||||||
android:clickable="true" />
|
android:maxLines="1"
|
||||||
|
android:color="@color/colorPrimary"
|
||||||
|
app:rippleColor="@color/colorAccent" />
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
<?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.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:background="#7F000000"/>
|
|
||||||
|
|
||||||
<android.support.wearable.view.CircledImageView
|
|
||||||
android:id="@+id/circleImageView"
|
|
||||||
android:layout_width="112dp"
|
|
||||||
android:layout_height="112dp"
|
|
||||||
android:layout_centerInParent="true"
|
|
||||||
app:circle_radius="52dp"
|
|
||||||
app:circle_radius_pressed="56dp"
|
|
||||||
app:circle_color="@color/action_color">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/imageView"
|
|
||||||
android:layout_width="64dp"
|
|
||||||
android:layout_height="64dp"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:src="@drawable/ic_full_open_on_device"
|
|
||||||
android:scaleType="centerCrop" />
|
|
||||||
|
|
||||||
</android.support.wearable.view.CircledImageView>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/textView"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
style="@style/ActionTextStyleRound"
|
|
||||||
android:layout_below="@id/circleImageView"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:gravity="center"
|
|
||||||
tools:text="Navigate" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
<?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.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:background="#7F000000"/>
|
|
||||||
|
|
||||||
<android.support.wearable.view.CircledImageView
|
|
||||||
android:id="@+id/circleImageView"
|
|
||||||
android:layout_width="112dp"
|
|
||||||
android:layout_height="112dp"
|
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_marginTop="24dp"
|
|
||||||
app:circle_radius="52dp"
|
|
||||||
app:circle_radius_pressed="56dp"
|
|
||||||
app:circle_color="@color/action_color">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/imageView"
|
|
||||||
android:layout_width="64dp"
|
|
||||||
android:layout_height="64dp"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:src="@drawable/ic_full_open_on_device"
|
|
||||||
android:scaleType="centerCrop" />
|
|
||||||
|
|
||||||
</android.support.wearable.view.CircledImageView>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/textView"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
style="@style/ActionTextStyle"
|
|
||||||
android:layout_below="@id/circleImageView"
|
|
||||||
android:layout_marginBottom="12dp"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:maxLines="2"
|
|
||||||
android:gravity="center"
|
|
||||||
tools:text="Navigate" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
@@ -31,18 +31,4 @@
|
|||||||
<item name="android:ellipsize">end</item>
|
<item name="android:ellipsize">end</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="ActionTextStyle" parent="@android:style/TextAppearance.Large">
|
|
||||||
<item name="android:fontFamily">sans-serif-condensed-light</item>
|
|
||||||
<item name="android:textStyle">normal</item>
|
|
||||||
<item name="android:textSize">18sp</item>
|
|
||||||
<item name="android:maxLines">2</item>
|
|
||||||
<item name="android:ellipsize">end</item>
|
|
||||||
<item name="android:textColor">#FFFFFF</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="ActionTextStyleRound" parent="ActionTextStyle">
|
|
||||||
<item name="android:textSize">14sp</item>
|
|
||||||
<item name="android:maxLines">1</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -84,6 +84,8 @@ public class AttractionsActivity extends Activity
|
|||||||
mAdapter = new AttractionsGridPagerAdapter(this, mAttractions);
|
mAdapter = new AttractionsGridPagerAdapter(this, mAttractions);
|
||||||
mAdapter.setOnChromeFadeListener(this);
|
mAdapter.setOnChromeFadeListener(this);
|
||||||
mGridViewPager.setAdapter(mAdapter);
|
mGridViewPager.setAdapter(mAdapter);
|
||||||
|
mDotsPageIndicator.setPager(mGridViewPager);
|
||||||
|
mDotsPageIndicator.setOnPageChangeListener(mAdapter);
|
||||||
|
|
||||||
topFrameLayout.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
|
topFrameLayout.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -243,8 +245,6 @@ public class AttractionsActivity extends Activity
|
|||||||
// Update UI based on the result of the background processing
|
// Update UI based on the result of the background processing
|
||||||
mAdapter.setData(result);
|
mAdapter.setData(result);
|
||||||
mAdapter.notifyDataSetChanged();
|
mAdapter.notifyDataSetChanged();
|
||||||
mDotsPageIndicator.setPager(mGridViewPager);
|
|
||||||
mDotsPageIndicator.setOnPageChangeListener(mAdapter);
|
|
||||||
mProgressBar.setVisibility(View.GONE);
|
mProgressBar.setVisibility(View.GONE);
|
||||||
mDotsPageIndicator.setVisibility(View.VISIBLE);
|
mDotsPageIndicator.setVisibility(View.VISIBLE);
|
||||||
mGridViewPager.setVisibility(View.VISIBLE);
|
mGridViewPager.setVisibility(View.VISIBLE);
|
||||||
|
|||||||
@@ -25,11 +25,11 @@ import android.graphics.drawable.ColorDrawable;
|
|||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.support.wearable.activity.ConfirmationActivity;
|
import android.support.wearable.activity.ConfirmationActivity;
|
||||||
|
import android.support.wearable.view.ActionPage;
|
||||||
import android.support.wearable.view.CardFrame;
|
import android.support.wearable.view.CardFrame;
|
||||||
import android.support.wearable.view.CardScrollView;
|
import android.support.wearable.view.CardScrollView;
|
||||||
import android.support.wearable.view.GridPagerAdapter;
|
import android.support.wearable.view.GridPagerAdapter;
|
||||||
import android.support.wearable.view.GridViewPager;
|
import android.support.wearable.view.GridViewPager;
|
||||||
import android.support.wearable.view.WatchViewStub;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -97,7 +97,7 @@ public class AttractionsGridPagerAdapter extends GridPagerAdapter
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Object instantiateItem(ViewGroup container, int row, final int column) {
|
public Object instantiateItem(ViewGroup container, int row, final int column) {
|
||||||
if (mAttractions != null && mAttractions.size() > 0) {
|
if (mAttractions != null && mAttractions.size() > 0) {
|
||||||
final Attraction attraction = mAttractions.get(row);
|
final Attraction attraction = mAttractions.get(row);
|
||||||
switch (column) {
|
switch (column) {
|
||||||
@@ -150,48 +150,30 @@ public class AttractionsGridPagerAdapter extends GridPagerAdapter
|
|||||||
return cardScrollView;
|
return cardScrollView;
|
||||||
case PAGER_NAVIGATE_ACTION_COLUMN:
|
case PAGER_NAVIGATE_ACTION_COLUMN:
|
||||||
// The navigate action
|
// The navigate action
|
||||||
final WatchViewStub navStub = (WatchViewStub) mLayoutInflater.inflate(
|
final ActionPage navActionPage = (ActionPage) mLayoutInflater.inflate(
|
||||||
R.layout.gridpager_action, container, false);
|
R.layout.gridpager_action, container, false);
|
||||||
|
|
||||||
navStub.setOnClickListener(getStartActionClickListener(
|
navActionPage.setOnClickListener(getStartActionClickListener(
|
||||||
attraction, Constants.START_NAVIGATION_PATH,
|
attraction, Constants.START_NAVIGATION_PATH,
|
||||||
ConfirmationActivity.SUCCESS_ANIMATION));
|
ConfirmationActivity.SUCCESS_ANIMATION));
|
||||||
|
navActionPage.setImageResource(R.drawable.ic_full_directions_walking);
|
||||||
|
navActionPage.setText(mContext.getString(R.string.action_navigate));
|
||||||
|
|
||||||
navStub.setOnLayoutInflatedListener(
|
container.addView(navActionPage);
|
||||||
new WatchViewStub.OnLayoutInflatedListener() {
|
return navActionPage;
|
||||||
@Override
|
|
||||||
public void onLayoutInflated(WatchViewStub watchViewStub) {
|
|
||||||
ImageView imageView = (ImageView) navStub.findViewById(R.id.imageView);
|
|
||||||
imageView.setImageResource(R.drawable.ic_full_directions_walking);
|
|
||||||
TextView textView = (TextView) navStub.findViewById(R.id.textView);
|
|
||||||
textView.setText(R.string.action_navigate);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
container.addView(navStub);
|
|
||||||
return navStub;
|
|
||||||
case PAGER_OPEN_ACTION_COLUMN:
|
case PAGER_OPEN_ACTION_COLUMN:
|
||||||
// The "open on device" action
|
// The "open on device" action
|
||||||
final WatchViewStub openStub = (WatchViewStub) mLayoutInflater.inflate(
|
final ActionPage openActionPage = (ActionPage) mLayoutInflater.inflate(
|
||||||
R.layout.gridpager_action, container, false);
|
R.layout.gridpager_action, container, false);
|
||||||
|
|
||||||
openStub.setOnClickListener(getStartActionClickListener(
|
openActionPage.setOnClickListener(getStartActionClickListener(
|
||||||
attraction, Constants.START_ATTRACTION_PATH,
|
attraction, Constants.START_ATTRACTION_PATH,
|
||||||
ConfirmationActivity.OPEN_ON_PHONE_ANIMATION));
|
ConfirmationActivity.OPEN_ON_PHONE_ANIMATION));
|
||||||
|
openActionPage.setImageResource(R.drawable.ic_full_openonphone);
|
||||||
|
openActionPage.setText(mContext.getString(R.string.action_open));
|
||||||
|
|
||||||
openStub.setOnLayoutInflatedListener(
|
container.addView(openActionPage);
|
||||||
new WatchViewStub.OnLayoutInflatedListener() {
|
return openActionPage;
|
||||||
@Override
|
|
||||||
public void onLayoutInflated(WatchViewStub watchViewStub) {
|
|
||||||
ImageView imageView = (ImageView) openStub.findViewById(R.id.imageView);
|
|
||||||
imageView.setImageResource(R.drawable.ic_full_open_on_device);
|
|
||||||
TextView textView = (TextView) openStub.findViewById(R.id.textView);
|
|
||||||
textView.setText(R.string.action_open);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
container.addView(openStub);
|
|
||||||
return openStub;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new View(mContext);
|
return new View(mContext);
|
||||||
@@ -209,7 +191,7 @@ public class AttractionsGridPagerAdapter extends GridPagerAdapter
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void destroyItem(ViewGroup viewGroup, int row, int column, Object object) {
|
public void destroyItem(ViewGroup viewGroup, int row, int column, Object object) {
|
||||||
mDelayedHide.remove((View) object);
|
mDelayedHide.remove((View) object);
|
||||||
viewGroup.removeView((View)object);
|
viewGroup.removeView((View)object);
|
||||||
}
|
}
|
||||||
@@ -242,6 +224,7 @@ public class AttractionsGridPagerAdapter extends GridPagerAdapter
|
|||||||
private void startAction(Attraction attraction, String pathName, int confirmAnimationType) {
|
private void startAction(Attraction attraction, String pathName, int confirmAnimationType) {
|
||||||
Intent intent = new Intent(mContext, ConfirmationActivity.class);
|
Intent intent = new Intent(mContext, ConfirmationActivity.class);
|
||||||
intent.putExtra(ConfirmationActivity.EXTRA_ANIMATION_TYPE, confirmAnimationType);
|
intent.putExtra(ConfirmationActivity.EXTRA_ANIMATION_TYPE, confirmAnimationType);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||||
mContext.startActivity(intent);
|
mContext.startActivity(intent);
|
||||||
|
|
||||||
UtilityService.clearNotification(mContext);
|
UtilityService.clearNotification(mContext);
|
||||||
@@ -270,8 +253,8 @@ public class AttractionsGridPagerAdapter extends GridPagerAdapter
|
|||||||
}
|
}
|
||||||
|
|
||||||
public interface OnChromeFadeListener {
|
public interface OnChromeFadeListener {
|
||||||
abstract void onChromeFadeIn();
|
void onChromeFadeIn();
|
||||||
abstract void onChromeFadeOut();
|
void onChromeFadeOut();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||