diff options
| -rw-r--r-- | altosdroid/.classpath | 7 | ||||
| -rw-r--r-- | altosdroid/.project | 33 | ||||
| -rw-r--r-- | altosdroid/.settings/org.eclipse.jdt.core.prefs | 5 | ||||
| -rw-r--r-- | altosdroid/AndroidManifest.xml | 38 | ||||
| -rw-r--r-- | altosdroid/default.properties | 11 | ||||
| -rw-r--r-- | altosdroid/keystore | bin | 0 -> 1270 bytes | |||
| -rw-r--r-- | altosdroid/res/drawable-hdpi/app_icon.png | bin | 0 -> 5589 bytes | |||
| -rw-r--r-- | altosdroid/res/drawable/app_icon.png | bin | 0 -> 4182 bytes | |||
| -rw-r--r-- | altosdroid/res/layout/custom_title.xml | 39 | ||||
| -rw-r--r-- | altosdroid/res/layout/device_list.xml | 56 | ||||
| -rw-r--r-- | altosdroid/res/layout/device_name.xml | 21 | ||||
| -rw-r--r-- | altosdroid/res/layout/main.xml | 46 | ||||
| -rw-r--r-- | altosdroid/res/layout/message.xml | 21 | ||||
| -rw-r--r-- | altosdroid/res/menu/option_menu.xml | 26 | ||||
| -rw-r--r-- | altosdroid/res/values/strings.xml | 41 | ||||
| -rw-r--r-- | altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java | 357 | ||||
| -rw-r--r-- | altosdroid/src/org/altusmetrum/AltosDroid/BluetoothChatService.java | 370 | ||||
| -rw-r--r-- | altosdroid/src/org/altusmetrum/AltosDroid/DeviceListActivity.java | 203 | 
18 files changed, 1274 insertions, 0 deletions
| diff --git a/altosdroid/.classpath b/altosdroid/.classpath new file mode 100644 index 00000000..6efcbb73 --- /dev/null +++ b/altosdroid/.classpath @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> +	<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> +	<classpathentry kind="src" path="src"/> +	<classpathentry kind="src" path="gen"/> +	<classpathentry kind="output" path="bin"/> +</classpath> diff --git a/altosdroid/.project b/altosdroid/.project new file mode 100644 index 00000000..7b56596a --- /dev/null +++ b/altosdroid/.project @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> +	<name>AltosDroid</name> +	<comment></comment> +	<projects> +	</projects> +	<buildSpec> +		<buildCommand> +			<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name> +			<arguments> +			</arguments> +		</buildCommand> +		<buildCommand> +			<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name> +			<arguments> +			</arguments> +		</buildCommand> +		<buildCommand> +			<name>org.eclipse.jdt.core.javabuilder</name> +			<arguments> +			</arguments> +		</buildCommand> +		<buildCommand> +			<name>com.android.ide.eclipse.adt.ApkBuilder</name> +			<arguments> +			</arguments> +		</buildCommand> +	</buildSpec> +	<natures> +		<nature>com.android.ide.eclipse.adt.AndroidNature</nature> +		<nature>org.eclipse.jdt.core.javanature</nature> +	</natures> +</projectDescription> diff --git a/altosdroid/.settings/org.eclipse.jdt.core.prefs b/altosdroid/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..e5d1cd30 --- /dev/null +++ b/altosdroid/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,5 @@ +#Wed Sep 28 19:51:24 NZDT 2011 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.source=1.5 diff --git a/altosdroid/AndroidManifest.xml b/altosdroid/AndroidManifest.xml new file mode 100644 index 00000000..b72f0384 --- /dev/null +++ b/altosdroid/AndroidManifest.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 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="org.altusmetrum.AltosDroid" +      android:versionCode="1" +      android:versionName="1.0"> +    <uses-sdk minSdkVersion="6" /> +    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> +    <uses-permission android:name="android.permission.BLUETOOTH" /> + +    <application android:label="@string/app_name" +                 android:icon="@drawable/app_icon" > +        <activity android:label="@string/app_name" +                  android:configChanges="orientation|keyboardHidden" android:name="org.altusmetrum.AltosDroid.AltosDroid"> +            <intent-filter> +                <action android:name="android.intent.action.MAIN" /> +                <category android:name="android.intent.category.LAUNCHER" /> +            </intent-filter> +        </activity> +        <activity android:name=".DeviceListActivity" +                  android:label="@string/select_device" +                  android:theme="@android:style/Theme.Dialog" +                  android:configChanges="orientation|keyboardHidden" /> +    </application> +</manifest> diff --git a/altosdroid/default.properties b/altosdroid/default.properties new file mode 100644 index 00000000..66db0d15 --- /dev/null +++ b/altosdroid/default.properties @@ -0,0 +1,11 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "build.properties", and override values to adapt the script to your +# project structure. + +# Project target. +target=android-10 diff --git a/altosdroid/keystore b/altosdroid/keystoreBinary files differ new file mode 100644 index 00000000..00739d03 --- /dev/null +++ b/altosdroid/keystore diff --git a/altosdroid/res/drawable-hdpi/app_icon.png b/altosdroid/res/drawable-hdpi/app_icon.pngBinary files differ new file mode 100644 index 00000000..8836ff65 --- /dev/null +++ b/altosdroid/res/drawable-hdpi/app_icon.png diff --git a/altosdroid/res/drawable/app_icon.png b/altosdroid/res/drawable/app_icon.pngBinary files differ new file mode 100644 index 00000000..d3e09a5b --- /dev/null +++ b/altosdroid/res/drawable/app_icon.png diff --git a/altosdroid/res/layout/custom_title.xml b/altosdroid/res/layout/custom_title.xml new file mode 100644 index 00000000..57eb6b4a --- /dev/null +++ b/altosdroid/res/layout/custom_title.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 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="match_parent" +    android:gravity="center_vertical" +  > +  <TextView android:id="@+id/title_left_text" +      android:layout_alignParentLeft="true" +      android:ellipsize="end" +      android:singleLine="true" +      style="?android:attr/windowTitleStyle" +      android:layout_width="wrap_content" +      android:layout_height="match_parent" +      android:layout_weight="1" +    /> +    <TextView android:id="@+id/title_right_text" +        android:layout_alignParentRight="true" +        android:ellipsize="end" +        android:singleLine="true" +        android:layout_width="wrap_content" +        android:layout_height="match_parent" +        android:textColor="#fff" +        android:layout_weight="1"  +    /> +</RelativeLayout>
\ No newline at end of file diff --git a/altosdroid/res/layout/device_list.xml b/altosdroid/res/layout/device_list.xml new file mode 100644 index 00000000..395695f8 --- /dev/null +++ b/altosdroid/res/layout/device_list.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 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="match_parent" +    android:layout_height="match_parent" +    > +    <TextView android:id="@+id/title_paired_devices" +        android:layout_width="match_parent" +        android:layout_height="wrap_content" +        android:text="@string/title_paired_devices" +        android:visibility="gone" +        android:background="#666" +        android:textColor="#fff" +        android:paddingLeft="5dp" +    /> +    <ListView android:id="@+id/paired_devices" +        android:layout_width="match_parent" +        android:layout_height="wrap_content" +        android:stackFromBottom="true" +        android:layout_weight="1" +    /> +    <TextView android:id="@+id/title_new_devices" +        android:layout_width="match_parent" +        android:layout_height="wrap_content" +        android:text="@string/title_other_devices" +        android:visibility="gone" +        android:background="#666" +        android:textColor="#fff" +        android:paddingLeft="5dp" +    /> +    <ListView android:id="@+id/new_devices" +        android:layout_width="match_parent" +        android:layout_height="wrap_content" +        android:stackFromBottom="true" +        android:layout_weight="2" +    /> +    <Button android:id="@+id/button_scan" +        android:layout_width="match_parent" +        android:layout_height="wrap_content" +        android:text="@string/button_scan" +    /> +</LinearLayout>
\ No newline at end of file diff --git a/altosdroid/res/layout/device_name.xml b/altosdroid/res/layout/device_name.xml new file mode 100644 index 00000000..8fa358c9 --- /dev/null +++ b/altosdroid/res/layout/device_name.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 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. +--> +<TextView xmlns:android="http://schemas.android.com/apk/res/android" +    android:layout_width="match_parent" +    android:layout_height="wrap_content" +    android:textSize="18sp" +    android:padding="5dp" +/>
\ No newline at end of file diff --git a/altosdroid/res/layout/main.xml b/altosdroid/res/layout/main.xml new file mode 100644 index 00000000..17025f6b --- /dev/null +++ b/altosdroid/res/layout/main.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 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="match_parent" +    android:layout_height="match_parent" +    > +    <ListView android:id="@+id/in" +        android:layout_width="match_parent" +        android:layout_height="match_parent" +        android:stackFromBottom="true" +        android:transcriptMode="alwaysScroll" +        android:layout_weight="1" +    /> +    <LinearLayout +        android:orientation="horizontal" +        android:layout_width="match_parent" +        android:layout_height="wrap_content" +        > +        <EditText android:id="@+id/edit_text_out" +            android:layout_width="wrap_content" +            android:layout_height="wrap_content" +            android:layout_weight="1" +            android:layout_gravity="bottom" +        /> +        <Button android:id="@+id/button_send" +            android:layout_width="wrap_content" +            android:layout_height="wrap_content" +            android:text="@string/send" +        /> +    </LinearLayout> +</LinearLayout> diff --git a/altosdroid/res/layout/message.xml b/altosdroid/res/layout/message.xml new file mode 100644 index 00000000..8fa358c9 --- /dev/null +++ b/altosdroid/res/layout/message.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 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. +--> +<TextView xmlns:android="http://schemas.android.com/apk/res/android" +    android:layout_width="match_parent" +    android:layout_height="wrap_content" +    android:textSize="18sp" +    android:padding="5dp" +/>
\ No newline at end of file diff --git a/altosdroid/res/menu/option_menu.xml b/altosdroid/res/menu/option_menu.xml new file mode 100644 index 00000000..18de725c --- /dev/null +++ b/altosdroid/res/menu/option_menu.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + +     Licensed under the Apache License, Version 2.0 (the "License"); +     you may not use this file except in compliance with the License. +     You may obtain a copy of the License at + +          http://www.apache.org/licenses/LICENSE-2.0 + +     Unless required by applicable law or agreed to in writing, software +     distributed under the License is distributed on an "AS IS" BASIS, +     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +     See the License for the specific language governing permissions and +     limitations under the License. +--> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> +    <item android:id="@+id/secure_connect_scan" +          android:icon="@android:drawable/ic_menu_search" +          android:title="@string/secure_connect" /> +    <item android:id="@+id/insecure_connect_scan" +          android:icon="@android:drawable/ic_menu_search" +          android:title="@string/insecure_connect" /> +    <item android:id="@+id/discoverable" +          android:icon="@android:drawable/ic_menu_mylocation" +          android:title="@string/discoverable" /> +</menu> diff --git a/altosdroid/res/values/strings.xml b/altosdroid/res/values/strings.xml new file mode 100644 index 00000000..53af2a30 --- /dev/null +++ b/altosdroid/res/values/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 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">Bluetooth Chat</string> + +    <!--  BluetoothChat --> +    <string name="send">Send</string> +    <string name="not_connected">You are not connected to a device</string> +    <string name="bt_not_enabled_leaving">Bluetooth was not enabled. Leaving Bluetooth Chat.</string> +    <string name="title_connecting">connecting...</string> +    <string name="title_connected_to">connected: </string> +    <string name="title_not_connected">not connected</string> + +    <!--  DeviceListActivity --> +    <string name="scanning">scanning for devices...</string> +    <string name="select_device">select a device to connect</string> +    <string name="none_paired">No devices have been paired</string> +    <string name="none_found">No devices found</string> +    <string name="title_paired_devices">Paired Devices</string> +    <string name="title_other_devices">Other Available Devices</string> +    <string name="button_scan">Scan for devices</string> + +    <!-- Options Menu --> +    <string name="secure_connect">Connect a device - Secure</string> +    <string name="insecure_connect">Connect a device - Insecure</string> +    <string name="discoverable">Make discoverable</string> +</resources> diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java new file mode 100644 index 00000000..844ca39e --- /dev/null +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2009 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 org.altusmetrum.AltosDroid; + +import android.app.Activity; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.Window; +import android.view.View.OnClickListener; +import android.view.inputmethod.EditorInfo; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; +import org.altusmetrum.AltosDroid.R; + +/** + * This is the main Activity that displays the current chat session. + */ +public class AltosDroid extends Activity { +    // Debugging +    private static final String TAG = "BluetoothChat"; +    private static final boolean D = true; + +    // Message types sent from the BluetoothChatService Handler +    public static final int MESSAGE_STATE_CHANGE = 1; +    public static final int MESSAGE_READ = 2; +    public static final int MESSAGE_WRITE = 3; +    public static final int MESSAGE_DEVICE_NAME = 4; +    public static final int MESSAGE_TOAST = 5; + +    // Key names received from the BluetoothChatService Handler +    public static final String DEVICE_NAME = "device_name"; +    public static final String TOAST = "toast"; + +    // Intent request codes +    private static final int REQUEST_CONNECT_DEVICE_SECURE = 1; +    private static final int REQUEST_CONNECT_DEVICE_INSECURE = 2; +    private static final int REQUEST_ENABLE_BT = 3; + +    // Layout Views +    private TextView mTitle; +    private ListView mConversationView; +    private EditText mOutEditText; +    private Button mSendButton; + +    // Name of the connected device +    private String mConnectedDeviceName = null; +    // Array adapter for the conversation thread +    private ArrayAdapter<String> mConversationArrayAdapter; +    // String buffer for outgoing messages +    private StringBuffer mOutStringBuffer; +    // Local Bluetooth adapter +    private BluetoothAdapter mBluetoothAdapter = null; +    // Member object for the chat services +    private BluetoothChatService mChatService = null; + + +    @Override +    public void onCreate(Bundle savedInstanceState) { +        super.onCreate(savedInstanceState); +        if(D) Log.e(TAG, "+++ ON CREATE +++"); + +        // Set up the window layout +        requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); +        setContentView(R.layout.main); +        getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title); + +        // Set up the custom title +        mTitle = (TextView) findViewById(R.id.title_left_text); +        mTitle.setText(R.string.app_name); +        mTitle = (TextView) findViewById(R.id.title_right_text); + +        // Get local Bluetooth adapter +        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + +        // If the adapter is null, then Bluetooth is not supported +        if (mBluetoothAdapter == null) { +            Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show(); +            finish(); +            return; +        } +    } + +    @Override +    public void onStart() { +        super.onStart(); +        if(D) Log.e(TAG, "++ ON START ++"); + +        // If BT is not on, request that it be enabled. +        // setupChat() will then be called during onActivityResult +        if (!mBluetoothAdapter.isEnabled()) { +            Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); +            startActivityForResult(enableIntent, REQUEST_ENABLE_BT); +        // Otherwise, setup the chat session +        } else { +            if (mChatService == null) setupChat(); +        } +    } + +    @Override +    public synchronized void onResume() { +        super.onResume(); +        if(D) Log.e(TAG, "+ ON RESUME +"); + +        // Performing this check in onResume() covers the case in which BT was +        // not enabled during onStart(), so we were paused to enable it... +        // onResume() will be called when ACTION_REQUEST_ENABLE activity returns. +        if (mChatService != null) { +            // Only if the state is STATE_NONE, do we know that we haven't started already +            if (mChatService.getState() == BluetoothChatService.STATE_NONE) { +              // Start the Bluetooth chat services +              mChatService.start(); +            } +        } +    } + +    private void setupChat() { +        Log.d(TAG, "setupChat()"); + +        // Initialize the array adapter for the conversation thread +        mConversationArrayAdapter = new ArrayAdapter<String>(this, R.layout.message); +        mConversationView = (ListView) findViewById(R.id.in); +        mConversationView.setAdapter(mConversationArrayAdapter); + +        // Initialize the compose field with a listener for the return key +        mOutEditText = (EditText) findViewById(R.id.edit_text_out); +        mOutEditText.setOnEditorActionListener(mWriteListener); + +        // Initialize the send button with a listener that for click events +        mSendButton = (Button) findViewById(R.id.button_send); +        mSendButton.setOnClickListener(new OnClickListener() { +            public void onClick(View v) { +                // Send a message using content of the edit text widget +                TextView view = (TextView) findViewById(R.id.edit_text_out); +                String message = view.getText().toString(); +                sendMessage(message); +            } +        }); + +        // Initialize the BluetoothChatService to perform bluetooth connections +        mChatService = new BluetoothChatService(this, mHandler); + +        // Initialize the buffer for outgoing messages +        mOutStringBuffer = new StringBuffer(""); +    } + +    @Override +    public synchronized void onPause() { +        super.onPause(); +        if(D) Log.e(TAG, "- ON PAUSE -"); +    } + +    @Override +    public void onStop() { +        super.onStop(); +        if(D) Log.e(TAG, "-- ON STOP --"); +    } + +    @Override +    public void onDestroy() { +        super.onDestroy(); +        // Stop the Bluetooth chat services +        if (mChatService != null) mChatService.stop(); +        if(D) Log.e(TAG, "--- ON DESTROY ---"); +    } + +    private void ensureDiscoverable() { +        if(D) Log.d(TAG, "ensure discoverable"); +        if (mBluetoothAdapter.getScanMode() != +            BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { +            Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); +            discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300); +            startActivity(discoverableIntent); +        } +    } + +    /** +     * Sends a message. +     * @param message  A string of text to send. +     */ +    private void sendMessage(String message) { +        // Check that we're actually connected before trying anything +        if (mChatService.getState() != BluetoothChatService.STATE_CONNECTED) { +            Toast.makeText(this, R.string.not_connected, Toast.LENGTH_SHORT).show(); +            return; +        } + +        // Check that there's actually something to send +        if (message.length() > 0) { +            // Get the message bytes and tell the BluetoothChatService to write +            byte[] send = message.getBytes(); +            mChatService.write(send); + +            // Reset out string buffer to zero and clear the edit text field +            mOutStringBuffer.setLength(0); +            mOutEditText.setText(mOutStringBuffer); +        } +    } + +    // The action listener for the EditText widget, to listen for the return key +    private TextView.OnEditorActionListener mWriteListener = +        new TextView.OnEditorActionListener() { +        public boolean onEditorAction(TextView view, int actionId, KeyEvent event) { +            // If the action is a key-up event on the return key, send the message +            if (actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_UP) { +                String message = view.getText().toString(); +                sendMessage(message); +            } +            if(D) Log.i(TAG, "END onEditorAction"); +            return true; +        } +    }; + +    // The Handler that gets information back from the BluetoothChatService +    private final Handler mHandler = new Handler() { +        @Override +        public void handleMessage(Message msg) { +            switch (msg.what) { +            case MESSAGE_STATE_CHANGE: +                if(D) Log.i(TAG, "MESSAGE_STATE_CHANGE: " + msg.arg1); +                switch (msg.arg1) { +                case BluetoothChatService.STATE_CONNECTED: +                    mTitle.setText(R.string.title_connected_to); +                    mTitle.append(mConnectedDeviceName); +                    mConversationArrayAdapter.clear(); +                    break; +                case BluetoothChatService.STATE_CONNECTING: +                    mTitle.setText(R.string.title_connecting); +                    break; +                case BluetoothChatService.STATE_READY: +                case BluetoothChatService.STATE_NONE: +                    mTitle.setText(R.string.title_not_connected); +                    break; +                } +                break; +            case MESSAGE_WRITE: +                byte[] writeBuf = (byte[]) msg.obj; +                // construct a string from the buffer +                String writeMessage = new String(writeBuf); +                mConversationArrayAdapter.add("Me:  " + writeMessage); +                break; +            case MESSAGE_READ: +                byte[] readBuf = (byte[]) msg.obj; +                // construct a string from the valid bytes in the buffer +                String readMessage = new String(readBuf, 0, msg.arg1); +                mConversationArrayAdapter.add(mConnectedDeviceName+":  " + readMessage); +                break; +            case MESSAGE_DEVICE_NAME: +                // save the connected device's name +                mConnectedDeviceName = msg.getData().getString(DEVICE_NAME); +                Toast.makeText(getApplicationContext(), "Connected to " +                               + mConnectedDeviceName, Toast.LENGTH_SHORT).show(); +                break; +            case MESSAGE_TOAST: +                Toast.makeText(getApplicationContext(), msg.getData().getString(TOAST), +                               Toast.LENGTH_SHORT).show(); +                break; +            } +        } +    }; + +    public void onActivityResult(int requestCode, int resultCode, Intent data) { +        if(D) Log.d(TAG, "onActivityResult " + resultCode); +        switch (requestCode) { +        case REQUEST_CONNECT_DEVICE_SECURE: +            // When DeviceListActivity returns with a device to connect +            if (resultCode == Activity.RESULT_OK) { +                connectDevice(data, true); +            } +            break; +        case REQUEST_CONNECT_DEVICE_INSECURE: +            // When DeviceListActivity returns with a device to connect +            if (resultCode == Activity.RESULT_OK) { +                connectDevice(data, false); +            } +            break; +        case REQUEST_ENABLE_BT: +            // When the request to enable Bluetooth returns +            if (resultCode == Activity.RESULT_OK) { +                // Bluetooth is now enabled, so set up a chat session +                setupChat(); +            } else { +                // User did not enable Bluetooth or an error occured +                Log.d(TAG, "BT not enabled"); +                Toast.makeText(this, R.string.bt_not_enabled_leaving, Toast.LENGTH_SHORT).show(); +                finish(); +            } +        } +    } + +    private void connectDevice(Intent data, boolean secure) { +        // Get the device MAC address +        String address = data.getExtras() +            .getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS); +        // Get the BLuetoothDevice object +        BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address); +        // Attempt to connect to the device +        mChatService.connect(device, secure); +    } + +    @Override +    public boolean onCreateOptionsMenu(Menu menu) { +        MenuInflater inflater = getMenuInflater(); +        inflater.inflate(R.menu.option_menu, menu); +        return true; +    } + +    @Override +    public boolean onOptionsItemSelected(MenuItem item) { +        Intent serverIntent = null; +        switch (item.getItemId()) { +        case R.id.secure_connect_scan: +            // Launch the DeviceListActivity to see devices and do scan +            serverIntent = new Intent(this, DeviceListActivity.class); +            startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE_SECURE); +            return true; +        case R.id.insecure_connect_scan: +            // Launch the DeviceListActivity to see devices and do scan +            serverIntent = new Intent(this, DeviceListActivity.class); +            startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE_INSECURE); +            return true; +        case R.id.discoverable: +            // Ensure this device is discoverable by others +            ensureDiscoverable(); +            return true; +        } +        return false; +    } + +} diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/BluetoothChatService.java b/altosdroid/src/org/altusmetrum/AltosDroid/BluetoothChatService.java new file mode 100644 index 00000000..93cb75de --- /dev/null +++ b/altosdroid/src/org/altusmetrum/AltosDroid/BluetoothChatService.java @@ -0,0 +1,370 @@ +/* + * Copyright (C) 2009 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 org.altusmetrum.AltosDroid; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +//import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothSocket; +import android.content.Context; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.util.Log; + +/** + * This class does all the work for setting up and managing Bluetooth + * connections with other devices. It has a thread that listens for + * incoming connections, a thread for connecting with a device, and a + * thread for performing data transmissions when connected. + */ +public class BluetoothChatService { +    // Debugging +    private static final String TAG = "BluetoothChatService"; +    private static final boolean D = true; + +    // Member fields +    private final BluetoothAdapter mAdapter; +    private final Handler mHandler; +    private ConnectThread mConnectThread; +    private ConnectedThread mConnectedThread; +    private int mState; + +    // Constants that indicate the current connection state +    public static final int STATE_NONE = 0;       // we're doing nothing +    public static final int STATE_READY = 1; +    public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection +    public static final int STATE_CONNECTED = 3;  // now connected to a remote device + +    /** +     * Constructor. Prepares a new BluetoothChat session. +     * @param context  The UI Activity Context +     * @param handler  A Handler to send messages back to the UI Activity +     */ +    public BluetoothChatService(Context context, Handler handler) { +        mAdapter = BluetoothAdapter.getDefaultAdapter(); +        mState = STATE_NONE; +        mHandler = handler; +    } + +    /** +     * Set the current state of the chat connection +     * @param state  An integer defining the current connection state +     */ +    private synchronized void setState(int state) { +        if (D) Log.d(TAG, "setState() " + mState + " -> " + state); +        mState = state; + +        // Give the new state to the Handler so the UI Activity can update +        mHandler.obtainMessage(AltosDroid.MESSAGE_STATE_CHANGE, state, -1).sendToTarget(); +    } + +    /** +     * Return the current connection state. */ +    public synchronized int getState() { +        return mState; +    } + +    /** +     * Start the chat service. Specifically start AcceptThread to begin a +     * session in listening (server) mode. Called by the Activity onResume() */ +    public synchronized void start() { +        if (D) Log.d(TAG, "start"); + +        // Cancel any thread attempting to make a connection +        if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;} + +        // Cancel any thread currently running a connection +        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;} + +        setState(STATE_READY); + +    } + +    /** +     * Start the ConnectThread to initiate a connection to a remote device. +     * @param device  The BluetoothDevice to connect +     * @param secure Socket Security type - Secure (true) , Insecure (false) +     */ +    public synchronized void connect(BluetoothDevice device, boolean secure) { +        if (D) Log.d(TAG, "connect to: " + device); + +        // Cancel any thread attempting to make a connection +        if (mState == STATE_CONNECTING) { +            if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;} +        } + +        // Cancel any thread currently running a connection +        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;} + +        // Start the thread to connect with the given device +        mConnectThread = new ConnectThread(device, secure); +        mConnectThread.start(); +        setState(STATE_CONNECTING); +    } + +    /** +     * Start the ConnectedThread to begin managing a Bluetooth connection +     * @param socket  The BluetoothSocket on which the connection was made +     * @param device  The BluetoothDevice that has been connected +     */ +    public synchronized void connected(BluetoothSocket socket, BluetoothDevice +            device, final String socketType) { +        if (D) Log.d(TAG, "connected, Socket Type:" + socketType); + +        // Cancel the thread that completed the connection +        if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;} + +        // Cancel any thread currently running a connection +        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;} + +        // Start the thread to manage the connection and perform transmissions +        mConnectedThread = new ConnectedThread(socket, socketType); +        mConnectedThread.start(); + +        // Send the name of the connected device back to the UI Activity +        Message msg = mHandler.obtainMessage(AltosDroid.MESSAGE_DEVICE_NAME); +        Bundle bundle = new Bundle(); +        bundle.putString(AltosDroid.DEVICE_NAME, device.getName()); +        msg.setData(bundle); +        mHandler.sendMessage(msg); + +        setState(STATE_CONNECTED); +    } + +    /** +     * Stop all threads +     */ +    public synchronized void stop() { +        if (D) Log.d(TAG, "stop"); + +        if (mConnectThread != null) { +            mConnectThread.cancel(); +            mConnectThread = null; +        } + +        if (mConnectedThread != null) { +            mConnectedThread.cancel(); +            mConnectedThread = null; +        } + +        setState(STATE_NONE); +    } + +    /** +     * Write to the ConnectedThread in an unsynchronized manner +     * @param out The bytes to write +     * @see ConnectedThread#write(byte[]) +     */ +    public void write(byte[] out) { +        // Create temporary object +        ConnectedThread r; +        // Synchronize a copy of the ConnectedThread +        synchronized (this) { +            if (mState != STATE_CONNECTED) return; +            r = mConnectedThread; +        } +        // Perform the write unsynchronized +        r.write(out); +    } + +    /** +     * Indicate that the connection attempt failed and notify the UI Activity. +     */ +    private void connectionFailed() { +        // Send a failure message back to the Activity +        Message msg = mHandler.obtainMessage(AltosDroid.MESSAGE_TOAST); +        Bundle bundle = new Bundle(); +        bundle.putString(AltosDroid.TOAST, "Unable to connect device"); +        msg.setData(bundle); +        mHandler.sendMessage(msg); + +        // Start the service over to restart listening mode +        BluetoothChatService.this.start(); +    } + +    /** +     * Indicate that the connection was lost and notify the UI Activity. +     */ +    private void connectionLost() { +        // Send a failure message back to the Activity +        Message msg = mHandler.obtainMessage(AltosDroid.MESSAGE_TOAST); +        Bundle bundle = new Bundle(); +        bundle.putString(AltosDroid.TOAST, "Device connection was lost"); +        msg.setData(bundle); +        mHandler.sendMessage(msg); + +        // Start the service over to restart listening mode +        BluetoothChatService.this.start(); +    } + + +    /** +     * This thread runs while attempting to make an outgoing connection +     * with a device. It runs straight through; the connection either +     * succeeds or fails. +     */ +    private class ConnectThread extends Thread { +        private final BluetoothSocket mmSocket; +        private final BluetoothDevice mmDevice; +        private String mSocketType; + +        public ConnectThread(BluetoothDevice device, boolean secure) { +            mmDevice = device; +            BluetoothSocket tmp = null; +            mSocketType = secure ? "Secure" : "Insecure"; + +            // Get a BluetoothSocket for a connection with the +            // given BluetoothDevice +            try { +                if (secure) { +                	Method m = device.getClass().getMethod("createRfcommSocket", new Class[] {int.class}); +                	tmp = (BluetoothSocket) m.invoke(device, 2); +//                    tmp = device.createRfcommSocket(1); +                } else { +                	Method m = device.getClass().getMethod("createInsecureRfcommSocket", new Class[] {int.class}); +                	tmp = (BluetoothSocket) m.invoke(device, 2); +//                    tmp = device.createInsecureRfcommSocket(1); +                } +            } catch (Exception e) { +                Log.e(TAG, "Socket Type: " + mSocketType + "create() failed", e); +				e.printStackTrace(); +			} +            mmSocket = tmp; +        } + +        public void run() { +            Log.i(TAG, "BEGIN mConnectThread SocketType:" + mSocketType); +            setName("ConnectThread" + mSocketType); + +            // Always cancel discovery because it will slow down a connection +            mAdapter.cancelDiscovery(); + +            // Make a connection to the BluetoothSocket +            try { +                // This is a blocking call and will only return on a +                // successful connection or an exception +                mmSocket.connect(); +            } catch (IOException e) { +                // Close the socket +                try { +                    mmSocket.close(); +                } catch (IOException e2) { +                    Log.e(TAG, "unable to close() " + mSocketType + +                            " socket during connection failure", e2); +                } +                connectionFailed(); +                return; +            } + +            // Reset the ConnectThread because we're done +            synchronized (BluetoothChatService.this) { +                mConnectThread = null; +            } + +            // Start the connected thread +            connected(mmSocket, mmDevice, mSocketType); +        } + +        public void cancel() { +            try { +                mmSocket.close(); +            } catch (IOException e) { +                Log.e(TAG, "close() of connect " + mSocketType + " socket failed", e); +            } +        } +    } + +    /** +     * This thread runs during a connection with a remote device. +     * It handles all incoming and outgoing transmissions. +     */ +    private class ConnectedThread extends Thread { +        private final BluetoothSocket mmSocket; +        private final InputStream mmInStream; +        private final OutputStream mmOutStream; + +        public ConnectedThread(BluetoothSocket socket, String socketType) { +            Log.d(TAG, "create ConnectedThread: " + socketType); +            mmSocket = socket; +            InputStream tmpIn = null; +            OutputStream tmpOut = null; + +            // Get the BluetoothSocket input and output streams +            try { +                tmpIn = socket.getInputStream(); +                tmpOut = socket.getOutputStream(); +            } catch (IOException e) { +                Log.e(TAG, "temp sockets not created", e); +            } + +            mmInStream = tmpIn; +            mmOutStream = tmpOut; +        } + +        public void run() { +            Log.i(TAG, "BEGIN mConnectedThread"); +            byte[] buffer = new byte[1024]; +            int bytes; + +            // Keep listening to the InputStream while connected +            while (true) { +                try { +                    // Read from the InputStream +                    bytes = mmInStream.read(buffer); + +                    // Send the obtained bytes to the UI Activity +                    mHandler.obtainMessage(AltosDroid.MESSAGE_READ, bytes, -1, buffer) +                            .sendToTarget(); +                } catch (IOException e) { +                    Log.e(TAG, "disconnected", e); +                    connectionLost(); +                    break; +                } +            } +        } + +        /** +         * Write to the connected OutStream. +         * @param buffer  The bytes to write +         */ +        public void write(byte[] buffer) { +            try { +                mmOutStream.write(buffer); + +                // Share the sent message back to the UI Activity +                mHandler.obtainMessage(AltosDroid.MESSAGE_WRITE, -1, -1, buffer) +                        .sendToTarget(); +            } catch (IOException e) { +                Log.e(TAG, "Exception during write", e); +            } +        } + +        public void cancel() { +            try { +                mmSocket.close(); +            } catch (IOException e) { +                Log.e(TAG, "close() of connect socket failed", e); +            } +        } +    } +} diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/DeviceListActivity.java b/altosdroid/src/org/altusmetrum/AltosDroid/DeviceListActivity.java new file mode 100644 index 00000000..af5d7f15 --- /dev/null +++ b/altosdroid/src/org/altusmetrum/AltosDroid/DeviceListActivity.java @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2009 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 org.altusmetrum.AltosDroid; + +import java.util.Set; +import org.altusmetrum.AltosDroid.R; + +import android.app.Activity; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.view.Window; +import android.view.View.OnClickListener; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.AdapterView.OnItemClickListener; + +/** + * This Activity appears as a dialog. It lists any paired devices and + * devices detected in the area after discovery. When a device is chosen + * by the user, the MAC address of the device is sent back to the parent + * Activity in the result Intent. + */ +public class DeviceListActivity extends Activity { +    // Debugging +    private static final String TAG = "DeviceListActivity"; +    private static final boolean D = true; + +    // Return Intent extra +    public static String EXTRA_DEVICE_ADDRESS = "device_address"; + +    // Member fields +    private BluetoothAdapter mBtAdapter; +    private ArrayAdapter<String> mPairedDevicesArrayAdapter; +    private ArrayAdapter<String> mNewDevicesArrayAdapter; + +    @Override +    protected void onCreate(Bundle savedInstanceState) { +        super.onCreate(savedInstanceState); + +        // Setup the window +        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); +        setContentView(R.layout.device_list); + +        // Set result CANCELED incase the user backs out +        setResult(Activity.RESULT_CANCELED); + +        // Initialize the button to perform device discovery +        Button scanButton = (Button) findViewById(R.id.button_scan); +        scanButton.setOnClickListener(new OnClickListener() { +            public void onClick(View v) { +                doDiscovery(); +                v.setVisibility(View.GONE); +            } +        }); + +        // Initialize array adapters. One for already paired devices and +        // one for newly discovered devices +        mPairedDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name); +        mNewDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name); + +        // Find and set up the ListView for paired devices +        ListView pairedListView = (ListView) findViewById(R.id.paired_devices); +        pairedListView.setAdapter(mPairedDevicesArrayAdapter); +        pairedListView.setOnItemClickListener(mDeviceClickListener); + +        // Find and set up the ListView for newly discovered devices +        ListView newDevicesListView = (ListView) findViewById(R.id.new_devices); +        newDevicesListView.setAdapter(mNewDevicesArrayAdapter); +        newDevicesListView.setOnItemClickListener(mDeviceClickListener); + +        // Register for broadcasts when a device is discovered +        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); +        this.registerReceiver(mReceiver, filter); + +        // Register for broadcasts when discovery has finished +        filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); +        this.registerReceiver(mReceiver, filter); + +        // Get the local Bluetooth adapter +        mBtAdapter = BluetoothAdapter.getDefaultAdapter(); + +        // Get a set of currently paired devices +        Set<BluetoothDevice> pairedDevices = mBtAdapter.getBondedDevices(); + +        // If there are paired devices, add each one to the ArrayAdapter +        if (pairedDevices.size() > 0) { +            findViewById(R.id.title_paired_devices).setVisibility(View.VISIBLE); +            for (BluetoothDevice device : pairedDevices) { +                mPairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress()); +            } +        } else { +            String noDevices = getResources().getText(R.string.none_paired).toString(); +            mPairedDevicesArrayAdapter.add(noDevices); +        } +    } + +    @Override +    protected void onDestroy() { +        super.onDestroy(); + +        // Make sure we're not doing discovery anymore +        if (mBtAdapter != null) { +            mBtAdapter.cancelDiscovery(); +        } + +        // Unregister broadcast listeners +        this.unregisterReceiver(mReceiver); +    } + +    /** +     * Start device discover with the BluetoothAdapter +     */ +    private void doDiscovery() { +        if (D) Log.d(TAG, "doDiscovery()"); + +        // Indicate scanning in the title +        setProgressBarIndeterminateVisibility(true); +        setTitle(R.string.scanning); + +        // Turn on sub-title for new devices +        findViewById(R.id.title_new_devices).setVisibility(View.VISIBLE); + +        // If we're already discovering, stop it +        if (mBtAdapter.isDiscovering()) { +            mBtAdapter.cancelDiscovery(); +        } + +        // Request discover from BluetoothAdapter +        mBtAdapter.startDiscovery(); +    } + +    // The on-click listener for all devices in the ListViews +    private OnItemClickListener mDeviceClickListener = new OnItemClickListener() { +        public void onItemClick(AdapterView<?> av, View v, int arg2, long arg3) { +            // Cancel discovery because it's costly and we're about to connect +            mBtAdapter.cancelDiscovery(); + +            // Get the device MAC address, which is the last 17 chars in the View +            String info = ((TextView) v).getText().toString(); +            String address = info.substring(info.length() - 17); + +            // Create the result Intent and include the MAC address +            Intent intent = new Intent(); +            intent.putExtra(EXTRA_DEVICE_ADDRESS, address); + +            // Set result and finish this Activity +            setResult(Activity.RESULT_OK, intent); +            finish(); +        } +    }; + +    // The BroadcastReceiver that listens for discovered devices and +    // changes the title when discovery is finished +    private final BroadcastReceiver mReceiver = new BroadcastReceiver() { +        @Override +        public void onReceive(Context context, Intent intent) { +            String action = intent.getAction(); + +            // When discovery finds a device +            if (BluetoothDevice.ACTION_FOUND.equals(action)) { +                // Get the BluetoothDevice object from the Intent +                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); +                // If it's already paired, skip it, because it's been listed already +                if (device.getBondState() != BluetoothDevice.BOND_BONDED) { +                    mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress()); +                } +            // When discovery is finished, change the Activity title +            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) { +                setProgressBarIndeterminateVisibility(false); +                setTitle(R.string.select_device); +                if (mNewDevicesArrayAdapter.getCount() == 0) { +                    String noDevices = getResources().getText(R.string.none_found).toString(); +                    mNewDevicesArrayAdapter.add(noDevices); +                } +            } +        } +    }; + +} | 
