diff --git a/src/org/kde/kdeconnect/UserInterface/MainActivity.java b/src/org/kde/kdeconnect/UserInterface/MainActivity.java new file mode 100644 index 00000000..82ea3fd8 --- /dev/null +++ b/src/org/kde/kdeconnect/UserInterface/MainActivity.java @@ -0,0 +1,429 @@ +package org.kde.kdeconnect.UserInterface; + +import android.Manifest; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.SubMenu; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.ActionBarDrawerToggle; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.view.GravityCompat; +import androidx.drawerlayout.widget.DrawerLayout; +import androidx.fragment.app.Fragment; +import androidx.preference.PreferenceManager; + +import com.google.android.material.navigation.NavigationView; + +import org.apache.commons.lang3.ArrayUtils; +import org.kde.kdeconnect.BackgroundService; +import org.kde.kdeconnect.Device; +import org.kde.kdeconnect.Helpers.DeviceHelper; +import org.kde.kdeconnect.Plugins.SharePlugin.ShareSettingsFragment; +import org.kde.kdeconnect.UserInterface.About.AboutFragment; +import org.kde.kdeconnect.UserInterface.About.ApplicationAboutDataKt; +import org.kde.kdeconnect_tp.R; +import org.kde.kdeconnect_tp.databinding.ActivityMainBinding; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Objects; + +public class MainActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener { + + private static final int MENU_ENTRY_ADD_DEVICE = 1; //0 means no-selection + private static final int MENU_ENTRY_SETTINGS = 2; + private static final int MENU_ENTRY_ABOUT = 3; + private static final int MENU_ENTRY_DEVICE_FIRST_ID = 1000; //All subsequent ids are devices in the menu + private static final int MENU_ENTRY_DEVICE_UNKNOWN = 9999; //It's still a device, but we don't know which one yet + private static final int STORAGE_LOCATION_CONFIGURED = 2020; + + private static final String STATE_SELECTED_MENU_ENTRY = "selected_entry"; //Saved only in onSaveInstanceState + private static final String STATE_SELECTED_DEVICE = "selected_device"; //Saved persistently in preferences + + public static final int RESULT_NEEDS_RELOAD = Activity.RESULT_FIRST_USER; + + public static final String PAIR_REQUEST_STATUS = "pair_req_status"; + public static final String PAIRING_ACCEPTED = "accepted"; + public static final String PAIRING_REJECTED = "rejected"; + public static final String PAIRING_PENDING = "pending"; + + public static final String EXTRA_DEVICE_ID = "deviceId"; + public static final String FLAG_FORCE_OVERVIEW = "forceOverview"; + + private NavigationView mNavigationView; + private DrawerLayout mDrawerLayout; + private TextView mNavViewDeviceName; + + private String mCurrentDevice; + private int mCurrentMenuEntry; + + private SharedPreferences preferences; + + private final HashMap mMapMenuToDeviceId = new HashMap<>(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + DeviceHelper.initializeDeviceId(this); + + final ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + mNavigationView = binding.navigationDrawer; + mDrawerLayout = binding.drawerLayout; + + View mDrawerHeader = mNavigationView.getHeaderView(0); + mNavViewDeviceName = mDrawerHeader.findViewById(R.id.device_name); + ImageView mNavViewDeviceType = mDrawerHeader.findViewById(R.id.device_type); + + setSupportActionBar(binding.toolbarLayout.toolbar); + + ActionBar actionBar = getSupportActionBar(); + + if (mDrawerLayout != null) { + ActionBarDrawerToggle mDrawerToggle = new ActionBarDrawerToggle(this, /* host Activity */ + mDrawerLayout, /* DrawerLayout object */ + R.string.open, /* "open drawer" description */ + R.string.close /* "close drawer" description */ + ); + + mDrawerLayout.addDrawerListener(mDrawerToggle); + mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); + + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + } + + mDrawerToggle.setDrawerIndicatorEnabled(true); + mDrawerToggle.syncState(); + } + + preferences = getSharedPreferences("stored_menu_selection", Context.MODE_PRIVATE); + + // Note: The preference changed listener should be registered before getting the name, because getting + // it can trigger a background fetch from the internet that will eventually update the preference + PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this); + String deviceName = DeviceHelper.getDeviceName(this); + mNavViewDeviceType.setImageDrawable(DeviceHelper.getDeviceType(this).getIcon(this)); + mNavViewDeviceName.setText(deviceName); + + mNavigationView.setNavigationItemSelectedListener(menuItem -> { + mCurrentMenuEntry = menuItem.getItemId(); + switch (mCurrentMenuEntry) { + case MENU_ENTRY_ADD_DEVICE: + mCurrentDevice = null; + preferences.edit().putString(STATE_SELECTED_DEVICE, null).apply(); + setContentFragment(new PairingFragment()); + break; + case MENU_ENTRY_SETTINGS: + mCurrentDevice = null; + preferences.edit().putString(STATE_SELECTED_DEVICE, null).apply(); + setContentFragment(new SettingsFragment()); + break; + case MENU_ENTRY_ABOUT: + mCurrentDevice = null; + preferences.edit().putString(STATE_SELECTED_DEVICE, null).apply(); + setContentFragment(AboutFragment.newInstance(Objects.requireNonNull(ApplicationAboutDataKt.getApplicationAboutData(this)))); + break; + default: + String deviceId = mMapMenuToDeviceId.get(menuItem); + onDeviceSelected(deviceId); + break; + } + + if (mDrawerLayout != null) { + mDrawerLayout.closeDrawer(mNavigationView); + } + return true; + }); + + // Decide which menu entry should be selected at start + String savedDevice; + int savedMenuEntry; + if (getIntent().hasExtra(FLAG_FORCE_OVERVIEW)) { + Log.i("MainActivity", "Requested to start main overview"); + savedDevice = null; + savedMenuEntry = MENU_ENTRY_ADD_DEVICE; + } else if (getIntent().hasExtra(EXTRA_DEVICE_ID)) { + Log.i("MainActivity", "Loading selected device from parameter"); + savedDevice = getIntent().getStringExtra(EXTRA_DEVICE_ID); + savedMenuEntry = MENU_ENTRY_DEVICE_UNKNOWN; + // If pairStatus is not empty, then the user has accepted/reject the pairing from the notification + String pairStatus = getIntent().getStringExtra(PAIR_REQUEST_STATUS); + if (pairStatus != null) { + Log.i("MainActivity", "pair status is " + pairStatus); + savedDevice = onPairResultFromNotification(savedDevice, pairStatus); + if (savedDevice == null) { + savedMenuEntry = MENU_ENTRY_ADD_DEVICE; + } + } + } else if (savedInstanceState != null) { + Log.i("MainActivity", "Loading selected device from saved activity state"); + savedDevice = savedInstanceState.getString(STATE_SELECTED_DEVICE); + savedMenuEntry = savedInstanceState.getInt(STATE_SELECTED_MENU_ENTRY, MENU_ENTRY_ADD_DEVICE); + } else { + Log.i("MainActivity", "Loading selected device from persistent storage"); + savedDevice = preferences.getString(STATE_SELECTED_DEVICE, null); + savedMenuEntry = (savedDevice != null)? MENU_ENTRY_DEVICE_UNKNOWN : MENU_ENTRY_ADD_DEVICE; + } + + mCurrentMenuEntry = savedMenuEntry; + mCurrentDevice = savedDevice; + mNavigationView.setCheckedItem(savedMenuEntry); + + //FragmentManager will restore whatever fragment was there + if (savedInstanceState != null) { + Fragment frag = getSupportFragmentManager().findFragmentById(R.id.container); + if (!(frag instanceof DeviceFragment) || ((DeviceFragment)frag).getDeviceId().equals(savedDevice)) { + return; + } + } + + // Activate the chosen fragment and select the entry in the menu + if (savedMenuEntry >= MENU_ENTRY_DEVICE_FIRST_ID && savedDevice != null) { + onDeviceSelected(savedDevice); + } else { + if (mCurrentMenuEntry == MENU_ENTRY_SETTINGS) { + setContentFragment(new SettingsFragment()); + } else if (mCurrentMenuEntry == MENU_ENTRY_ABOUT) { + setContentFragment(AboutFragment.newInstance(Objects.requireNonNull(ApplicationAboutDataKt.getApplicationAboutData(this)))); + } else { + setContentFragment(new PairingFragment()); + } + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(this); + } + + private String onPairResultFromNotification(String deviceId, String pairStatus) { + assert(deviceId != null); + + if (!pairStatus.equals(PAIRING_PENDING)) { + BackgroundService.RunCommand(this, service -> { + Device device = service.getDevice(deviceId); + if (device == null) { + Log.w("rejectPairing", "Device no longer exists: " + deviceId); + return; + } + + if (pairStatus.equals(PAIRING_ACCEPTED)) { + device.acceptPairing(); + } else if (pairStatus.equals(PAIRING_REJECTED)) { + device.rejectPairing(); + } + }); + } + + if (pairStatus.equals(PAIRING_ACCEPTED) || pairStatus.equals(PAIRING_PENDING)) { + return deviceId; + } else { + return null; + } + } + + private int deviceIdToMenuEntryId(String deviceId) { + for (HashMap.Entry entry : mMapMenuToDeviceId.entrySet()) { + if (TextUtils.equals(entry.getValue(), deviceId)) { //null-safe + return entry.getKey().getItemId(); + } + } + return MENU_ENTRY_DEVICE_UNKNOWN; + } + + @Override + public void onBackPressed() { + if (mDrawerLayout != null && mDrawerLayout.isDrawerOpen(mNavigationView)) { + mDrawerLayout.closeDrawer(mNavigationView); + } else if (mCurrentMenuEntry == MENU_ENTRY_SETTINGS || mCurrentMenuEntry == MENU_ENTRY_ABOUT) { + mCurrentMenuEntry = MENU_ENTRY_ADD_DEVICE; + mNavigationView.setCheckedItem(MENU_ENTRY_ADD_DEVICE); + setContentFragment(new PairingFragment()); + } else { + super.onBackPressed(); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (mDrawerLayout != null && item.getItemId() == android.R.id.home) { + mDrawerLayout.openDrawer(mNavigationView); + return true; + } else { + return super.onOptionsItemSelected(item); + } + } + + private void updateDeviceList() { + BackgroundService.RunCommand(MainActivity.this, service -> { + + Menu menu = mNavigationView.getMenu(); + menu.clear(); + mMapMenuToDeviceId.clear(); + + SubMenu devicesMenu = menu.addSubMenu(R.string.devices); + + int id = MENU_ENTRY_DEVICE_FIRST_ID; + Collection devices = service.getDevices().values(); + for (Device device : devices) { + if (device.isReachable() && device.isPaired()) { + MenuItem item = devicesMenu.add(Menu.FIRST, id++, 1, device.getName()); + item.setIcon(device.getIcon()); + item.setCheckable(true); + mMapMenuToDeviceId.put(item, device.getDeviceId()); + } + } + + MenuItem addDeviceItem = devicesMenu.add(Menu.FIRST, MENU_ENTRY_ADD_DEVICE, 1000, R.string.pair_new_device); + addDeviceItem.setIcon(R.drawable.ic_action_content_add_circle_outline_32dp); + addDeviceItem.setCheckable(true); + + MenuItem settingsItem = menu.add(Menu.FIRST, MENU_ENTRY_SETTINGS, 1000, R.string.settings); + settingsItem.setIcon(R.drawable.ic_settings_white_32dp); + settingsItem.setCheckable(true); + + MenuItem aboutItem = menu.add(Menu.FIRST, MENU_ENTRY_ABOUT, 1000, R.string.about); + aboutItem.setIcon(R.drawable.ic_baseline_info_24); + aboutItem.setCheckable(true); + + //Ids might have changed + if (mCurrentMenuEntry >= MENU_ENTRY_DEVICE_FIRST_ID) { + mCurrentMenuEntry = deviceIdToMenuEntryId(mCurrentDevice); + } + mNavigationView.setCheckedItem(mCurrentMenuEntry); + }); + } + + + @Override + protected void onStart() { + super.onStart(); + BackgroundService.RunCommand(this, service -> { + service.onNetworkChange(); + service.addDeviceListChangedCallback("MainActivity", unused -> updateDeviceList()); + }); + updateDeviceList(); + } + + @Override + protected void onStop() { + BackgroundService.RunCommand(this, service -> service.removeDeviceListChangedCallback("MainActivity")); + super.onStop(); + } + + private static void uncheckAllMenuItems(Menu menu) { + int size = menu.size(); + for (int i = 0; i < size; i++) { + MenuItem item = menu.getItem(i); + if(item.hasSubMenu()) { + uncheckAllMenuItems(item.getSubMenu()); + } else { + item.setChecked(false); + } + } + } + + public void onDeviceSelected(String deviceId, boolean fromDeviceList) { + mCurrentDevice = deviceId; + preferences.edit().putString(STATE_SELECTED_DEVICE, deviceId).apply(); + + if (mCurrentDevice != null) { + mCurrentMenuEntry = deviceIdToMenuEntryId(deviceId); + if (mCurrentMenuEntry == MENU_ENTRY_DEVICE_UNKNOWN) { + uncheckAllMenuItems(mNavigationView.getMenu()); + } else { + mNavigationView.setCheckedItem(mCurrentMenuEntry); + } + setContentFragment(DeviceFragment.Companion.newInstance(deviceId, fromDeviceList)); + } else { + mCurrentMenuEntry = MENU_ENTRY_ADD_DEVICE; + mNavigationView.setCheckedItem(mCurrentMenuEntry); + setContentFragment(new PairingFragment()); + } + } + + private void setContentFragment(Fragment fragment) { + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.container, fragment) + .commit(); + } + + public void onDeviceSelected(String deviceId) { + onDeviceSelected(deviceId, false); + } + + @Override + protected void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString(STATE_SELECTED_DEVICE, mCurrentDevice); + outState.putInt(STATE_SELECTED_MENU_ENTRY, mCurrentMenuEntry); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == RESULT_NEEDS_RELOAD) { + BackgroundService.RunCommand(this, service -> { + Device device = service.getDevice(mCurrentDevice); + device.reloadPluginsFromSettings(); + }); + } else if (requestCode == STORAGE_LOCATION_CONFIGURED && resultCode == RESULT_OK && data != null){ + Uri uri = data.getData(); + ShareSettingsFragment.saveStorageLocationPreference(this, uri); + } else { + super.onActivityResult(requestCode, resultCode, data); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode,permissions,grantResults); + boolean permissionsGranted = ArrayUtils.contains(grantResults, PackageManager.PERMISSION_GRANTED); + if (permissionsGranted) { + int i = ArrayUtils.indexOf(permissions, Manifest.permission.WRITE_EXTERNAL_STORAGE); + boolean writeStoragePermissionGranted = (i != ArrayUtils.INDEX_NOT_FOUND && + grantResults[i] == PackageManager.PERMISSION_GRANTED); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && writeStoragePermissionGranted) { + // To get a writeable path manually on Android 10 and later for Share and Receive Plugin. + // Otherwise Receiving files will keep failing until the user chooses a path manually to receive files. + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); + startActivityForResult(intent, STORAGE_LOCATION_CONFIGURED); + } + + //New permission granted, reload plugins + BackgroundService.RunCommand(this, service -> { + Device device = service.getDevice(mCurrentDevice); + device.reloadPluginsFromSettings(); + }); + } + } + + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (DeviceHelper.KEY_DEVICE_NAME_PREFERENCE.equals(key)) { + mNavViewDeviceName.setText(DeviceHelper.getDeviceName(this)); + BackgroundService.RunCommand(this, BackgroundService::onNetworkChange); //Re-send our identity packet + } + } +} diff --git a/src/org/kde/kdeconnect/UserInterface/MainActivity.kt b/src/org/kde/kdeconnect/UserInterface/MainActivity.kt deleted file mode 100644 index b21ccb06..00000000 --- a/src/org/kde/kdeconnect/UserInterface/MainActivity.kt +++ /dev/null @@ -1,376 +0,0 @@ -package org.kde.kdeconnect.UserInterface - -import android.Manifest -import android.content.Intent -import android.content.SharedPreferences -import android.content.SharedPreferences.OnSharedPreferenceChangeListener -import android.content.pm.PackageManager -import android.os.Build -import android.os.Bundle -import android.util.Log -import android.view.Menu -import android.view.MenuItem -import android.widget.ImageView -import android.widget.TextView -import androidx.appcompat.app.ActionBarDrawerToggle -import androidx.appcompat.app.AppCompatActivity -import androidx.core.view.GravityCompat -import androidx.drawerlayout.widget.DrawerLayout -import androidx.fragment.app.Fragment -import androidx.preference.PreferenceManager -import com.google.android.material.navigation.NavigationView -import org.apache.commons.lang3.ArrayUtils -import org.kde.kdeconnect.BackgroundService -import org.kde.kdeconnect.Device -import org.kde.kdeconnect.Helpers.DeviceHelper -import org.kde.kdeconnect.Plugins.SharePlugin.ShareSettingsFragment -import org.kde.kdeconnect.UserInterface.About.AboutFragment.Companion.newInstance -import org.kde.kdeconnect.UserInterface.About.getApplicationAboutData -import org.kde.kdeconnect.UserInterface.DeviceFragment.Companion.newInstance -import org.kde.kdeconnect_tp.R -import org.kde.kdeconnect_tp.databinding.ActivityMainBinding -import java.util.* - -private const val MENU_ENTRY_ADD_DEVICE = 1 //0 means no-selection -private const val MENU_ENTRY_SETTINGS = 2 -private const val MENU_ENTRY_ABOUT = 3 -private const val MENU_ENTRY_DEVICE_FIRST_ID = 1000 //All subsequent ids are devices in the menu -private const val MENU_ENTRY_DEVICE_UNKNOWN = 9999 //It's still a device, but we don't know which one yet -private const val STORAGE_LOCATION_CONFIGURED = 2020 -private const val STATE_SELECTED_MENU_ENTRY = "selected_entry" //Saved only in onSaveInstanceState -private const val STATE_SELECTED_DEVICE = "selected_device" //Saved persistently in preferences - -class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener { - private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) } - private val mNavigationView: NavigationView by lazy { binding.navigationDrawer } - private val mDrawerLayout: DrawerLayout? by lazy { binding.drawerLayout } - - private lateinit var mNavViewDeviceName: TextView - - private var mCurrentDevice: String? = null - private var mCurrentMenuEntry = 0 - private val preferences: SharedPreferences by lazy { getSharedPreferences("stored_menu_selection", MODE_PRIVATE) } - private val mMapMenuToDeviceId = HashMap() - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - DeviceHelper.initializeDeviceId(this) - - setContentView(binding.root) - - val mDrawerHeader = mNavigationView.getHeaderView(0) - mNavViewDeviceName = mDrawerHeader.findViewById(R.id.device_name) - val mNavViewDeviceType = mDrawerHeader.findViewById(R.id.device_type) - - setSupportActionBar(binding.toolbarLayout.toolbar) - mDrawerLayout?.let { - supportActionBar?.setDisplayHomeAsUpEnabled(true) - val mDrawerToggle = ActionBarDrawerToggle( - this, /* host Activity */ - it, /* DrawerLayout object */ - R.string.open, /* "open drawer" description */ - R.string.close /* "close drawer" description */ - ).apply { - isDrawerIndicatorEnabled = true - syncState() - } - it.addDrawerListener(mDrawerToggle) - it.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START) - } ?: { - supportActionBar?.setDisplayShowHomeEnabled(false) - supportActionBar?.setHomeButtonEnabled(false) - } - - // Note: The preference changed listener should be registered before getting the name, because getting - // it can trigger a background fetch from the internet that will eventually update the preference - PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this) - val deviceName = DeviceHelper.getDeviceName(this) - mNavViewDeviceType?.setImageDrawable(DeviceHelper.getDeviceType(this).getIcon(this)) - mNavViewDeviceName.text = deviceName - mNavigationView.setNavigationItemSelectedListener { menuItem: MenuItem -> - mCurrentMenuEntry = menuItem.itemId - when (mCurrentMenuEntry) { - MENU_ENTRY_ADD_DEVICE -> { - mCurrentDevice = null - preferences.edit().putString(STATE_SELECTED_DEVICE, null).apply() - setContentFragment(PairingFragment()) - } - - MENU_ENTRY_SETTINGS -> { - mCurrentDevice = null - preferences.edit().putString(STATE_SELECTED_DEVICE, null).apply() - setContentFragment(SettingsFragment()) - } - - MENU_ENTRY_ABOUT -> { - mCurrentDevice = null - preferences.edit().putString(STATE_SELECTED_DEVICE, null).apply() - setContentFragment(newInstance(getApplicationAboutData(this))) - } - - else -> { - val deviceId = mMapMenuToDeviceId[menuItem] - onDeviceSelected(deviceId) - } - } - mDrawerLayout?.closeDrawer(mNavigationView) - true - } - - // Decide which menu entry should be selected at start - var savedDevice: String? - var savedMenuEntry: Int - when { - intent.hasExtra(FLAG_FORCE_OVERVIEW) -> { - Log.i(this::class.simpleName, "Requested to start main overview") - savedDevice = null - savedMenuEntry = MENU_ENTRY_ADD_DEVICE - } - - intent.hasExtra(EXTRA_DEVICE_ID) -> { - Log.i(this::class.simpleName, "Loading selected device from parameter") - savedDevice = intent.getStringExtra(EXTRA_DEVICE_ID) - savedMenuEntry = MENU_ENTRY_DEVICE_UNKNOWN - // If pairStatus is not empty, then the user has accepted/reject the pairing from the notification - val pairStatus = intent.getStringExtra(PAIR_REQUEST_STATUS) - if (pairStatus != null) { - Log.i(this::class.simpleName, "Pair status is $pairStatus") - savedDevice = onPairResultFromNotification(savedDevice, pairStatus) - if (savedDevice == null) { - savedMenuEntry = MENU_ENTRY_ADD_DEVICE - } - } - } - - savedInstanceState != null -> { - Log.i(this::class.simpleName, "Loading selected device from saved activity state") - savedDevice = savedInstanceState.getString(STATE_SELECTED_DEVICE) - savedMenuEntry = savedInstanceState.getInt(STATE_SELECTED_MENU_ENTRY, MENU_ENTRY_ADD_DEVICE) - } - - else -> { - Log.i(this::class.simpleName, "Loading selected device from persistent storage") - savedDevice = preferences.getString(STATE_SELECTED_DEVICE, null) - savedMenuEntry = if (savedDevice != null) MENU_ENTRY_DEVICE_UNKNOWN else MENU_ENTRY_ADD_DEVICE - } - } - mCurrentMenuEntry = savedMenuEntry - mCurrentDevice = savedDevice - mNavigationView.setCheckedItem(savedMenuEntry) - - //FragmentManager will restore whatever fragment was there - if (savedInstanceState != null) { - val frag = supportFragmentManager.findFragmentById(R.id.container) - if (frag !is DeviceFragment || frag.deviceId == savedDevice) return - } - - // Activate the chosen fragment and select the entry in the menu - if (savedMenuEntry >= MENU_ENTRY_DEVICE_FIRST_ID && savedDevice != null) { - onDeviceSelected(savedDevice) - } else { - when (mCurrentMenuEntry) { - MENU_ENTRY_SETTINGS -> setContentFragment(SettingsFragment()) - MENU_ENTRY_ABOUT -> setContentFragment(newInstance(Objects.requireNonNull(getApplicationAboutData(this)))) - else -> setContentFragment(PairingFragment()) - } - } - } - - override fun onDestroy() { - super.onDestroy() - PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(this) - } - - private fun onPairResultFromNotification(deviceId: String?, pairStatus: String): String? { - assert(deviceId != null) - if (pairStatus != PAIRING_PENDING) { - BackgroundService.RunCommand(this) { service: BackgroundService -> - val device = service.getDevice(deviceId) - if (device == null) { - Log.w(this::class.simpleName, "Reject pairing - device no longer exists: $deviceId") - return@RunCommand - } - when (pairStatus) { - PAIRING_ACCEPTED -> device.acceptPairing() - PAIRING_REJECTED -> device.rejectPairing() - } - } - } - return if (pairStatus == PAIRING_ACCEPTED || pairStatus == PAIRING_PENDING) deviceId else null - } - - private fun deviceIdToMenuEntryId(deviceId: String?): Int { - for ((key, value) in mMapMenuToDeviceId) { - if (value == deviceId) { - return key.itemId - } - } - return MENU_ENTRY_DEVICE_UNKNOWN - } - - @Deprecated("Deprecated in Java") - override fun onBackPressed() { - mDrawerLayout?.let { - it.closeDrawer(mNavigationView) - return - } - if (mCurrentMenuEntry == MENU_ENTRY_SETTINGS || mCurrentMenuEntry == MENU_ENTRY_ABOUT) { - mCurrentMenuEntry = MENU_ENTRY_ADD_DEVICE - mNavigationView.setCheckedItem(MENU_ENTRY_ADD_DEVICE) - setContentFragment(PairingFragment()) - } else { - super.onBackPressed() - } - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean = - if (item.itemId == android.R.id.home) { - mDrawerLayout?.openDrawer(mNavigationView) - true - } else { - super.onOptionsItemSelected(item) - } - - private fun updateDeviceList() { - BackgroundService.RunCommand(this@MainActivity) { service: BackgroundService -> - val menu = mNavigationView.menu - menu.clear() - mMapMenuToDeviceId.clear() - val devicesMenu = menu.addSubMenu(R.string.devices) - var id = MENU_ENTRY_DEVICE_FIRST_ID - val devices: Collection = service.devices.values - for (device in devices) { - if (device.isReachable && device.isPaired) { - val item = devicesMenu.add(Menu.FIRST, id++, 1, device.name) - item.setIcon(device.icon) - item.setCheckable(true) - mMapMenuToDeviceId[item] = device.deviceId - } - } - val addDeviceItem = devicesMenu.add(Menu.FIRST, MENU_ENTRY_ADD_DEVICE, 1000, R.string.pair_new_device) - addDeviceItem.setIcon(R.drawable.ic_action_content_add_circle_outline_32dp) - addDeviceItem.setCheckable(true) - val settingsItem = menu.add(Menu.FIRST, MENU_ENTRY_SETTINGS, 1000, R.string.settings) - settingsItem.setIcon(R.drawable.ic_settings_white_32dp) - settingsItem.setCheckable(true) - val aboutItem = menu.add(Menu.FIRST, MENU_ENTRY_ABOUT, 1000, R.string.about) - aboutItem.setIcon(R.drawable.ic_baseline_info_24) - aboutItem.setCheckable(true) - - //Ids might have changed - if (mCurrentMenuEntry >= MENU_ENTRY_DEVICE_FIRST_ID) { - mCurrentMenuEntry = deviceIdToMenuEntryId(mCurrentDevice) - } - mNavigationView.setCheckedItem(mCurrentMenuEntry) - } - } - - override fun onStart() { - super.onStart() - BackgroundService.RunCommand(this) { service: BackgroundService -> - service.onNetworkChange() - service.addDeviceListChangedCallback(this::class.simpleName) { updateDeviceList() } - } - updateDeviceList() - } - - override fun onStop() { - BackgroundService.RunCommand(this) { service: BackgroundService -> service.removeDeviceListChangedCallback(this::class.simpleName) } - super.onStop() - } - - @JvmOverloads - fun onDeviceSelected(deviceId: String?, fromDeviceList: Boolean = false) { - mCurrentDevice = deviceId - preferences.edit().putString(STATE_SELECTED_DEVICE, deviceId).apply() - if (mCurrentDevice != null) { - mCurrentMenuEntry = deviceIdToMenuEntryId(deviceId) - if (mCurrentMenuEntry == MENU_ENTRY_DEVICE_UNKNOWN) { - uncheckAllMenuItems(mNavigationView.menu) - } else { - mNavigationView.setCheckedItem(mCurrentMenuEntry) - } - setContentFragment(newInstance(deviceId, fromDeviceList)) - } else { - mCurrentMenuEntry = MENU_ENTRY_ADD_DEVICE - mNavigationView.setCheckedItem(mCurrentMenuEntry) - setContentFragment(PairingFragment()) - } - } - - private fun setContentFragment(fragment: Fragment) { - supportFragmentManager - .beginTransaction() - .replace(R.id.container, fragment) - .commit() - } - - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - outState.putString(STATE_SELECTED_DEVICE, mCurrentDevice) - outState.putInt(STATE_SELECTED_MENU_ENTRY, mCurrentMenuEntry) - } - - @Deprecated("Deprecated in Java") - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - when { - requestCode == RESULT_NEEDS_RELOAD -> BackgroundService.RunCommand(this) { service: BackgroundService -> - val device = service.getDevice(mCurrentDevice) - device.reloadPluginsFromSettings() - } - - requestCode == STORAGE_LOCATION_CONFIGURED && resultCode == RESULT_OK && data != null -> { - val uri = data.data - ShareSettingsFragment.saveStorageLocationPreference(this, uri) - } - - else -> super.onActivityResult(requestCode, resultCode, data) - } - } - - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - val permissionsGranted = ArrayUtils.contains(grantResults, PackageManager.PERMISSION_GRANTED) - if (permissionsGranted) { - val i = ArrayUtils.indexOf(permissions, Manifest.permission.WRITE_EXTERNAL_STORAGE) - val writeStoragePermissionGranted = i != ArrayUtils.INDEX_NOT_FOUND && - grantResults[i] == PackageManager.PERMISSION_GRANTED - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && writeStoragePermissionGranted) { - // To get a writeable path manually on Android 10 and later for Share and Receive Plugin. - // Otherwise, Receiving files will keep failing until the user chooses a path manually to receive files. - val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) - startActivityForResult(intent, STORAGE_LOCATION_CONFIGURED) - } - - //New permission granted, reload plugins - BackgroundService.RunCommand(this) { service: BackgroundService -> - val device = service.getDevice(mCurrentDevice) - device.reloadPluginsFromSettings() - } - } - } - - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { - if (DeviceHelper.KEY_DEVICE_NAME_PREFERENCE == key) { - mNavViewDeviceName.text = DeviceHelper.getDeviceName(this) - BackgroundService.RunCommand(this) { obj: BackgroundService -> obj.onNetworkChange() } //Re-send our identity packet - } - } - - private fun uncheckAllMenuItems(menu: Menu) { - val size = menu.size() - for (i in 0 until size) { - val item = menu.getItem(i) - item.subMenu?.let { uncheckAllMenuItems(it) } ?: item.setChecked(false) - } - } - - companion object { - const val EXTRA_DEVICE_ID = "deviceId" - const val PAIR_REQUEST_STATUS = "pair_req_status" - const val PAIRING_ACCEPTED = "accepted" - const val PAIRING_REJECTED = "rejected" - const val PAIRING_PENDING = "pending" - const val RESULT_NEEDS_RELOAD = RESULT_FIRST_USER - const val FLAG_FORCE_OVERVIEW = "forceOverview" - } -}