//
// anyRemote android client
// a bluetooth/wi-fi remote control for Linux.
//
// Copyright (C) 2011-2016 Mikhail Fedotov <anyremote@mail.ru>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
//
package anyremote.client.android;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnDismissListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.app.Activity;
import android.app.Dialog;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.TextView;
import android.widget.ListView;
import android.widget.Toast;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
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 anyremote.client.android.R;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import anyremote.client.android.util.About;
import anyremote.client.android.util.Address;
import anyremote.client.android.util.AddressAdapter;
import anyremote.client.android.util.BTScanner;
import anyremote.client.android.util.IPScanner;
import anyremote.client.android.util.ZCScanner;
import anyremote.client.android.util.IScanner;
import anyremote.client.android.util.ScanMessage;
public class SearchForm extends arActivity
implements OnItemClickListener,
//DialogInterface.OnDismissListener,
//DialogInterface.OnCancelListener,
AdapterView.OnItemSelectedListener {
ListView searchList;
AddressAdapter dataSource;
int selected = 0;
Handler handler = null;
IScanner scanner = null;
// BT stuff
private BluetoothAdapter mBtAdapter;
String connectTo = "";
String connectName = "";
String connectPass = "";
boolean connectAuto = false;
boolean skipDismissDialog = false;
boolean deregStateRcv = false;
String id;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
prefix = "SearchForm"; // log stuff
Intent intent = getIntent();
id = intent.getStringExtra("SUBID");
log("onCreate " + id);
//requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
requestWindowFeature(Window.FEATURE_ACTION_BAR);
setContentView(R.layout.search_dialog);
setResult(Activity.RESULT_CANCELED);
setTitle(R.string.searchFormUnconnected);
searchList = (ListView) findViewById(R.id.search_list);
searchList.setOnItemClickListener(this);
registerForContextMenu(searchList);
dataSource = new AddressAdapter(this, R.layout.search_list_item, anyRemote.protocol.loadPrefs());
searchList.setAdapter(dataSource);
searchList.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
searchList.setOnItemSelectedListener(this);
//searchList.setItemChecked(selected, true);
// Get the local Bluetooth adapter
mBtAdapter = BluetoothAdapter.getDefaultAdapter();
if (dataSource.size() == 0) { // first-time run
Toast.makeText(this, "Press Menu ...", Toast.LENGTH_SHORT).show();
} else {
if (anyRemote.firstConnect) {
anyRemote.firstConnect = false;
final Address auto = dataSource.getAutoconnectItem();
if (auto != null) {
log("onCreate: autoconnect to "+auto.name);
doConnect(auto);
}
} else {
if (anyRemote.protocol.currentConnName.length() > 0) {
final Address conn = dataSource.getItem(anyRemote.protocol.currentConnName);
if (conn != null) {
log("onCreate: resume connect to "+conn.name);
doConnect(conn);
}
}
}
}
handler = new Handler() {
@Override
public void handleMessage(Message inputMessage) {
switch (inputMessage.what) {
case IScanner.SCAN_STARTED: // Indicate scanning in the title
setProgressBarIndeterminateVisibility(true);
setTitle(R.string.searching);
break;
case IScanner.SCAN_FAILED:
setProgressBarIndeterminateVisibility(false);
setTitle(R.string.searchFormUnconnected);
break;
case IScanner.SCAN_FINISHED:
setProgressBarIndeterminateVisibility(false);
setTitle(R.string.searchFormUnconnected);
break;
case IScanner.SCAN_PROGRESS:
ScanMessage pmsg = (ScanMessage) inputMessage.obj;
setProgressBarIndeterminateVisibility(true);
setTitle(pmsg.name);
break;
case IScanner.SCAN_FOUND:
ScanMessage fmsg = (ScanMessage) inputMessage.obj;
dataSource.addIfNew(fmsg.name, fmsg.address, "",false);
break;
default:
super.handleMessage(inputMessage);
}
}
};
}
@Override
protected void onPause() {
log("onPause "+id);
super.onPause();
}
@Override
protected void onResume() {
log("onResume "+id);
super.onResume();
}
@Override
protected void onStop() {
log("onStop "+id);
super.onStop();
}
@Override
protected void onDestroy() {
log("onDestroy "+id);
// Make sure we're not doing discovery anymore
stopSearch();
super.onDestroy();
}
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
Address a = dataSource.getItem(arg2);
if (a != null) {
doConnect(a);
}
}
// Handle long-click
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
if (v.getId() == R.id.search_list) {
final AdapterContextMenuInfo info = (AdapterContextMenuInfo)menuInfo;
final TextView tv= (TextView) info.targetView.findViewById(R.id.peer_list_item);
menu.setHeaderTitle(tv.getText());
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.search_menu2, menu);
}
}
// Handle context menu, opened by long-click
@Override
public boolean onContextItemSelected(MenuItem item) {
stopSearch();
final AdapterContextMenuInfo info = (AdapterContextMenuInfo) item
.getMenuInfo();
Address a = dataSource.getItem(info.position);
switch (item.getItemId()) {
case R.id.connect_to:
doConnect(a);
break;
case R.id.enter_item_addr:
changeAddress(a);
break;
case R.id.clean_item:
final String address = a.name;
cleanAddress(address);
dataSource.remove(address);
break;
}
return super.onContextItemSelected(item);
}
@Override
public void onBackPressed() {
doExit();
}
// The BroadcastReceiver that handles BT state
private final BroadcastReceiver mBTStateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
log("BroadcastReceiver::onReceive state");
String stateExtra = BluetoothAdapter.EXTRA_STATE;
int state = intent.getIntExtra(stateExtra, -1);
switch (state) {
case (BluetoothAdapter.STATE_TURNING_ON) : {
setTitle(R.string.bt_turning_on);
break;
}
case (BluetoothAdapter.STATE_ON) : {
log("BroadcastReceiver::onReceive state ON");
setTitle(R.string.bt_on);
unregisterReceiver(this);
deregStateRcv = false;
doRealConnect();
break;
}
case (BluetoothAdapter.STATE_TURNING_OFF) : {
setTitle(R.string.bt_turning_off);
break;
}
case (BluetoothAdapter.STATE_OFF) : {
log("BroadcastReceiver::onReceive state OFF");
setTitle(R.string.bt_off);
unregisterReceiver(this);
deregStateRcv = false;
break;
}
}
}
};
private void stopSearch() {
//log("stopSearch");
if (scanner != null) {
scanner.stopScan();
scanner = null;
}
setProgressBarIndeterminateVisibility(false);
setTitle(R.string.searchFormUnconnected);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
menu.clear();
MenuInflater mi = getMenuInflater();
mi.inflate(R.menu.search_menu, menu);
log("onPrepareOptionsMenu: "+(scanner == null ? "NULL" : "OK"));
if (scanner != null) { // have active search
/*MenuItem bsrch = menu.findItem(R.id.bt_search_item);
bsrch.setVisible(false);
MenuItem isrch = menu.findItem(R.id.tcp_search_item);
isrch.setVisible(false);*/
MenuItem isrch = menu.findItem(R.id.search_item);
isrch.setVisible(false);
} else {
MenuItem srch = menu.findItem(R.id.cancel_search_item);
srch.setVisible(false);
}
return true;
}
// Selection handlers
//@Override
public void onItemSelected(AdapterView<?> parentView, View childView, int position, long id) {
setSelected(position);
}
//@Override
public void onNothingSelected(AdapterView<?> parentView) {
setSelected(-1);
}
public void setSelected(int pos) {
//log("setSelected "+pos);
selected = pos;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
case R.id.search_item:
stopSearch();
showDialog(Dispatcher.CMD_SEARCH_DIALOG);
break;
case R.id.cancel_search_item:
stopSearch();
break;
case R.id.enter_item:
stopSearch();
showDialog(Dispatcher.CMD_NEW_ADDR_DIALOG);
break;
case R.id.exit_item:
doExit();
break;
case R.id.log_item:
stopSearch();
showLog();
break;
case R.id.about_item:
About about = new About(this);
about.setTitle(R.string.about);
about.show();
}
return true;
}
public void doExit() {
log("doExit");
stopSearch();
anyRemote.sendGlobal(anyRemote.DO_EXIT, null);
finish();
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
log("onKeyUp: "+keyCode);
boolean lp = longPress;
longPress = false;
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
if (event.isTracking() && !event.isCanceled() && lp) {
log("onKeyUp KEYCODE_BACK long press - show menu");
new Handler().postDelayed(new Runnable() {
public void run() {
openOptionsMenu();
}
}, 1000);
} else {
doExit();
}
return true;
case KeyEvent.KEYCODE_VOLUME_UP:
if (selected > 0) {
selected--;
}
searchList.setSelection(selected);
return true;
case KeyEvent.KEYCODE_VOLUME_DOWN:
if (selected < dataSource.size() - 1) {
selected++;
}
searchList.setSelection(selected);
return true;
}
return false;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
return true;
case KeyEvent.KEYCODE_BACK:
event.startTracking();
return true;
}
return false;
}
protected void showWeb() {
log("showWeb");
final Intent showWeb = new Intent(getBaseContext(), WebScreen.class);
startActivity(showWeb);
}
public void switchBluetoothOn() {
log("switchBluetoothOn");
deregStateRcv = true;
registerReceiver(mBTStateReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), 0);
}
public void doConnect(String address) {
log("doConnect: "+address);
Address a = dataSource.getItem(address);
if (a == null) {
log("doConnect: can not get information for "+address);
return;
}
doConnect(a);
}
private void doConnect(Address a) {
log("doConnect: host is "+a.URL);
stopSearch();
connectTo = a.URL;
connectName = a.name;
connectPass = a.pass;
connectAuto = a.autoconnect;
if (connectTo.startsWith("btspp:") && mBtAdapter != null && !mBtAdapter.isEnabled()) {
switchBluetoothOn();
} else if (connectTo.startsWith("web://")) {
anyRemote.protocol.webUrl = "http://"+connectTo.substring(6); // 6 == size("web://")
addAddress(connectName,connectTo,connectPass,connectAuto);
showWeb();
} else {
doRealConnect();
}
}
public void doRealConnect() {
//log("doRealConnect");
stopSearch();
log("doRealConnect: address is "+connectTo);
// be sure peer is stored
addAddress(connectName,connectTo,connectPass,connectAuto);
Address conn = new Address();
conn.name = connectName;
conn.URL = connectTo;
conn.pass = connectPass;
connectTo = "";
connectName = "";
connectPass = "";
connectAuto = false;
anyRemote.sendGlobal(anyRemote.DO_CONNECT, conn);
log("SearchForm::doRealConnect: finish");
finish();
}
public void changeAddress(Address a) {
log("changeAddress");
if (a == null) {
return;
}
if (a.URL == null) {
log("changeAddress: can not get peer address for "+a.name);
return;
}
connectPass = (a.pass == null ? "" : a.pass);
connectTo = a.URL;
connectName = a.name;
connectAuto = a.autoconnect;
showDialog(Dispatcher.CMD_EDIT_FORM_ADDR);
}
public void cleanAddress(String name) {
//log("cleanAddress "+name);
anyRemote.protocol.cleanAddress(name);
}
// save new address in preferences
public void addAddress(String name, String URL, String pass,boolean autoconnect) {
log("addAddress "+name+" >"+URL+"<");
anyRemote.protocol.addAddress(name, URL, pass, autoconnect);
}
// Got result from AddressDialog dialog ("Ok"/"Cancel" was pressed)
public void onDismissAddressDialog (DialogInterface dialog) {
if (skipDismissDialog) {
skipDismissDialog = false;
} else {
String n = stripNewLines(((AddressDialog) dialog).getPeerName());
String a = stripNewLines(((AddressDialog) dialog).getPeerAddress());
String p = stripNewLines(((AddressDialog) dialog).getPeerPassword());
boolean ac = ((AddressDialog) dialog).getPeerAutoConnect();
log("onDismiss AddressDialog >"+n+"< >"+a+"< >"+p+"<");
if (a.length() == 0) {
return;
}
if (n.length() == 0) {
n = a;
}
if (a.startsWith("btspp://")) {
a = formatBTAddr(a);
}
if (connectName.length() > 0 && a != connectName) {
cleanAddress(connectName);
dataSource.remove(connectName);
}
if (dataSource.addIfNew(n,a,p,ac)) {
addAddress(n,a,p,ac);
}
}
}
// Got result from SearchDialog dialog ("Ok"/"Cancel" was pressed)
public void onDismissProtoChooseDialog (DialogInterface dialog) {
if (skipDismissDialog) {
skipDismissDialog = false;
} else {
boolean isBT = ((BT_IP_Choose_Dialog) dialog).isBluetooth();
if (((BT_IP_Choose_Dialog) dialog).id() == Dispatcher.CMD_NEW_ADDR_DIALOG) {
int id = (isBT ? Dispatcher.CMD_EDIT_FORM_BT : Dispatcher.CMD_EDIT_FORM_IP);
showDialog(id);
} else if (((BT_IP_Choose_Dialog) dialog).id() == Dispatcher.CMD_SEARCH_DIALOG) {
if (scanner != null) {
log("scanner: already searching");
return;
}
if (isBT) {
scanner = new BTScanner(handler, this);
} else {
boolean zeroconf = ((BT_IP_Choose_Dialog) dialog).isZeroconf();
int apiVersion = Integer.valueOf(android.os.Build.VERSION.SDK);
if (zeroconf && apiVersion >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
scanner = new ZCScanner(handler, anyRemote.protocol.context);
} else {
scanner = new IPScanner(handler);
}
}
if (scanner != null) {
scanner.startScan();
setProgressBarIndeterminateVisibility(true);
setTitle(R.string.searching);
}
}
}
}
// Handle "Cancel" press in EditFieldDialog/SearchDialog
public void onCancel(DialogInterface dialog) {
skipDismissDialog = true;
}
@Override
protected Dialog onCreateDialog(int id) {
switch(id){
case Dispatcher.CMD_EDIT_FORM_ADDR:
case Dispatcher.CMD_EDIT_FORM_IP:
case Dispatcher.CMD_EDIT_FORM_BT:
return new AddressDialog(this);
case Dispatcher.CMD_NEW_ADDR_DIALOG:
return new BT_IP_Choose_Dialog(this, Dispatcher.CMD_NEW_ADDR_DIALOG);
case Dispatcher.CMD_SEARCH_DIALOG:
return new BT_IP_Choose_Dialog(this, Dispatcher.CMD_SEARCH_DIALOG);
case Dispatcher.CMD_SENSOR_DIALOG:
return new BT_IP_Choose_Dialog(this, Dispatcher.CMD_SEARCH_DIALOG);
}
return null;
}
@Override
protected void onPrepareDialog(int id, Dialog d) {
if (d == null) return;
skipDismissDialog = false;
switch(id){
case Dispatcher.CMD_EDIT_FORM_ADDR:
((AddressDialog) d).setupDialog(connectName,connectTo,connectPass,connectAuto);
break;
case Dispatcher.CMD_EDIT_FORM_IP:
connectPass = "";
connectTo = "";
connectName = "";
((AddressDialog) d).setupDialog("",getResources().getString(R.string.default_ip),"",false);
break;
case Dispatcher.CMD_EDIT_FORM_BT:
connectPass = "";
connectTo = "";
connectName = "";
((AddressDialog) d).setupDialog("",getResources().getString(R.string.default_bt),"",false);
break;
case Dispatcher.CMD_NEW_ADDR_DIALOG:
// nothing
break;
case Dispatcher.CMD_SEARCH_DIALOG:
// nothing
break;
default:
return;
}
switch(id){
case Dispatcher.CMD_EDIT_FORM_ADDR:
case Dispatcher.CMD_EDIT_FORM_IP:
case Dispatcher.CMD_EDIT_FORM_BT:
d.setOnDismissListener(new OnDismissListener() {
public void onDismiss(DialogInterface dialog) {
onDismissAddressDialog(dialog);
}
});
break;
case Dispatcher.CMD_NEW_ADDR_DIALOG:
case Dispatcher.CMD_SEARCH_DIALOG:
d.setOnDismissListener(new OnDismissListener() {
public void onDismiss(DialogInterface dialog) {
onDismissProtoChooseDialog(dialog);
}
});
}
d.setOnCancelListener (this);
}
private String formatBTAddr(String value) {
// Samsung's does allows BT address only in capital
StringBuffer baddr = new StringBuffer("btspp://");
baddr.append(value.substring(8)
.replace('a', 'A').replace('b', 'B')
.replace('c', 'C').replace('d', 'D')
.replace('e', 'E').replace('f', 'F'));
//int i = 8;
//while (i < baddr.length() - 3) {
// if (baddr.charAt(i) == ':') {
// baddr.deleteCharAt(i);
// } else {
// i++;
// }
//}
log("formatBTAddr: "+baddr);
return new String(baddr);
}
private String stripNewLines(String value) {
if (value.length() > 0 && value.charAt(value.length() - 1) == '\n') {
value = value.substring(0, value.length() - 1);
}
if (value.length() > 0 && value.charAt(0) == '\n') {
value = value.substring(1, value.length());
}
return value;
}
}