I develop an bluetooth app which will connect to a paired device and send a message, but I have to test connection before. I've tried many options, but nothing works in good way. So could you send me any example of code which can do it? I made an thread, but I can't get an good state of connection to build an "if" function. Here is the code:
package com.example.szukacz;
import java.lang.reflect.Method;
import java.util.Set;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity {
LinearLayout sparowaneUrzadzenia;
public void lokalizowanie() {
Intent intencja = new Intent(this, Lokalizator.class);
startActivity(intencja);
}
public void parowanie(View v) {
Intent intencja = new Intent(this, Parowanie.class);
startActivity(intencja);
}
boolean isRunning;
Handler handler = new Handler() {
#Override
public void handleMessage(Message msg) {
String status = (String)msg.obj;
if(status == "polaczony") {
alarm();
showToast("prawda, zwraca" + status);
} else {
showToast("wykonanie x, zwraca: " + status);
};
}
};
public void alarm() {
showToast("Alarm!!!");
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sparowaneUrzadzenia = (LinearLayout) findViewById(R.id.listaUrzadzenGlowna);
pokazSparowane();
}
public void onStop() {
super.onStop();
isRunning = false;
}
public void onStart() {
super.onStart();
Thread testPolaczen = new Thread(new Runnable() {
public void run() {
try {
for(int i = 0; i < 1000000; i++) {
Thread.sleep(5000);
testujPolaczenia();
int stan = 0;
String status = Integer.toString(stan);
Message msg = handler.obtainMessage(1, (String)status);
if(isRunning == true) {
handler.sendMessage(msg);
}
}
} catch (Throwable t) {
// watek stop
}
}
});
isRunning = true;
testPolaczen.start();
}
private void testujPolaczenia() {
}
public void pokazSparowane(){
/*
* Wyświetlanie listy sparowanych urządzeń .
* */
Log.d("INFO","Sparowane dla tego urzÄ…dzenia");
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
if (pairedDevices.size() > 0) {
for (BluetoothDevice device : pairedDevices) {
Log.d("INFO",device.getName()+" - "+device.getAddress());
// dodawanie urzadzen do listy
Button urzadzenie = new Button(getApplicationContext());
urzadzenie.setText(device.getName());
// urzadzenie.setTextColor(0xffffff); //jak ustawic na czarny kolor napsisów ?
urzadzenie.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v)
{
showToast("klik");
lokalizowanie();
}
});
sparowaneUrzadzenia.addView(urzadzenie);
}
} else {
showToast("brak sparowanych urzadzen");
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
private void showToast(String message) {
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
thanks!
I faced the same problem as I am working on an app which may use TTS while it is running. I think there is no way to check if there is any bluetooth device connected by the BluetoothAdapter class immediately except creating a broadcast receiver and monitor the changes of status of bluetooth.
After scratching my head for a few hours, I found a quite subtle way to solve this problem. I tried, it works pretty well for me.
AudioManager audioManager = (AudioManager) getApplicationContext.getSystemService(Context.AUDIO_SERVICE);
if (audioManager.isBluetoothA2dpOn()) {
//audio is currently being routed to bluetooth -> bluetooth is connected
}
Source: http://developer.android.com/training/managing-audio/audio-output.html
I think it's to late for answer to your question but I think can helps somebody :
If you use Thread you have to create a BroadcastReceiver in your main activity on create :
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_digital_metrix_connexion);
BroadcastReceiver bState = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED))
{
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
BluetoothAdapter.ERROR);
switch (state)
{
case BluetoothAdapter.ACTION_ACL_CONNECTED:
{
//Do something you need here
System.out.println("Connected");
break;
}
default:
System.out.println("Default");
break;
}
}
}
};
}
BluetoothAdapter.STATE_CONNECTED is one state over many, for exemple it's possible to check if device connecting or disconnecting thanks to BluetoothAdapter.ACTION_ACL_DISCONNECTED or BluetoothAdapter.ACTION_ACL_DISCONNECTED_REQUEST .
After, you have to create a filter in your thread class or in you main activity if you don't use thread :
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
This filter check the bluetoothadapter state.
And now you have to register your filter so if you use thread pass context in parameter of your thread like this context.registerReceiver(bState,filter);
or in your main Activity : registerReceiver(bState,filter);
If you have any question don't hesitate to ask me.
Hope I helps you somebody.
Related
today I updated a google play services lib into an app for a newer version. Eclipse is not giving me any kind of errors, I compiled it and when I test on my phone works fine then I go to the menu and select exit and confirm with a yes to close it and the app disappears from screen.
But when you hit the square at the bottom of the phone (when you list all opened apps) the app is still there running! o_O
Prior to the update the app was closing fine and now remains active after exit. But I dont think that gps could cause this kind of issue. Any suggestion???
Here is my MainActivity.java (where the exit menu process is executed)
package com.myapp.radio;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.IBinder;
import android.view.Menu;
import android.view.MenuItem;
import com.myapp.radio.R;
public class BaseActivity extends Activity {
private Intent bindIntent;
private RadioService radioService;
private static boolean isExitMenuClicked;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
isExitMenuClicked = false;
// Bind to the service
bindIntent = new Intent(this, RadioService.class);
bindService(bindIntent, radioConnection, Context.BIND_AUTO_CREATE);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
}
#Override
protected void onResume() {
super.onResume();
if (isExitMenuClicked == true)
finish();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main_menu, menu);
if (radioService.getTotalStationNumber() <= 1) {
}
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
Intent i;
final String thisClassName = this.getClass().getName();
final String thisPackageName = this.getPackageName();
if (item.getItemId() == R.id.radio) {
if (!thisClassName.equals(thisPackageName + ".MainActivity")) {
i = new Intent(this, MainActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(i);
return true;
}
} else if (item.getItemId() == R.id.exit) {
String title = "Cerrar aplicación";
String message = "¿Realmente desea salir?";
String buttonYesString = "Si";
String buttonNoString = "No";
isExitMenuClicked = true;
AlertDialog.Builder ad = new AlertDialog.Builder(this);
ad.setTitle(title);
ad.setMessage(message);
ad.setCancelable(true);
ad.setPositiveButton(buttonYesString,
new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
if (radioService != null) {
radioService.exitNotification();
radioService.stop();
radioService.stopService(bindIntent);
isExitMenuClicked = true;
finish();
}
}
});
ad.setNegativeButton(buttonNoString, null);
ad.show();
return true;
} else if (item.getItemId() == R.id.about) {
if (!thisClassName.equals(thisPackageName + ".AboutActivity")) {
i = new Intent(this, AboutActivity.class);
startActivity(i);
return true;
}
} else if (item.getItemId() == R.id.facebook) {
if (!thisClassName.equals(thisPackageName + ".FacebookActivity")) {
i = new Intent(this, FacebookActivity.class);
startActivity(i);
return true;
}
} else if (item.getItemId() == R.id.twitter) {
if (!thisClassName.equals(thisPackageName + ".TwitterActivity")) {
i = new Intent(this, TwitterActivity.class);
startActivity(i);
return true;
}
}
return super.onOptionsItemSelected(item);
}
// Handles the connection between the service and activity
private final ServiceConnection radioConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className, IBinder service) {
radioService = ((RadioService.RadioBinder) service).getService();
}
#Override
public void onServiceDisconnected(ComponentName className) {
radioService = null;
}
};
protected void onCreate1(Bundle savedInstanceState) {
// TODO Auto-generated method stub
}
}
This behavior is not related to Google Play Services, this is how Android works.
When you leave any application it's still alive for some time. There are no any running Activities, but Application instance is active. And if you launch it again after short time - it's started pretty quick, because some application data inherited from previous run. For example, all the static variables - they are still alive and contains previous values. It can cause very complex bugs at the restarted application, so be aware of that!
That extra living time depends on current conditions. If there are enough memory - it's longer, not enough - killed sooner.
I'm doing an app which uses bluetooth communication.
At first, I'm trying to get a list of all bluetooth devices detectable by the device.
I've managed to load in a listview all the paired devices, but i'm unable to list un-paired devices, I've found some tutorials but I'm a bit confused as I'm a bit new to android development & bluetooth communication.
So i need to find every bluetooth device in range, and I don't know how to do it (lazy)
Here is the (edited) code:
package com.voice.benz.instaurentremote;
import android.bluetooth.*;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ListView;
import android.widget.TextView;
import android.bluetooth.BluetoothAdapter;
import android.content.Intent;
import android.widget.ArrayAdapter;
import java.util.Set;
import android.content.Context;
import android.content.BroadcastReceiver;
import android.view.Window;
import android.app.Activity;
import android.content.IntentFilter;
import android.util.Log;
import android.widget.Button;
import android.view.View.OnClickListener;
import android.widget.AdapterView.OnItemClickListener;
import java.util.ArrayList;
public class bluetooth extends ActionBarActivity {
private TextView bluetoothPaired;
private TextView txt_status;
private BluetoothAdapter btAdapter;
private ListView newdevices_listview;
private Set<BluetoothDevice>pairedDevices;
private ArrayAdapter<String> adapter = null;
private static final int BLUETOOTH_ON = 1000;
private static final int REQUEST_ENABLE_BT = 1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bluetooth);
// Initialize the button to perform device discovery
txt_status = (TextView) findViewById(R.id.txt_status);
newdevices_listview = (ListView)findViewById(R.id.newdevices_listview);
adapter=new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1);
newdevices_listview.setAdapter(adapter);
// Initialize adapter
btAdapter = BluetoothAdapter.getDefaultAdapter();
// Register for broadcasts when a device is discovered
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
this.registerReceiver(mReceiver, filter);
filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
this.registerReceiver(mReceiver, filter);
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) {
//discovery starts, we can show progress dialog or perform other tasks
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
//discovery finishes, dismis progress dialog
} else if (BluetoothDevice.ACTION_FOUND.equals(action)) {
//bluetooth device found
BluetoothDevice device = (BluetoothDevice) intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
txt_status.setText("Found device " + device.getName());
adapter.add(device.getName() + "\n" + device.getAddress());
}
}
};
public void attivaBluetooth (View view) {
if (!btAdapter.isEnabled()) {
Intent turnOn = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(turnOn, 1);
}
}
public void cercaDispositivi (View view)
{
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_FOUND);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(mReceiver, filter);
btAdapter.startDiscovery();
}
#Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
unregisterReceiver(mReceiver);
}
public void disattivaBluetooth (View view)
{
if (btAdapter.isEnabled())
{
btAdapter.disable();
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_bluetooth, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
You don't seem to be registering your BroadcastReceiver myBluetoothReceiver anywhere. If you do that in onResume() or onCreate you'll probably have more luck. :)
I am trying to make a loop in Android app which is triggered by a button click.
After reading tips on making loops/delays on SO ( for example here), I decided to use message handler approach instead of Runnable.
In the code below, toastLoop() is executed and it prints "starting in x" seconds.
However, the message does not seem to be posted with that delay.
Or, the message is posted but the handler does not receive it.
I am a newbie and I am probably making a silly mistake somewhere.
What am I missing in the code below? Or is this code totally stupid?
package com.example.testapp;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.NavUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
public class ExecActivity extends FragmentActivity {
static Context context = null;
String LOG_TAG = "FTR";
static boolean test_status = false;
ToastLoop toast_loop;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_exec);
// Show the Up button in the action bar.
setupActionBar();
}
/**
* Set up the {#link android.app.ActionBar}, if the API is available.
*/
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void setupActionBar() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
getActionBar().setDisplayHomeAsUpEnabled(true);
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.exec, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
// This ID represents the Home or Up button. In the case of this
// activity, the Up button is shown. Use NavUtils to allow users
// to navigate up one level in the application structure. For
// more details, see the Navigation pattern on Android Design:
//
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
//
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
class ToastLoop {
private final int loop_max_duration = 60; // in seconds
final int TOAST = 1;
Handler handler = new Handler() {
#Override
public void handleMessage(Message msg) {
Log.i(LOG_TAG, "Handler(): msg: " + msg.what);
switch (msg.what) {
case TOAST:
Toast.makeText( ExecActivity.this, "Doing my thing", Toast.LENGTH_SHORT).show();
if ( test_status) { // test is still running
toastLoop();
}
break;
default:
Toast.makeText(ExecActivity.this, "Unhandled", Toast.LENGTH_SHORT).show();
break;
}
}
};
public boolean toastLoop() {
if ( test_status) { // test is still running
long curr_time_milli = System.currentTimeMillis();
long window_position_sec = (long)( ((long)(curr_time_milli/1000))/loop_max_duration); // fraction discarded
long loop_start_time_sec = (window_position_sec + 1 ) * loop_max_duration;
long actual_start_time_milli = loop_start_time_sec * 1000;
Log.i(LOG_TAG, "toastLoop(): starting in " + ((actual_start_time_milli - curr_time_milli)/1000) );
Message msg = handler.obtainMessage( TOAST);
handler.sendMessageAtTime( msg, actual_start_time_milli );
return true;
}
return false;
}
}
public boolean beginTest( View view) {
Log.i(LOG_TAG, "in beginTest()");
test_status = true;
toast_loop = new ToastLoop();
toast_loop.toastLoop();
return true;
}
public boolean endTest( View view) {
Log.i(LOG_TAG, "in endTest()");
test_status = false;
return true;
}
}
Hie all,
i am working on Bluetooth connections and to do that i have device address and i want send it to a service which handle Bluetooth connections
i want to send string(device address) from activity to service (Android)
Code In ACTIVITY CLASS:
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_CONNECT_DEVICE:
// When DeviceListActivity returns with a device to connect
if (resultCode == Activity.RESULT_OK) {
// Get the device MAC address
address = data.getExtras()
.getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
// Get the BLuetoothDevice object
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
startService(new Intent(CallBluetooth .this,BluetoothService.class));
Intent myIntent = new Intent(CallBluetooth.this, BluetoothService.class);
Bundle bundle = new Bundle();
CharSequence data1 = "abc";
bundle.putCharSequence("extraData",data1);
myIntent.putExtras(bundle);
PendingIntent pendingIntent = PendingIntent.getService(CallBluetooth.this, 0, myIntent, 0);
/*BluetoothService s = new BluetoothService();
s.deviceAddress(address);*/
// Attempt to connect to the device
// mChatService.connect(device);
}
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
openOptionsMenu();
} else {
// User did not enable Bluetooth or an error occured
Toast.makeText(this, R.string.bt_not_enabled_leaving, Toast.LENGTH_SHORT).show();
finish();
}
}
Code In SERVICE CLASS:
public void onStart(Intent intent, int startId) {
// TODO Auto-generated method stub
super.onStart(intent, startId);
Bundle bundle = intent.getExtras();
System.out.println("*******************"+"InsideOnStart");
if(bundle != null)
{
CharSequence data = (String) bundle.getCharSequence("extraData");
System.out.println("*******************"+data);
}
}
According to this article i've implemented Activity that sends String to Service. You can look below, maybe it will help.
MainActivity.java
package com.example.androidtranslator;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.view.Menu;
import android.view.View;
public class MainActivity extends Activity {
private Messenger mService = null;
private boolean mBound;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// some gui code etc...
/**
* Connect to an application service, creating it if needed
*/
bindService(new Intent(this, TranslatorService.class), mConnection,
Context.BIND_AUTO_CREATE);
}
#Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
/**
* on some GUI action (click button) send message
*
* #param view
*/
public void translate(View view) {
if (!mBound)
return;
Message msg = Message.obtain(null, TranslatorService.MSG_STRING,
"Some message (it can be from textView for example)");
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
private ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceDisconnected(ComponentName name) {
mService = null;
mBound = false;
}
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = new Messenger(service);
mBound = true;
}
};
}
TranslatorService.java
package com.example.androidtranslator;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.widget.Toast;
public class TranslatorService extends Service {
public static final int MSG_STRING = 0;
/**
* Handler of incoming messages from clients.
* Show Toast with received string
*/
class IncomingHandler extends Handler {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_STRING:
Toast.makeText(getApplicationContext(), msg.obj.toString(), Toast.LENGTH_SHORT).show();
break;
default:
super.handleMessage(msg);
}
}
}
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
final Messenger mMessenger = new Messenger(new IncomingHandler());
/**
* When binding to the service, we return an interface to our messenger
* for sending messages to the service.
*/
#Override
public IBinder onBind(Intent intent) {
Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
return mMessenger.getBinder();
}
}
Have you looked at the Service documentation and examples from and Android docs?
In a nutshell, you can use a Messenger to send meaningful messages to the service. Look at the section on: Remote Messenger Service Sample
you cant send data directly from activity to service,
you need to used Android Interface Definition Language (AIDL)
Using aidl you can call any method in service that define in .aidl file from activity, if you want to pass data than you can pass data as arguments of methods
for additional info see Implementing Remote Interface Using AIDL
I have a ListView which is updated by some service sending intent. If update event arrives while I'm pressing some item in the ListView I get some weird behavior. The default orange rectangle in the pressed item disappears and some other item(s)'s text becomes darker (as if its item is being pressed).
How do I postpone ListView update after it becomes "not pressed"? Or more specifically which events should I listen to in order to determine that ListView is no longer pressed? (I can create some thread executed periodically to update when it's appropriate but I think it's overkill). Or maybe there are better solution or workaround.
Here is sample code illustrating the problem. Service sends update intents every 2 seconds. If I try to long press some item in the list I get the weird behavior I described above.
The activity:
package org.me.listviewupdate;
import android.app.ListActivity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.ArrayAdapter;
public class MyActivity extends ListActivity {
private class MyHandler extends Handler {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_UPDATE_DATA:
mAdapter.notifyDataSetChanged();
break;
default:
super.handleMessage(msg);
break;
}
}
}
private class MyBroadcastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
mHandler.sendEmptyMessage(MSG_UPDATE_DATA);
}
}
private static final int MSG_UPDATE_DATA = 0;
private String[] mItems = new String[] { "Item1", "Item2", "Item3", "Item4" };
private ArrayAdapter<String> mAdapter;
private Handler mHandler;
private BroadcastReceiver mBroadcastReceiver;
#Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
mHandler = new MyHandler();
mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,
android.R.id.text1, mItems);
setListAdapter(mAdapter);
mBroadcastReceiver = new MyBroadcastReceiver();
registerReceiver(mBroadcastReceiver, new IntentFilter(MyService.UPDATE_EVENT));
startService(new Intent(this, MyService.class));
}
#Override
protected void onDestroy() {
unregisterReceiver(mBroadcastReceiver);
stopService(new Intent(this, MyService.class));
super.onDestroy();
}
}
The service:
package org.me.listviewupdate;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class MyService extends Service {
private class MyUpdateTask implements Runnable {
public void run() {
sendBroadcast(new Intent(UPDATE_EVENT));
}
}
public static final String UPDATE_EVENT =
"org.me.listviewupdate.intent.event.UPDATED";
private static final int UPDATE_INTERVAL = 2;
private ScheduledExecutorService mUpdater;
#Override
public IBinder onBind(Intent arg0) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
mUpdater = Executors.newSingleThreadScheduledExecutor();
}
#Override
public void onDestroy() {
mUpdater.shutdownNow();
super.onDestroy();
}
#Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
mUpdater.scheduleAtFixedRate(new MyUpdateTask(), UPDATE_INTERVAL,
UPDATE_INTERVAL, TimeUnit.SECONDS);
}
}
Thank you.
My solution. Well, just in case it may help somebody here is my solution. The code added to MyActivity.onCreate():
getListView().setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
boolean isReleased = event.getAction() == MotionEvent.ACTION_UP
|| event.getAction() == MotionEvent.ACTION_CANCEL;
if (mHasPendingUpdate && isReleased) {
mHandler.sendEmptyMessage(MSG_UPDATE_DATA);
}
return false;
}
});
getListView().setOnKeyListener(new OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
boolean isKeyOfInterest = keyCode == KeyEvent.KEYCODE_DPAD_CENTER
|| keyCode == KeyEvent.KEYCODE_ENTER;
boolean isReleased = event.getAction() == KeyEvent.ACTION_UP;
if (mHasPendingUpdate && isKeyOfInterest && isReleased) {
mHandler.sendEmptyMessage(MSG_UPDATE_DATA);
}
return false;
}
});
Also I added a variable mHasPendingUpdate and modified MyHandler:
private class MyHandler extends Handler {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_UPDATE_DATA:
if (getListView().isPressed()) {
mHasPendingUpdate = true;
} else {
mAdapter.notifyDataSetChanged();
mHasPendingUpdate = false;
}
break;
default:
super.handleMessage(msg);
break;
}
}
}
Catch the longpress and prevent the dataset from being updated. Override the listview's onlongpress method. I'll post code in a minute
Update:
Actually. That might not work since it could update while you're pressing but before the longpress method is called. If what I suggested above doesn't work, I would implement onClickListener on your ListActivity, listen for any motion event, set the global variable preventing the update, and return false in the onMotionEvent() method so that the list item can consume the click.