diff --git a/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/NotificationFilterActivity.java b/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/NotificationFilterActivity.java index 25607694..15ee8933 100644 --- a/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/NotificationFilterActivity.java +++ b/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/NotificationFilterActivity.java @@ -1,235 +1,235 @@ /* * Copyright 2015 Vineet Garg * * 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 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . */ package org.kde.kdeconnect.Plugins.NotificationsPlugin; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.BaseAdapter; import android.widget.CheckBox; import android.widget.CheckedTextView; import android.widget.ListView; import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.UserInterface.ThemeUtil; import org.kde.kdeconnect_tp.R; import java.util.Arrays; import java.util.List; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; //TODO: Turn this into a PluginSettingsFragment public class NotificationFilterActivity extends AppCompatActivity { private AppDatabase appDatabase; private ListView listView; static class AppListInfo { String pkg; String name; Drawable icon; boolean isEnabled; } private AppListInfo[] apps; class AppListAdapter extends BaseAdapter { @Override public int getCount() { return apps.length + 1; } @Override public AppListInfo getItem(int position) { return apps[position - 1]; } @Override public long getItemId(int position) { return position - 1; } public View getView(int position, View view, ViewGroup parent) { if (view == null) { LayoutInflater inflater = getLayoutInflater(); view = inflater.inflate(android.R.layout.simple_list_item_multiple_choice, null, true); } CheckedTextView checkedTextView = (CheckedTextView) view; if (position == 0) { checkedTextView.setText(R.string.all); checkedTextView.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null); } else { checkedTextView.setText(apps[position - 1].name); checkedTextView.setCompoundDrawablesWithIntrinsicBounds(apps[position - 1].icon, null, null, null); checkedTextView.setCompoundDrawablePadding((int) (8 * getResources().getDisplayMetrics().density)); } return view; } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ThemeUtil.setUserPreferredTheme(this); setContentView(R.layout.activity_notification_filter); appDatabase = new AppDatabase(NotificationFilterActivity.this, false); new Thread(() -> { PackageManager packageManager = getPackageManager(); List appList = packageManager.getInstalledApplications(0); int count = appList.size(); apps = new AppListInfo[count]; for (int i = 0; i < count; i++) { ApplicationInfo appInfo = appList.get(i); apps[i] = new AppListInfo(); apps[i].pkg = appInfo.packageName; apps[i].name = appInfo.loadLabel(packageManager).toString(); apps[i].icon = resizeIcon(appInfo.loadIcon(packageManager), 48); apps[i].isEnabled = appDatabase.isEnabled(appInfo.packageName); } Arrays.sort(apps, (lhs, rhs) -> lhs.name.compareToIgnoreCase(rhs.name)); runOnUiThread(this::displayAppList); }).start(); } private void displayAppList() { listView = findViewById(R.id.lvFilterApps); AppListAdapter adapter = new AppListAdapter(); listView.setAdapter(adapter); listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); listView.setLongClickable(true); listView.setOnItemClickListener((adapterView, view, i, l) -> { if (i == 0) { boolean enabled = listView.isItemChecked(0); for (int j = 0; j < apps.length; j++) { listView.setItemChecked(j, enabled); } appDatabase.setAllEnabled(enabled); } else { boolean checked = listView.isItemChecked(i); appDatabase.setEnabled(apps[i - 1].pkg, checked); apps[i - 1].isEnabled = checked; } }); listView.setOnItemLongClickListener((adapterView, view, i, l) -> { if(i == 0) return true; Context context = this; AlertDialog.Builder builder = new AlertDialog.Builder(context); View mView = getLayoutInflater().inflate(R.layout.popup_notificationsfilter, null); builder.setMessage(context.getResources().getString(R.string.extra_options)); ListView lv = mView.findViewById(R.id.extra_options_list); final String[] options = new String[] { context.getResources().getString(R.string.privacy_options) }; ArrayAdapter extra_options_adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, options); lv.setAdapter(extra_options_adapter); builder.setView(mView); AlertDialog ad = builder.create(); lv.setOnItemClickListener((new_adapterView, new_view, new_i, new_l) -> { switch (new_i){ case 0: AlertDialog.Builder myBuilder = new AlertDialog.Builder(context); String packageName = apps[i - 1].pkg; View myView = getLayoutInflater().inflate(R.layout.privacy_options, null); CheckBox checkbox_contents = myView.findViewById(R.id.checkbox_contents); checkbox_contents.setChecked(appDatabase.getPrivacy(packageName, AppDatabase.PrivacyOptions.BLOCK_CONTENTS)); checkbox_contents.setText(context.getResources().getString(R.string.block_contents)); CheckBox checkbox_images = myView.findViewById(R.id.checkbox_images); checkbox_images.setChecked(appDatabase.getPrivacy(packageName, AppDatabase.PrivacyOptions.BLOCK_IMAGES)); checkbox_images.setText(context.getResources().getString(R.string.block_images)); myBuilder.setView(myView); myBuilder.setTitle(context.getResources().getString(R.string.privacy_options)); myBuilder.setPositiveButton(context.getResources().getString(R.string.ok), (dialog, id) -> dialog.dismiss()); myBuilder.setMessage(context.getResources().getString(R.string.set_privacy_options)); checkbox_contents.setOnCheckedChangeListener((compoundButton, b) -> appDatabase.setPrivacy(packageName, AppDatabase.PrivacyOptions.BLOCK_CONTENTS, compoundButton.isChecked())); checkbox_images.setOnCheckedChangeListener((compoundButton, b) -> appDatabase.setPrivacy(packageName, AppDatabase.PrivacyOptions.BLOCK_IMAGES, compoundButton.isChecked())); ad.cancel(); - myBuilder.create().show(); + myBuilder.show(); break; } }); ad.show(); return true; }); listView.setItemChecked(0, appDatabase.getAllEnabled()); //"Select all" button for (int i = 0; i < apps.length; i++) { listView.setItemChecked(i + 1, apps[i].isEnabled); } listView.setVisibility(View.VISIBLE); findViewById(R.id.spinner).setVisibility(View.GONE); } private Drawable resizeIcon(Drawable icon, int maxSize) { Resources res = getResources(); //Convert to display pixels maxSize = (int) (maxSize * res.getDisplayMetrics().density); Bitmap bitmap = Bitmap.createBitmap(maxSize, maxSize, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); icon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); icon.draw(canvas); return new BitmapDrawable(res, bitmap); } } diff --git a/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandActivity.java b/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandActivity.java index 8e3d925b..9a26d458 100644 --- a/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandActivity.java +++ b/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandActivity.java @@ -1,167 +1,165 @@ /* * Copyright 2015 Aleix Pol Gonzalez * Copyright 2015 Albert Vaca Cintora * * 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 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . */ package org.kde.kdeconnect.Plugins.RunCommandPlugin; import android.content.ClipboardManager; import android.content.Context; import android.os.Build; import android.os.Bundle; import android.util.Log; import android.view.ContextMenu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import com.google.android.material.floatingactionbutton.FloatingActionButton; import org.json.JSONException; import org.json.JSONObject; import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.UserInterface.List.ListAdapter; import org.kde.kdeconnect.UserInterface.ThemeUtil; import org.kde.kdeconnect_tp.R; import java.util.ArrayList; import java.util.Collections; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; public class RunCommandActivity extends AppCompatActivity { private String deviceId; private final RunCommandPlugin.CommandsChangedCallback commandsChangedCallback = this::updateView; private ArrayList commandItems; private void updateView() { BackgroundService.RunWithPlugin(this, deviceId, RunCommandPlugin.class, plugin -> runOnUiThread(() -> { ListView view = findViewById(R.id.runcommandslist); registerForContextMenu(view); commandItems = new ArrayList<>(); for (JSONObject obj : plugin.getCommandList()) { try { commandItems.add(new CommandEntry(obj.getString("name"), obj.getString("command"), obj.getString("key"))); } catch (JSONException e) { Log.e("RunCommand", "Error parsing JSON", e); } } Collections.sort(commandItems, (lhs, rhs) -> { String lName = ((CommandEntry) lhs).getName(); String rName = ((CommandEntry) rhs).getName(); return lName.compareTo(rName); }); ListAdapter adapter = new ListAdapter(RunCommandActivity.this, commandItems); view.setAdapter(adapter); view.setOnItemClickListener((adapterView, view1, i, l) -> { CommandEntry entry = (CommandEntry) commandItems.get(i); plugin.runCommand(entry.getKey()); }); TextView explanation = findViewById(R.id.addcomand_explanation); String text = getString(R.string.addcommand_explanation); if (!plugin.canAddCommand()) { text += "\n" + getString(R.string.addcommand_explanation2); } explanation.setText(text); explanation.setVisibility(commandItems.isEmpty() ? View.VISIBLE : View.GONE); })); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ThemeUtil.setUserPreferredTheme(this); setContentView(R.layout.activity_runcommand); deviceId = getIntent().getStringExtra("deviceId"); boolean canAddCommands = BackgroundService.getInstance().getDevice(deviceId).getPlugin(RunCommandPlugin.class).canAddCommand(); FloatingActionButton addCommandButton = findViewById(R.id.add_command_button); if (canAddCommands) { addCommandButton.show(); } else { addCommandButton.hide(); } addCommandButton.setOnClickListener(v -> BackgroundService.RunWithPlugin(RunCommandActivity.this, deviceId, RunCommandPlugin.class, plugin -> { plugin.sendSetupPacket(); - AlertDialog dialog = new AlertDialog.Builder(RunCommandActivity.this) + new AlertDialog.Builder(RunCommandActivity.this) .setTitle(R.string.add_command) .setMessage(R.string.add_command_description) .setPositiveButton(R.string.ok, null) - .create(); - dialog.show(); + .show(); })); - updateView(); } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.runcommand_context, menu); } @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB) @Override public boolean onContextItemSelected(MenuItem item) { AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); if (item.getItemId() == R.id.copy_url_to_clipboard) { CommandEntry entry = (CommandEntry) commandItems.get(info.position); String url = "kdeconnect://runcommand/" + deviceId + "/" + entry.getKey(); ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); cm.setText(url); Toast toast = Toast.makeText(this, R.string.clipboard_toast, Toast.LENGTH_SHORT); toast.show(); } return false; } @Override protected void onResume() { super.onResume(); BackgroundService.RunWithPlugin(this, deviceId, RunCommandPlugin.class, plugin -> plugin.addCommandsUpdatedCallback(commandsChangedCallback)); } @Override protected void onPause() { super.onPause(); BackgroundService.RunWithPlugin(this, deviceId, RunCommandPlugin.class, plugin -> plugin.removeCommandsUpdatedCallback(commandsChangedCallback)); } } diff --git a/src/org/kde/kdeconnect/UserInterface/DeviceFragment.java b/src/org/kde/kdeconnect/UserInterface/DeviceFragment.java index 2f772b3b..f6a39514 100644 --- a/src/org/kde/kdeconnect/UserInterface/DeviceFragment.java +++ b/src/org/kde/kdeconnect/UserInterface/DeviceFragment.java @@ -1,409 +1,409 @@ /* * Copyright 2014 Albert Vaca Cintora * * 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 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . */ package org.kde.kdeconnect.UserInterface; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.Fragment; import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.Device; import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper; import org.kde.kdeconnect.Plugins.Plugin; import org.kde.kdeconnect.UserInterface.List.PluginListHeaderItem; import org.kde.kdeconnect.UserInterface.List.FailedPluginListItem; import org.kde.kdeconnect.UserInterface.List.ListAdapter; import org.kde.kdeconnect.UserInterface.List.PluginItem; import org.kde.kdeconnect_tp.R; import java.util.ArrayList; import java.util.Collection; import java.util.ConcurrentModificationException; import java.util.concurrent.ConcurrentHashMap; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; import butterknife.Unbinder; /** * Main view. Displays the current device and its plugins */ public class DeviceFragment extends Fragment { private static final String ARG_DEVICE_ID = "deviceId"; private static final String ARG_FROM_DEVICE_LIST = "fromDeviceList"; private static final String TAG = "KDE/DeviceFragment"; private View rootView; private String mDeviceId; private Device device; private MainActivity mActivity; private ArrayList pluginListItems; @BindView(R.id.pair_button) Button pairButton; @BindView(R.id.accept_button) Button acceptButton; @BindView(R.id.reject_button) Button rejectButton; @BindView(R.id.pair_message) TextView pairMessage; @BindView(R.id.pair_progress) ProgressBar pairProgress; @BindView(R.id.pairing_buttons) View pairingButtons; @BindView(R.id.pair_request_buttons) View pairRequestButtons; @BindView(R.id.error_message_container) View errorMessageContainer; @BindView(R.id.not_reachable_message) TextView notReachableMessage; @BindView(R.id.buttons_list) ListView buttonsList; private Unbinder unbinder; public DeviceFragment() { } public static DeviceFragment newInstance(String deviceId, boolean fromDeviceList) { DeviceFragment frag = new DeviceFragment(); Bundle args = new Bundle(); args.putString(ARG_DEVICE_ID, deviceId); args.putBoolean(ARG_FROM_DEVICE_LIST, fromDeviceList); frag.setArguments(args); return frag; } @Override public void onAttach(Context context) { super.onAttach(context); mActivity = ((MainActivity) getActivity()); } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments() == null || !getArguments().containsKey(ARG_DEVICE_ID)) { throw new RuntimeException("You must instantiate a new DeviceFragment using DeviceFragment.newInstance()"); } mDeviceId = getArguments().getString(ARG_DEVICE_ID); } @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { rootView = inflater.inflate(R.layout.activity_device, container, false); unbinder = ButterKnife.bind(this, rootView); setHasOptionsMenu(true); BackgroundService.RunCommand(mActivity, service -> { device = service.getDevice(mDeviceId); if (device == null) { Log.e(TAG, "Trying to display a device fragment but the device is not present"); mActivity.onDeviceSelected(null); return; } mActivity.getSupportActionBar().setTitle(device.getName()); device.addPairingCallback(pairingCallback); device.addPluginsChangedListener(pluginsChangedListener); refreshUI(); }); return rootView; } String getDeviceId() { return mDeviceId; } private final Device.PluginsChangedListener pluginsChangedListener = device -> refreshUI(); @OnClick(R.id.pair_button) void pairButtonClicked(Button pairButton) { pairButton.setVisibility(View.GONE); pairMessage.setText(""); pairProgress.setVisibility(View.VISIBLE); BackgroundService.RunCommand(mActivity, service -> { device = service.getDevice(mDeviceId); if (device == null) return; device.requestPairing(); }); } @OnClick(R.id.accept_button) void acceptButtonClicked() { if (device != null) { device.acceptPairing(); pairingButtons.setVisibility(View.GONE); } } @OnClick(R.id.reject_button) void setRejectButtonClicked() { if (device != null) { //Remove listener so buttons don't show for a while before changing the view device.removePluginsChangedListener(pluginsChangedListener); device.removePairingCallback(pairingCallback); device.rejectPairing(); } mActivity.onDeviceSelected(null); } @Override public void onDestroyView() { BackgroundService.RunCommand(mActivity, service -> { Device device = service.getDevice(mDeviceId); if (device == null) return; device.removePluginsChangedListener(pluginsChangedListener); device.removePairingCallback(pairingCallback); }); unbinder.unbind(); rootView = null; super.onDestroyView(); } @Override public void onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); menu.clear(); if (device == null) { return; } //Plugins button list final Collection plugins = device.getLoadedPlugins().values(); for (final Plugin p : plugins) { if (!p.displayInContextMenu()) { continue; } menu.add(p.getActionName()).setOnMenuItemClickListener(item -> { p.startMainActivity(mActivity); return true; }); } menu.add(R.string.device_menu_plugins).setOnMenuItemClickListener(menuItem -> { Intent intent = new Intent(mActivity, PluginSettingsActivity.class); intent.putExtra("deviceId", mDeviceId); startActivity(intent); return true; }); if (device.isReachable()) { menu.add(R.string.encryption_info_title).setOnMenuItemClickListener(menuItem -> { Context context = mActivity; AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(context.getResources().getString(R.string.encryption_info_title)); builder.setPositiveButton(context.getResources().getString(R.string.ok), (dialog, id) -> dialog.dismiss()); if (device.certificate == null) { builder.setMessage(R.string.encryption_info_msg_no_ssl); } else { builder.setMessage(context.getResources().getString(R.string.my_device_fingerprint) + "\n" + SslHelper.getCertificateHash(SslHelper.certificate) + "\n\n" + context.getResources().getString(R.string.remote_device_fingerprint) + "\n" + SslHelper.getCertificateHash(device.certificate)); } - builder.create().show(); + builder.show(); return true; }); } if (device.isPaired()) { menu.add(R.string.device_menu_unpair).setOnMenuItemClickListener(menuItem -> { //Remove listener so buttons don't show for a while before changing the view device.removePluginsChangedListener(pluginsChangedListener); device.removePairingCallback(pairingCallback); device.unpair(); mActivity.onDeviceSelected(null); return true; }); } } @Override public void onResume() { super.onResume(); getView().setFocusableInTouchMode(true); getView().requestFocus(); getView().setOnKeyListener((v, keyCode, event) -> { if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { boolean fromDeviceList = getArguments().getBoolean(ARG_FROM_DEVICE_LIST, false); // Handle back button so we go to the list of devices in case we came from there if (fromDeviceList) { mActivity.onDeviceSelected(null); return true; } } return false; }); } private void refreshUI() { if (device == null || rootView == null) { return; } //Once in-app, there is no point in keep displaying the notification if any device.hidePairingNotification(); mActivity.runOnUiThread(new Runnable() { @Override public void run() { if (device.isPairRequestedByPeer()) { pairMessage.setText(R.string.pair_requested); pairingButtons.setVisibility(View.VISIBLE); pairProgress.setVisibility(View.GONE); pairButton.setVisibility(View.GONE); pairRequestButtons.setVisibility(View.VISIBLE); } else { boolean paired = device.isPaired(); boolean reachable = device.isReachable(); pairingButtons.setVisibility(paired ? View.GONE : View.VISIBLE); errorMessageContainer.setVisibility((paired && !reachable) ? View.VISIBLE : View.GONE); notReachableMessage.setVisibility((paired && !reachable) ? View.VISIBLE : View.GONE); try { pluginListItems = new ArrayList<>(); if (paired && reachable) { //Plugins button list final Collection plugins = device.getLoadedPlugins().values(); for (final Plugin p : plugins) { if (!p.hasMainActivity()) continue; if (p.displayInContextMenu()) continue; pluginListItems.add(new PluginItem(p, v -> p.startMainActivity(mActivity))); } DeviceFragment.this.createPluginsList(device.getPluginsWithoutPermissions(), R.string.plugins_need_permission, (plugin) -> { AlertDialogFragment dialog = plugin.getPermissionExplanationDialog(MainActivity.RESULT_NEEDS_RELOAD); if (dialog != null) { dialog.show(getChildFragmentManager(), null); } }); DeviceFragment.this.createPluginsList(device.getPluginsWithoutOptionalPermissions(), R.string.plugins_need_optional_permission, (plugin) -> { AlertDialogFragment dialog = plugin.getOptionalPermissionExplanationDialog(MainActivity.RESULT_NEEDS_RELOAD); if (dialog != null) { dialog.show(getChildFragmentManager(), null); } }); } ListAdapter adapter = new ListAdapter(mActivity, pluginListItems); buttonsList.setAdapter(adapter); mActivity.invalidateOptionsMenu(); } catch (IllegalStateException e) { //Ignore: The activity was closed while we were trying to update it } catch (ConcurrentModificationException e) { Log.e(TAG, "ConcurrentModificationException"); this.run(); //Try again } } } }); } private final Device.PairingCallback pairingCallback = new Device.PairingCallback() { @Override public void incomingRequest() { refreshUI(); } @Override public void pairingSuccessful() { refreshUI(); } @Override public void pairingFailed(final String error) { mActivity.runOnUiThread(() -> { if (rootView == null) return; pairMessage.setText(error); pairProgress.setVisibility(View.GONE); pairButton.setVisibility(View.VISIBLE); pairRequestButtons.setVisibility(View.GONE); refreshUI(); }); } @Override public void unpaired() { mActivity.runOnUiThread(() -> { if (rootView == null) return; pairMessage.setText(R.string.device_not_paired); pairProgress.setVisibility(View.GONE); pairButton.setVisibility(View.VISIBLE); pairRequestButtons.setVisibility(View.GONE); refreshUI(); }); } }; private void createPluginsList(ConcurrentHashMap plugins, int headerText, FailedPluginListItem.Action action) { if (plugins.isEmpty()) return; pluginListItems.add(new PluginListHeaderItem(headerText)); for (Plugin plugin : plugins.values()) { if (!device.isPluginEnabled(plugin.getPluginKey())) { continue; } pluginListItems.add(new FailedPluginListItem(plugin, action)); } } }