diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 79d56a8a..5a5606b6 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -59,7 +59,8 @@ android:networkSecurityConfig="@xml/network_security_config" android:localeConfig="@xml/locales_config" android:theme="@style/KdeConnectTheme.NoActionBar" - android:name="org.kde.kdeconnect.MyApplication"> + android:name="org.kde.kdeconnect.MyApplication" + android:enableOnBackInvokedCallback="true"> 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 new file mode 100644 index 00000000..33ad2867 --- /dev/null +++ b/src/org/kde/kdeconnect/UserInterface/MainActivity.kt @@ -0,0 +1,397 @@ +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.view.View +import android.widget.ImageView +import android.widget.TextView +import androidx.activity.OnBackPressedCallback +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 + +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 set(value) { + field = value + //Enabling "go to default fragment on back" callback when user in settings or "about" fragment + mainFragmentCallback.isEnabled = value == MENU_ENTRY_SETTINGS || value == MENU_ENTRY_ABOUT + } + private val preferences: SharedPreferences by lazy { getSharedPreferences("stored_menu_selection", MODE_PRIVATE) } + private val mMapMenuToDeviceId = HashMap() + + private val closeDrawerCallback = object : OnBackPressedCallback(false) { + override fun handleOnBackPressed() { + mDrawerLayout?.closeDrawer(mNavigationView) + } + } + + private val mainFragmentCallback = object : OnBackPressedCallback(false) { + override fun handleOnBackPressed() { + mCurrentMenuEntry = mCurrentDevice?.let { deviceIdToMenuEntryId(it) } ?: MENU_ENTRY_ADD_DEVICE + mNavigationView.setCheckedItem(mCurrentMenuEntry) + setContentFragment(mCurrentDevice?.let { newInstance(it, false) } ?: PairingFragment()) + } + } + + 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 = DrawerToggle(it).apply { syncState() } + it.addDrawerListener(mDrawerToggle) + it.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START) + } ?: { + closeDrawerCallback.isEnabled = false + 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(getApplicationAboutData(this))) + else -> setContentFragment(PairingFragment()) + } + } + + onBackPressedDispatcher.addCallback(mainFragmentCallback) + onBackPressedDispatcher.addCallback(closeDrawerCallback) + } + + 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 + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return 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" + } + + private inner class DrawerToggle(drawerLayout: DrawerLayout) : ActionBarDrawerToggle( + this, /* host Activity */ + drawerLayout, /* DrawerLayout object */ + R.string.open, /* "open drawer" description */ + R.string.close /* "close drawer" description */ + ) { + override fun onDrawerClosed(drawerView: View) { + super.onDrawerClosed(drawerView) + closeDrawerCallback.isEnabled = false + } + + override fun onDrawerOpened(drawerView: View) { + super.onDrawerOpened(drawerView) + closeDrawerCallback.isEnabled = true + } + } + +}