Java tutorial
/************************************************************************** * This file is part of HCI Debugger * * <p/> * * Copyright (C) 2016-2017 Bertrand Martel * * <p/> * * HCI Debugger 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. * * <p/> * * HCI Debugger 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. * * <p/> * * You should have received a copy of the GNU General Public License * * along with HCI Debugger. If not, see <http://www.gnu.org/licenses/>. * */ package com.github.akinaru.hcidebugger.activity; import android.Manifest; import android.app.AlertDialog; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.NavigationView; import android.support.v4.view.MenuItemCompat; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.ShareActionProvider; import android.text.Editable; import android.text.TextWatcher; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; import com.github.akinaru.hcidebugger.R; import com.github.akinaru.hcidebugger.adapter.PacketAdapter; import com.github.akinaru.hcidebugger.common.Constants; import com.github.akinaru.hcidebugger.common.SimpleDividerItemDecoration; import com.github.akinaru.hcidebugger.dialog.SnoopFileDialog; import com.github.akinaru.hcidebugger.inter.IHciDebugger; import com.github.akinaru.hcidebugger.inter.IViewHolderClickListener; import com.github.akinaru.hcidebugger.menu.MenuUtils; import com.github.akinaru.hcidebugger.model.AdvertizingReport; import com.github.akinaru.hcidebugger.model.Filters; import com.github.akinaru.hcidebugger.model.Packet; import com.github.akinaru.hcidebugger.model.PacketDest; import com.github.akinaru.hcidebugger.model.PacketHciAclData; import com.github.akinaru.hcidebugger.model.PacketHciCmd; import com.github.akinaru.hcidebugger.model.PacketHciEvent; import com.github.akinaru.hcidebugger.model.PacketHciEventLEMeta; import com.github.akinaru.hcidebugger.model.PacketHciLEAdvertizing; import com.github.akinaru.hcidebugger.model.PacketHciScoData; import com.github.akinaru.hcidebugger.model.ScanType; import com.github.akinaru.hcidebugger.model.ValuePair; import com.github.akinaru.hcidebugger.service.HciDebuggerService; import com.github.akinaru.hcidebugger.service.IDecodingService; import com.github.akinaru.hcidebugger.service.IPacketReceptionCallback; import com.github.akinaru.hcidebugger.view.CustomRecyclerView; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Main activity * * @author Bertrand Martel */ public class HciDebuggerActivity extends BaseActivity implements SwipeRefreshLayout.OnRefreshListener, IHciDebugger, IPacketReceptionCallback { private static String TAG = HciDebuggerActivity.class.getSimpleName(); /** * number of frame decoded via the callback onHciFrameReceived */ private int frameCount = 1; /** * HCI packet recycler view (customized for scroll speed issue) */ private CustomRecyclerView packetListView; /** * adapter for recyclerview */ private PacketAdapter packetAdapter; /** * thread pool of 1 thread used to execute btsnoop file decoding task */ private ExecutorService pool = Executors.newFixedThreadPool(1); /** * bluetooth adapter for setting bluetooth on/off, scanning */ private BluetoothAdapter mBluetoothAdapter = null; /** * define if bluetooth is scanning or not */ private boolean mScanning; /** * define if current configuration is the result of a filter operation or not */ private boolean isFiltered = false; /** * request permission code for read external (for sdk 23+) */ private final static int REQUEST_PERMISSION_READ_EXTERNAL = 1; /** * total list of HCI packet decoded */ private List<Packet> packetList = new ArrayList<>(); /** * list of filtered HCI packet (this is cleared when filter is cancelled) */ private List<Packet> packetFilteredList = new ArrayList<>(); /** * filter management object */ private Filters filters; /** * permit to start a scan AFTER enabling bluetooth */ private boolean startScan = false; /** * define if float button is targetting a scroll down (false for scroll up) */ private boolean floatBtnDown = true; /** * frame used for the swipe + recyclerview */ FrameLayout mDisplayFrame; /** * frame used for displaying a textview to wait for packet count resolution */ FrameLayout mWaitingFrame; /** * total HCI packet count (even those not decoded) */ private int mPacketCount = 0; /** * max packet count to be displayed */ private int mMaxPacketCount = Constants.DEFAULT_LAST_PACKET_COUNT; /** * define if all packet have been received once */ private boolean mAllPacketInit = false; /** * HCI decoding service */ private IDecodingService service; /** * define if activity is bound to service */ private boolean bound = false; private TextView mNothingToShowTv; private boolean mFirstPacketReceived = true; private ScanType mScanType = ScanType.CLASSIC_SCAN; private static final int REQUEST_PERMISSION_COARSE_LOCATION = 2; private BluetoothAdapter.LeScanCallback scanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) { } }; /** * task run in a thread to decoded btsnoop file, HCI packets */ private Runnable decodingTask = new Runnable() { @Override public void run() { Log.v(TAG, "decoding task"); runOnUiThread(new Runnable() { @Override public void run() { mFirstPacketReceived = true; //when decoding task is launched hide recyclerview to wait for packet count callback mDisplayFrame.setVisibility(View.GONE); mNothingToShowTv.setText(getResources().getString(R.string.processing_hci_frames)); mWaitingFrame.setVisibility(View.VISIBLE); } }); configSnoopFile(true); File file = new File(mBtSnoopFilePath); if (!file.exists()) { resetSnoopFile(); file = new File(mBtSnoopFilePath); if (!file.exists()) showWarningDialog(getResources().getString(R.string.hci_warning)); return; } mAllPacketInit = false; if (!mBtSnoopFilePath.equals("")) { if (service != null) { service.startHciLogStream(mBtSnoopFilePath, prefs.getInt(Constants.PREFERENCES_MAX_PACKET_COUNT, Constants.DEFAULT_LAST_PACKET_COUNT)); } else { Log.e(TAG, "error service null"); } } else { showWarningDialog( "HCI file path not specified in " + getResources().getString(R.string.bluetooth_config)); } } }; /** * show a dialog when problem occur * * @param message */ private void showWarningDialog(final String message) { runOnUiThread(new Runnable() { @Override public void run() { DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { switch (which) { case DialogInterface.BUTTON_POSITIVE: SnoopFileDialog dialogBrowse = new SnoopFileDialog(HciDebuggerActivity.this); setCurrentDialog(dialogBrowse); dialogBrowse.show(); break; case DialogInterface.BUTTON_NEGATIVE: pool.execute(decodingTask); break; } } }; AlertDialog.Builder builder = new AlertDialog.Builder(HciDebuggerActivity.this); builder.setCancelable(false); builder.setMessage(message) .setPositiveButton(getResources().getString(R.string.browse_button), dialogClickListener) .setNegativeButton(getResources().getString(R.string.warning_dialog_retry), dialogClickListener) .show(); } }); } /** * swipe refresh layout used to make refresh animation when scrolling top of recyclerview */ private SwipeRefreshLayout mSwipeRefreshLayout; protected void onCreate(Bundle savedInstanceState) { setLayout(R.layout.debugger_activity); super.onCreate(savedInstanceState); //setup frames mDisplayFrame = (FrameLayout) findViewById(R.id.display_frame); mWaitingFrame = (FrameLayout) findViewById(R.id.waiting_frame); mNothingToShowTv = (TextView) findViewById(R.id.nothing_to_show); // Setup drawer view setupDrawerContent(nvDrawer); filters = new Filters(this, "", "", "", "", ""); filters.setPacketType(prefs.getString(Constants.PREFERENCES_PACKET_TYPE_FILTER, "")); filters.setEventType(prefs.getString(Constants.PREFERENCES_EVENT_TYPE_FILTER, "")); filters.setOgf(prefs.getString(Constants.PREFERENCES_OGF_FILTER, "")); filters.setSubEventType(prefs.getString(Constants.PREFERENCES_SUBEVENT_FILTERS, "")); filters.setAddress(prefs.getString(Constants.PREFERENCES_ADVERTISING_ADDR, "")); mMaxPacketCount = prefs.getInt(Constants.PREFERENCES_MAX_PACKET_COUNT, Constants.DEFAULT_LAST_PACKET_COUNT); mScanType = ScanType.getScanType(prefs.getString(Constants.PREFERENCES_SCAN_TYPE, "")); mBtSnoopFilePath = prefs.getString(Constants.PREFERENCES_BTSNOOP_FILEPATH, getHciLogFilePath()); // init Bluetooth adapter final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE); mBluetoothAdapter = bluetoothManager.getAdapter(); //init recycler view packetListView = (CustomRecyclerView) findViewById(R.id.packet_list); packetList = new ArrayList<>(); packetAdapter = new PacketAdapter(packetList, this, new IViewHolderClickListener() { @Override public void onClick(View view) { int index = packetListView.getChildAdapterPosition(view); //launch packet description activity Intent intent = new Intent(HciDebuggerActivity.this, DescriptionActivity.class); if (packetFilteredList.size() != 0) { intent.putExtra(Constants.INTENT_HCI_PACKET, packetFilteredList.get(index).getJsonFormattedHciPacket()); intent.putExtra(Constants.INTENT_SNOOP_PACKET, packetFilteredList.get(index).getJsonFormattedSnoopPacket()); intent.putExtra(Constants.INTENT_PACKET_NUMBER, packetFilteredList.get(index).getNum()); intent.putExtra(Constants.INTENT_PACKET_TS, timestampFormat.format(packetFilteredList.get(index).getTimestamp().getTime())); intent.putExtra(Constants.INTENT_PACKET_TYPE, packetFilteredList.get(index).getDisplayedType()); intent.putExtra(Constants.INTENT_PACKET_DEST, packetFilteredList.get(index).getDest().toString()); } else { intent.putExtra(Constants.INTENT_HCI_PACKET, packetList.get(index).getJsonFormattedHciPacket()); intent.putExtra(Constants.INTENT_SNOOP_PACKET, packetList.get(index).getJsonFormattedSnoopPacket()); intent.putExtra(Constants.INTENT_PACKET_NUMBER, packetList.get(index).getNum()); intent.putExtra(Constants.INTENT_PACKET_TS, timestampFormat.format(packetList.get(index).getTimestamp().getTime())); intent.putExtra(Constants.INTENT_PACKET_TYPE, packetList.get(index).getDisplayedType()); intent.putExtra(Constants.INTENT_PACKET_DEST, packetList.get(index).getDest().toString()); } startActivity(intent); } }); //set layout manager packetListView.setLayoutManager(new GridLayoutManager(this, 1, LinearLayoutManager.VERTICAL, false)); //set line decoration packetListView.addItemDecoration(new SimpleDividerItemDecoration(getApplicationContext())); packetListView.setAdapter(packetAdapter); //setup swipe refresh mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swiperefresh); mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { refresh(); } }); //setup floating button final FloatingActionButton upFloatingBtn = (FloatingActionButton) findViewById(R.id.updown_btn); upFloatingBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // Click action if (floatBtnDown) { packetListView.scrollToPosition(packetListView.getAdapter().getItemCount() - 1); floatBtnDown = false; upFloatingBtn.setImageResource(R.drawable.ic_arrow_upward); } else { packetListView.scrollToPosition(0); floatBtnDown = true; upFloatingBtn.setImageResource(R.drawable.ic_arrow_downward); } } }); if (Build.VERSION.SDK_INT >= 23) { if (checkSelfPermission( Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[] { Manifest.permission.READ_EXTERNAL_STORAGE }, REQUEST_PERMISSION_READ_EXTERNAL); } else { bindService(); } } else { bindService(); } } @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { switch (requestCode) { case REQUEST_PERMISSION_READ_EXTERNAL: { // If request is cancelled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { bindService(); } else { finish(); } break; } case REQUEST_PERMISSION_COARSE_LOCATION: { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { runOnUiThread(new Runnable() { @Override public void run() { startBleScan(); startScanSetup(); } }); } else { runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(HciDebuggerActivity.this, "permission coarse location required for ble scan", Toast.LENGTH_SHORT).show(); } }); } } } } private void bindService() { Intent intent = new Intent(this, HciDebuggerService.class); // bind the service to current activity and create it if it didnt exist before startService(intent); bound = bindService(intent, mServiceConnection, BIND_AUTO_CREATE); } public void onResume() { super.onResume(); //register bluetooth receiver IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED); registerReceiver(mBroadcastReceiver, intentFilter); } /** * Manage Bluetooth Service lifecycle */ private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.v(TAG, "Connected to service"); HciDebuggerActivity.this.service = ((HciDebuggerService.LocalBinder) service).getService(); HciDebuggerActivity.this.service.setPacketReceptionCb(HciDebuggerActivity.this); //launch btsnoop file decoding pool.execute(decodingTask); } @Override public void onServiceDisconnected(ComponentName name) { } }; /** * clear adapter */ private void clearAdapter() { runOnUiThread(new Runnable() { @Override public void run() { packetList.clear(); packetFilteredList.clear(); notifyAdapter(); } }); } /** * start/stop bluetooth scan * * @param item scan menu item */ @Override public void toggleScan(MenuItem item) { if (!mScanning) { Log.v(TAG, "starting scan"); if (!mBluetoothAdapter.isEnabled()) { startScan = true; setBluetooth(true); } else { startScan(); } } else { Log.v(TAG, "stopping scan"); stopScan(); } } private void stopScan() { if (mScanning) { runOnUiThread(new Runnable() { @Override public void run() { if (mScanType == ScanType.CLASSIC_SCAN) { mBluetoothAdapter.cancelDiscovery(); } else { Toast.makeText(HciDebuggerActivity.this, getResources().getString(R.string.toast_scan_stop), Toast.LENGTH_SHORT).show(); mBluetoothAdapter.stopLeScan(scanCallback); } mScanning = false; MenuItem item; if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { item = nvDrawer.getMenu().findItem(R.id.scan_btn_nv); } else { item = toolbar.getMenu().findItem(R.id.scan_btn); } if (item != null) { if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { item.setIcon(R.drawable.ic_looks); } else { item.setIcon(R.drawable.ic_action_scanning); } item.setTitle(getResources().getString(R.string.menu_item_title_start_scan)); } //Toast.makeText(HciDebuggerActivity.this, getResources().getString(R.string.toast_scan_stop), Toast.LENGTH_SHORT).show(); } }); } else { Log.v(TAG, "not scanning"); } } @Override public boolean onPrepareOptionsMenu(Menu menu) { boolean ret = super.onPrepareOptionsMenu(menu); if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { toolbar.getMenu().findItem(R.id.scan_btn).setVisible(false); toolbar.getMenu().findItem(R.id.state_bt_btn).setVisible(false); toolbar.getMenu().findItem(R.id.reset_snoop_file).setVisible(false); nvDrawer.getMenu().findItem(R.id.scan_btn_nv).setVisible(true); if (mScanType == ScanType.CLASSIC_SCAN) { nvDrawer.getMenu().findItem(R.id.change_settings) .setTitle(getString(R.string.scan_settings_type_classic_scan)); } else { nvDrawer.getMenu().findItem(R.id.change_settings) .setTitle(getString(R.string.scan_settings_type_ble_scan)); } if (Build.VERSION.SDK_INT <= 22) { nvDrawer.getMenu().findItem(R.id.reset_snoop_file_nv).setVisible(true); } MenuItem stateBtn = nvDrawer.getMenu().findItem(R.id.state_bt_btn_nv); stateBtn.setVisible(true); if (mBluetoothAdapter.isEnabled()) { stateBtn.setIcon(R.drawable.ic_bluetooth); } else { stateBtn.setIcon(R.drawable.ic_bluetooth_disabled); } stateBtn.setTitle(getResources().getString(R.string.menu_item_title_enable_bluetooth_portrait)); } return ret; } /** * start bluetooth scan (launch from menu or when bluetooth is activated if previous start scan was launched) */ private void startScan() { mScanning = true; if (mScanType == ScanType.CLASSIC_SCAN) { if (mBluetoothAdapter.isDiscovering()) { mBluetoothAdapter.cancelDiscovery(); } mBluetoothAdapter.startDiscovery(); } else { if (Build.VERSION.SDK_INT >= 23) { if (checkSelfPermission( Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[] { Manifest.permission.ACCESS_COARSE_LOCATION }, REQUEST_PERMISSION_COARSE_LOCATION); return; } else { startBleScan(); } } } startScanSetup(); } private void startScanSetup() { MenuItem item; if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { item = nvDrawer.getMenu().findItem(R.id.scan_btn_nv); } else { item = toolbar.getMenu().findItem(R.id.scan_btn); } if (item != null) { item.setIcon(R.drawable.ic_portable_wifi_off); item.setTitle(getResources().getString(R.string.menu_item_title_stop_scan)); } Toast.makeText(HciDebuggerActivity.this, getResources().getString(R.string.toast_scan_start), Toast.LENGTH_SHORT).show(); } private void startBleScan() { mBluetoothAdapter.stopLeScan(scanCallback); mBluetoothAdapter.startLeScan(scanCallback); } /** * switch on/off bluetooth */ @Override public void toggleBtState() { if (mBluetoothAdapter.isEnabled()) { setBluetooth(false); } else { setBluetooth(true); } } public void resetSnoopFile() { configSnoopFile(false); configSnoopFile(true); //refresh(); } @Override public ScanType getScanType() { return mScanType; } @Override public void setScanType(ScanType scanType) { SharedPreferences.Editor editor = prefs.edit(); editor.putString(Constants.PREFERENCES_SCAN_TYPE, scanType.getValue()); editor.commit(); if (scanType == ScanType.CLASSIC_SCAN) { nvDrawer.getMenu().findItem(R.id.change_settings) .setTitle(getResources().getString(R.string.scan_settings_type_classic_scan)); } else { nvDrawer.getMenu().findItem(R.id.change_settings) .setTitle(getResources().getString(R.string.scan_settings_type_ble_scan)); } mScanType = scanType; } @Override public String getBtSnoopFilePath() { return mBtSnoopFilePath; } @Override public void setBtSnoopFilePath(String path) { mBtSnoopFilePath = path; setSharedIntent(); SharedPreferences.Editor editor = prefs.edit(); editor.putString(Constants.PREFERENCES_BTSNOOP_FILEPATH, mBtSnoopFilePath); editor.commit(); refresh(); } @Override public String getDefaultBtSnoopPath() { return getHciLogFilePath(); } /** * Activate/Desactivate snoop file log * * @param state true if snoop file log is activated, false elsewhere */ public void configSnoopFile(boolean state) { BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); Method[] adapterMethods = adapter.getClass().getDeclaredMethods(); for (Method method : adapterMethods) { if (method.getName().equals("configHciSnoopLog")) { Log.v(TAG, "activating snoop file log"); try { boolean ret = (boolean) method.invoke(adapter, state); Log.v(TAG, "configSnoop return with state " + ret); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } } /** * setup a filter */ private void filter() { Log.v(TAG, "setting filter"); final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(HciDebuggerActivity.this); LayoutInflater inflater = getLayoutInflater(); final View dialogView = inflater.inflate(R.layout.filter_dialog, null); dialogBuilder.setView(dialogView); //packet type setupSpinnerAdapter(R.array.packet_type_array, dialogView, R.id.packet_type_filter, filters.getPacketTypeFilter()); //event type setupSpinnerAdapter(R.array.event_type_array, dialogView, R.id.event_type_filter, filters.getEventTypeFilter()); //ogf setupSpinnerAdapter(R.array.ogf_array, dialogView, R.id.cmd_ogf_filter, filters.getOgfFilter()); //subevent_type_filter setupSpinnerAdapter(R.array.subevent_array, dialogView, R.id.subevent_type_filter, filters.getSubeventFilter()); EditText addressText = (EditText) dialogView.findViewById(R.id.device_address_edit); addressText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { filters.setAddress(s.toString()); SharedPreferences.Editor editor = prefs.edit(); editor.putString(Constants.PREFERENCES_ADVERTISING_ADDR, s.toString()); editor.commit(); } @Override public void afterTextChanged(Editable s) { } }); addressText.setText(filters.getAdvertizingAddr()); final AlertDialog alertDialog = dialogBuilder.create(); final Button button_withdraw_filter = (Button) dialogView.findViewById(R.id.button_withdraw_filter); if (isFiltered) button_withdraw_filter.setVisibility(View.VISIBLE); else button_withdraw_filter.setVisibility(View.GONE); button_withdraw_filter.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { isFiltered = false; packetAdapter.setPacketList(packetList); notifyAdapter(); alertDialog.cancel(); alertDialog.dismiss(); packetFilteredList.clear(); } }); Button button_apply = (Button) dialogView.findViewById(R.id.button_apply_filter); button_apply.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Spinner packet_type_filter = (Spinner) dialogView.findViewById(R.id.packet_type_filter); Spinner ogf_filter = (Spinner) dialogView.findViewById(R.id.cmd_ogf_filter); Spinner event_type_filter = (Spinner) dialogView.findViewById(R.id.event_type_filter); Spinner subevent_type_filter = (Spinner) dialogView.findViewById(R.id.subevent_type_filter); EditText device_address_edit = (EditText) dialogView.findViewById(R.id.device_address_edit); filters = new Filters(HciDebuggerActivity.this, packet_type_filter.getSelectedItem().toString(), event_type_filter.getSelectedItem().toString(), ogf_filter.getSelectedItem().toString(), subevent_type_filter.getSelectedItem().toString(), device_address_edit.getText().toString()); packetFilteredList = new ArrayList<Packet>(); for (int i = 0; i < packetList.size(); i++) { if (matchFilter(packetList.get(i))) { packetFilteredList.add(packetList.get(i)); } } isFiltered = true; packetAdapter.setPacketList(packetFilteredList); notifyAdapter(); alertDialog.cancel(); alertDialog.dismiss(); } }); Button button_cancel = (Button) dialogView.findViewById(R.id.button_cancel_filter); button_cancel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { alertDialog.cancel(); alertDialog.dismiss(); } }); alertDialog.show(); } /** * refresh adapter */ public void refresh() { Log.v(TAG, "refreshing adapter"); packetList.clear(); packetFilteredList.clear(); notifyAdapter(); frameCount = 1; if (service != null) { service.stopHciLogStream(); } else { Log.e(TAG, "error service null"); } pool.execute(decodingTask); mSwipeRefreshLayout.setRefreshing(false); } @Override public Context getContext() { return this; } /** * broadcast receiver for receiver callback when bluetooth is set on/off or external bluetooth discoverey started/stopped */ private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); //stop discovery detected if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) { if (mScanning) { if (mScanType == ScanType.CLASSIC_SCAN) { mBluetoothAdapter.startDiscovery(); } } else { runOnUiThread(new Runnable() { @Override public void run() { MenuItem item = toolbar.getMenu().findItem(R.id.scan_btn); if (item != null) { item.setIcon(R.drawable.ic_action_scanning); item.setTitle(getResources().getString(R.string.menu_item_title_start_scan)); } item = nvDrawer.getMenu().findItem(R.id.scan_btn_nv); if (item != null) { item.setIcon(R.drawable.ic_looks); item.setTitle(getResources().getString(R.string.menu_item_title_start_scan)); } Toast.makeText(HciDebuggerActivity.this, getResources().getString(R.string.toast_scan_stop), Toast.LENGTH_SHORT).show(); } }); } //start discovery detected } else if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) { runOnUiThread(new Runnable() { @Override public void run() { MenuItem item = toolbar.getMenu().findItem(R.id.scan_btn); if (item != null) { item.setIcon(R.drawable.ic_portable_wifi_off); item.setTitle(getResources().getString(R.string.menu_item_title_stop_scan)); } item = nvDrawer.getMenu().findItem(R.id.scan_btn_nv); if (item != null) { item.setIcon(R.drawable.ic_portable_wifi_off); item.setTitle(getResources().getString(R.string.menu_item_title_stop_scan)); } //Toast.makeText(HciDebuggerActivity.this, getResources().getString(R.string.toast_scan_start), Toast.LENGTH_SHORT).show(); } }); //bluetooth state change detected } else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); if (state == BluetoothAdapter.STATE_OFF) { runOnUiThread(new Runnable() { @Override public void run() { if (getResources() .getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { MenuItem stateBtn = nvDrawer.getMenu().findItem(R.id.state_bt_btn_nv); if (stateBtn != null) { stateBtn.setIcon(R.drawable.ic_bluetooth_disabled); stateBtn.setTitle(getResources() .getString(R.string.menu_item_title_enable_bluetooth_portrait)); } } else { MenuItem item = toolbar.getMenu().findItem(R.id.state_bt_btn); if (item != null) { item.setIcon(R.drawable.ic_bluetooth_disabled); item.setTitle( getResources().getString(R.string.menu_item_title_enable_bluetooth)); } } Toast.makeText(HciDebuggerActivity.this, getResources().getString(R.string.toast_bluetooth_disabled), Toast.LENGTH_SHORT) .show(); } }); } else if (state == BluetoothAdapter.STATE_ON) { runOnUiThread(new Runnable() { @Override public void run() { if (Build.VERSION.SDK_INT <= 21) { refresh(); } if (getResources() .getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { MenuItem stateBtn = nvDrawer.getMenu().findItem(R.id.state_bt_btn_nv); if (stateBtn != null) { stateBtn.setIcon(R.drawable.ic_bluetooth); stateBtn.setTitle(getResources() .getString(R.string.menu_item_title_enable_bluetooth_portrait)); } } else { MenuItem item = toolbar.getMenu().findItem(R.id.state_bt_btn); if (item != null) { item.setIcon(R.drawable.ic_bluetooth); item.setTitle( getResources().getString(R.string.menu_item_title_disable_bluetooth)); } } Toast.makeText(HciDebuggerActivity.this, getResources().getString(R.string.toast_bluetooth_enabled), Toast.LENGTH_SHORT) .show(); if (startScan) { startScan(); startScan = false; } } }); } } } }; @Override public boolean onCreateOptionsMenu(Menu menu) { this.getMenuInflater().inflate(R.menu.toolbar_menu, menu); //clear button MenuItem item = menu.findItem(R.id.clear_btn); if (item != null) { item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { clearAdapter(); return true; } }); } //bluetoooth scan button item = menu.findItem(R.id.scan_btn); if (item != null) { item.setIcon(R.drawable.ic_action_scanning); item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { toggleScan(item); return true; } }); } //bluetooth state button item = menu.findItem(R.id.state_bt_btn); if (item != null) { item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { toggleBtState(); return true; } }); if (mBluetoothAdapter.isEnabled()) { item.setIcon(R.drawable.ic_bluetooth); } else { item.setIcon(R.drawable.ic_bluetooth_disabled); } } //reset snoop file button if (Build.VERSION.SDK_INT <= 22) { item = menu.findItem(R.id.reset_snoop_file); item.setVisible(true); if (item != null) { item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { resetSnoopFile(); refresh(); return true; } }); } } //filter button item = menu.findItem(R.id.filter_btn); if (item != null) { item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { filter(); return true; } }); } //refresh button item = menu.findItem(R.id.refresh); if (item != null) { if (getResources().getConfiguration().orientation != Configuration.ORIENTATION_PORTRAIT) { item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { refresh(); return true; } }); } else { item.setVisible(false); } } item = menu.findItem(R.id.share); if (item != null) { // Fetch and store ShareActionProvider mShareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(item); setSharedIntent(); } return super.onCreateOptionsMenu(menu); } /** * setup navigation view * * @param navigationView */ private void setupDrawerContent(NavigationView navigationView) { navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(MenuItem menuItem) { MenuUtils.selectDrawerItem(menuItem, mDrawer, HciDebuggerActivity.this, HciDebuggerActivity.this, HciDebuggerActivity.this); return true; } }); } /** * determine if a packet match current filter or not * * @param packet generic HCI packet * @return true if packet match filter */ private boolean matchFilter(Packet packet) { if (!filters.getPacketTypeFilter().equals("")) { if (packet.getType().getValue().contains(filters.getPacketTypeFilter())) { if (filters.getPacketTypeFilter().equals(Constants.HCI_COMMAND)) { PacketHciCmd cmd = (PacketHciCmd) packet; if (!filters.getOgfFilter().equals("")) { if (cmd.getOgf().getValue().contains(filters.getOgfFilter())) { return true; } else { return false; } } else { return true; } } else if (filters.getPacketTypeFilter().equals(Constants.HCI_EVENT)) { PacketHciEvent event = (PacketHciEvent) packet; if (!filters.getEventTypeFilter().equals("")) { if (event.getEventType().getValue().contains(filters.getEventTypeFilter())) { if (!filters.getSubeventFilter().equals("") && filters.getEventTypeFilter().equals(Constants.HCI_LE_META)) { if (packet instanceof PacketHciEventLEMeta) { PacketHciEventLEMeta leMeta = (PacketHciEventLEMeta) packet; if (!filters.getSubeventFilter().equals("")) { if (leMeta.getSubevent().getValue().contains(filters.getSubeventFilter())) { if (filters.getSubeventFilter() .equals(Constants.HCI_ADVERTISING_REPORT)) { if (!filters.getAdvertizingAddr().equals("")) { PacketHciLEAdvertizing adReportFrame = (PacketHciLEAdvertizing) packet; for (int i = 0; i < adReportFrame.getReports().size(); i++) { if (adReportFrame.getReports().get(i).getAddress() .toLowerCase().equals(filters.getAdvertizingAddr() .toLowerCase())) { return true; } } return false; } else { return true; } } else { return true; } } else { return false; } } else { return true; } } else { return false; } } else { return true; } } else { return false; } } else { return true; } } return true; } } return false; } /** * setup filter spinner * * @param ressourceId * @param view * @param spinnerId * @param value */ private void setupSpinnerAdapter(final int ressourceId, final View view, int spinnerId, String value) { ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(HciDebuggerActivity.this, ressourceId, android.R.layout.simple_spinner_item); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); final Spinner sItems = (Spinner) view.findViewById(spinnerId); sItems.setAdapter(adapter); sItems.setSelection(getIndex(sItems, value)); sItems.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view2, int position, long id) { if (!sItems.getSelectedItem().toString().equals(getResources().getString(R.string.filter_choose))) { SharedPreferences.Editor editor = prefs.edit(); if (ressourceId == R.array.packet_type_array) { if (sItems.getSelectedItem().toString().equals(Constants.HCI_EVENT)) { displayEventSpinner(view); } else { displayCmdSpinner(view); } filters.setPacketType(sItems.getSelectedItem().toString()); editor.putString(Constants.PREFERENCES_PACKET_TYPE_FILTER, sItems.getSelectedItem().toString()); editor.commit(); } else if (ressourceId == R.array.event_type_array) { if (sItems.getSelectedItem().toString().equals(Constants.HCI_LE_META)) { displaySubEventSpinner(view); } filters.setEventType(sItems.getSelectedItem().toString()); editor.putString(Constants.PREFERENCES_EVENT_TYPE_FILTER, sItems.getSelectedItem().toString()); editor.commit(); } else if (ressourceId == R.array.ogf_array) { filters.setOgf(sItems.getSelectedItem().toString()); editor.putString(Constants.PREFERENCES_OGF_FILTER, sItems.getSelectedItem().toString()); editor.commit(); } else if (ressourceId == R.array.subevent_array) { if (sItems.getSelectedItem().toString().equals(Constants.HCI_ADVERTISING_REPORT)) { displayAdvertizingReportFilter(view); } filters.setSubEventType(sItems.getSelectedItem().toString()); editor.putString(Constants.PREFERENCES_SUBEVENT_FILTERS, sItems.getSelectedItem().toString()); editor.commit(); } } } @Override public void onNothingSelected(AdapterView<?> parent) { } }); } private int getIndex(Spinner spinner, String myString) { int index = 0; for (int i = 0; i < spinner.getCount(); i++) { if (spinner.getItemAtPosition(i).equals(myString)) { index = i; } } return index; } /** * display event filter spinner * * @param view */ private void displayEventSpinner(View view) { Spinner ogf_filter = (Spinner) view.findViewById(R.id.cmd_ogf_filter); ogf_filter.setVisibility(View.GONE); Spinner event_type_filter = (Spinner) view.findViewById(R.id.event_type_filter); event_type_filter.setVisibility(View.VISIBLE); } /** * display sub event spinner * * @param view */ private void displaySubEventSpinner(View view) { displayEventSpinner(view); Spinner subevent_type_filter = (Spinner) view.findViewById(R.id.subevent_type_filter); subevent_type_filter.setVisibility(View.VISIBLE); } /** * display advertising report filter views * * @param view */ private void displayAdvertizingReportFilter(View view) { displaySubEventSpinner(view); TextView device_address_label = (TextView) view.findViewById(R.id.device_address_label); device_address_label.setVisibility(View.VISIBLE); EditText device_address_edit = (EditText) view.findViewById(R.id.device_address_edit); device_address_edit.setVisibility(View.VISIBLE); } /** * setup visibility of all filter spinner * * @param view */ private void displayCmdSpinner(View view) { Spinner ogf_filter = (Spinner) view.findViewById(R.id.cmd_ogf_filter); ogf_filter.setVisibility(View.VISIBLE); Spinner event_type_filter = (Spinner) view.findViewById(R.id.event_type_filter); event_type_filter.setVisibility(View.GONE); Spinner subevent_type_filter = (Spinner) view.findViewById(R.id.subevent_type_filter); subevent_type_filter.setVisibility(View.GONE); TextView device_address_label = (TextView) view.findViewById(R.id.device_address_label); device_address_label.setVisibility(View.GONE); EditText device_address_edit = (EditText) view.findViewById(R.id.device_address_edit); device_address_edit.setVisibility(View.GONE); } /** * set bluetooth state * * @param state bluetooth state * @return success if true / failure if false */ private boolean setBluetooth(boolean state) { boolean isEnabled = mBluetoothAdapter.isEnabled(); Log.v(TAG, "Setting bluetooth " + isEnabled + " : " + state); if (!isEnabled && state) { return mBluetoothAdapter.enable(); } else if (isEnabled && !state) { return mBluetoothAdapter.disable(); } return false; } @Override public void onPause() { super.onPause(); stopScan(); unregisterReceiver(mBroadcastReceiver); } @Override protected void onDestroy() { super.onDestroy(); Log.v(TAG, "exiting HCI Debugger app"); if (mDialog != null) { mDialog.dismiss(); } if (service != null) { service.stopHciLogStream(); } try { if (bound) { // unregister receiver or you will have strong exception unbindService(mServiceConnection); bound = false; } } catch (IllegalArgumentException e) { e.printStackTrace(); } } /** * callback called from native function when packet count is finished * * @param packetCount total number of HCI packet available */ @Override public void onFinishedPacketCount(int packetCount) { Log.v(TAG, "onFinishedPacketCount " + packetCount); mPacketCount = packetCount; runOnUiThread(new Runnable() { @Override public void run() { //display recyclerview + swipe refresh view mNothingToShowTv.setText(getResources().getString(R.string.file_snoop_empty)); } }); } @Override public void onError(final int errorCode, String errorMessage) { runOnUiThread(new Runnable() { @Override public void run() { if (errorCode == 0) showWarningDialog(getResources().getString(R.string.error_opening)); else showWarningDialog(getResources().getString(R.string.error_reading)); } }); } /** * callback called from native function when a HCI pakcet has been decoded * * @param snoopFrame snoop frame part * @param hciFrame HCI packet part */ @Override public void onHciFrameReceived(final String snoopFrame, final String hciFrame) { if (mFirstPacketReceived) { mFirstPacketReceived = false; runOnUiThread(new Runnable() { @Override public void run() { //display recyclerview + swipe refresh view mWaitingFrame.setVisibility(View.GONE); mDisplayFrame.setVisibility(View.VISIBLE); } }); } if (!mAllPacketInit && ((frameCount >= mPacketCount) || (frameCount >= mMaxPacketCount))) { mAllPacketInit = true; } if (mAllPacketInit) mPacketCount++; try { JSONObject snoopJson = new JSONObject(snoopFrame); final Date timestamp = new Date(snoopJson.getLong("timestamp_microseconds") / 1000); PacketDest dest = PacketDest.PACKET_SENT; if (snoopJson.getBoolean("packet_received")) { dest = PacketDest.PACKET_RECEIVED; } JSONObject hciJson = new JSONObject(hciFrame); JSONObject packet_type = hciJson.getJSONObject("packet_type"); final ValuePair type = new ValuePair(packet_type.getInt("code"), packet_type.getString("value")); JSONObject parameters = null; if (hciJson.has("parameters")) parameters = hciJson.getJSONObject("parameters"); final PacketDest finalDest = dest; if (type.getCode() == 4) { JSONObject event_code = hciJson.getJSONObject("event_code"); final ValuePair eventType = new ValuePair(event_code.getInt("code"), event_code.getString("value")); if (hciJson.has("subevent_code")) { JSONObject subevent_code = hciJson.getJSONObject("subevent_code"); final ValuePair subevent_code_val = new ValuePair(subevent_code.getInt("code"), subevent_code.getString("value")); if (subevent_code_val.getCode() == 2) { if (parameters != null && parameters.has("reports")) { JSONArray reports = parameters.getJSONArray("reports"); final List<AdvertizingReport> reportList = new ArrayList<>(); for (int i = 0; i < reports.length(); i++) { JSONObject reportItem = reports.getJSONObject(i); reportList.add(new AdvertizingReport(reportItem.getString("address"), reportItem.getInt("address_type"), reportItem.getJSONArray("data"), reportItem.getInt("data_length"), reportItem.getInt("event_type"), reportItem.getInt("rssi"))); } runOnUiThread(new Runnable() { @Override public void run() { Packet packet = new PacketHciLEAdvertizing(frameCount++, timestamp, finalDest, type, eventType, subevent_code_val, reportList, hciFrame, snoopFrame); packetList.add(0, packet); if (isFiltered && matchFilter(packet)) packetFilteredList.add(0, packet); notifyAdapter(); } }); return; } } } runOnUiThread(new Runnable() { @Override public void run() { Packet packet = new PacketHciEvent(frameCount++, timestamp, finalDest, type, eventType, hciFrame, snoopFrame); packetList.add(0, packet); if (isFiltered && matchFilter(packet)) packetFilteredList.add(0, packet); notifyAdapter(); } }); } else if (type.getCode() == 1) { JSONObject ogf_obj = hciJson.getJSONObject("ogf"); final ValuePair ogf = new ValuePair(ogf_obj.getInt("code"), ogf_obj.getString("value")); final ValuePair ocf; if (hciJson.has("ocf")) { JSONObject ocf_obj = hciJson.getJSONObject("ocf"); ocf = new ValuePair(ocf_obj.getInt("code"), ocf_obj.getString("value")); } else { ocf = new ValuePair(-1, ""); } runOnUiThread(new Runnable() { @Override public void run() { Packet packet = new PacketHciCmd(frameCount++, timestamp, finalDest, type, ocf, ogf, hciFrame, snoopFrame); packetList.add(0, packet); if (isFiltered && matchFilter(packet)) packetFilteredList.add(0, packet); notifyAdapter(); } }); } else if (type.getCode() == 2) { runOnUiThread(new Runnable() { @Override public void run() { Packet packet = new PacketHciAclData(frameCount++, timestamp, finalDest, type, hciFrame, snoopFrame); packetList.add(0, packet); if (isFiltered && matchFilter(packet)) packetFilteredList.add(0, packet); notifyAdapter(); } }); } else if (type.getCode() == 3) { runOnUiThread(new Runnable() { @Override public void run() { Packet packet = new PacketHciScoData(frameCount++, timestamp, finalDest, type, hciFrame, snoopFrame); packetList.add(0, packet); if (isFiltered && matchFilter(packet)) packetFilteredList.add(0, packet); notifyAdapter(); } }); } } catch (JSONException e) { e.printStackTrace(); } } /** * Edit frame count text view and refresh adapter */ private void notifyAdapter() { runOnUiThread(new Runnable() { @Override public void run() { MenuItem item = toolbar.getMenu().findItem(R.id.packet_number_entry); if (item != null) { item.setTitle(packetAdapter.getItemCount() + "/" + mPacketCount); } packetAdapter.notifyDataSetChanged(); } }); } @Override public void onRefresh() { } @Override public void setMaxPacketValue(int maxPacketValue) { mMaxPacketCount = maxPacketValue; } }