2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-08-22 01:51:47 +00:00

Migrate from PreferenceActivity to PreferenceFragment because it has been deprecated since Android 3.0 API11

Summary:
PreferenceActivity has been deprecated since Android 3.0 API 11 in favour of PreferenceFragment

| {F6515113} | {F6515114}|
|Before|
| {F6515111} | {F6515112}|
|After|

Test Plan:
Apply patch and observe that the DeviceSettings and PluginSettings screen behave as expected
and now also follow material design guidelines

Reviewers: #kde_connect, nicolasfella

Reviewed By: #kde_connect, nicolasfella

Subscribers: kdeconnect

Tags: #kde_connect

Differential Revision: https://phabricator.kde.org/D17859
This commit is contained in:
Erik Duisters 2019-01-06 12:26:05 +01:00
parent 045e329b53
commit 9fb780f919
21 changed files with 569 additions and 181 deletions

View File

@ -87,22 +87,6 @@
android:name="android.support.PARENT_ACTIVITY"
android:value="org.kde.kdeconnect.UserInterface.MainActivity" />
</activity>
<activity
android:name="org.kde.kdeconnect.UserInterface.PluginSettingsActivity"
android:label="@string/device_menu_plugins"
android:parentActivityName="org.kde.kdeconnect.UserInterface.DeviceSettingsActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.kde.kdeconnect.UserInterface.DeviceSettingsActivity" />
</activity>
<activity
android:name="org.kde.kdeconnect.Plugins.SharePlugin.ShareSettingsActivity"
android:label="@string/device_menu_plugins"
android:parentActivityName="org.kde.kdeconnect.UserInterface.DeviceSettingsActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.kde.kdeconnect.Plugins.SharePlugin.ShareSettingsActivity" />
</activity>
<receiver android:name="org.kde.kdeconnect.KdeConnectBroadcastReceiver">
<intent-filter>
@ -268,10 +252,10 @@
<activity
android:name="org.kde.kdeconnect.Plugins.NotificationsPlugin.NotificationFilterActivity"
android:label="@string/title_activity_notification_filter"
android:parentActivityName="org.kde.kdeconnect.UserInterface.PluginSettingsActivity">
android:parentActivityName="org.kde.kdeconnect.UserInterface.DeviceSettingsActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.kde.kdeconnect.UserInterface.PluginSettingsActivity" />
android:value="org.kde.kdeconnect.UserInterface.DeviceSettingsActivity" />
</activity>
</application>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragmentPlaceHolder"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

View File

@ -34,17 +34,20 @@
android:id="@android:id/widget_frame"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="vertical" />
android:gravity="left|center_vertical|start"
android:minWidth="56dp"
android:orientation="vertical">
</LinearLayout>
<RelativeLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="6dip"
android:layout_marginEnd="4dip"
android:layout_marginLeft="12dip"
android:layout_marginLeft="4dip"
android:layout_marginRight="4dip"
android:layout_marginStart="12dip"
android:layout_marginStart="4dip"
android:layout_marginTop="6dip"
android:layout_weight="1">

View File

@ -325,4 +325,5 @@
<string name="block_images">Block images in notifications</string>
<string name="notification_channel_receivenotification">Notifications from other devices</string>
<string name="findmyphone_preference_key_ringtone" translatable="false">findmyphone_ringtone</string>
</resources>

View File

@ -3,8 +3,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<RingtonePreference
android:key="select_ringtone"
android:showSilent="false"
<Preference
android:key="@string/findmyphone_preference_key_ringtone"
android:title="@string/select_ringtone" />
</PreferenceScreen>

View File

@ -435,5 +435,4 @@ public class BackgroundService extends Service {
cb.run(plugin);
});
}
}

View File

@ -80,7 +80,7 @@ public class FindMyPhoneActivity extends Activity {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
Uri ringtone;
String ringtoneString = prefs.getString("select_ringtone", "");
String ringtoneString = prefs.getString(getString(R.string.findmyphone_preference_key_ringtone), "");
if (ringtoneString.isEmpty()) {
ringtone = Settings.System.DEFAULT_RINGTONE_URI;
} else {

View File

@ -25,6 +25,7 @@ import android.content.Intent;
import org.kde.kdeconnect.Helpers.DeviceHelper;
import org.kde.kdeconnect.NetworkPacket;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.UserInterface.PluginSettingsFragment;
import org.kde.kdeconnect_tp.R;
public class FindMyPhonePlugin extends Plugin {
@ -75,4 +76,8 @@ public class FindMyPhonePlugin extends Plugin {
return true;
}
@Override
public PluginSettingsFragment getSettingsFragment() {
return FindMyPhoneSettingsFragment.newInstance(getPluginKey());
}
}

View File

@ -0,0 +1,113 @@
/*
* Copyright 2018 Erik Duisters <e.duisters1@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.kde.kdeconnect.Plugins.FindMyPhonePlugin;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import org.kde.kdeconnect.UserInterface.PluginSettingsFragment;
import org.kde.kdeconnect_tp.R;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
import androidx.preference.PreferenceManager;
public class FindMyPhoneSettingsFragment extends PluginSettingsFragment {
private static final int REQUEST_CODE_SELECT_RINGTONE = 1000;
private String preferenceKeyRingtone;
private SharedPreferences sharedPreferences;
private Preference ringtonePreference;
public static FindMyPhoneSettingsFragment newInstance(@NonNull String pluginKey) {
FindMyPhoneSettingsFragment fragment = new FindMyPhoneSettingsFragment();
fragment.setArguments(pluginKey);
return fragment;
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
super.onCreatePreferences(savedInstanceState, rootKey);
preferenceKeyRingtone = getString(R.string.findmyphone_preference_key_ringtone);
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext());
ringtonePreference = getPreferenceScreen().findPreference(preferenceKeyRingtone);
setRingtoneSummary();
}
private void setRingtoneSummary() {
String ringtone = sharedPreferences.getString(preferenceKeyRingtone, Settings.System.DEFAULT_RINGTONE_URI.toString());
Uri ringtoneUri = Uri.parse(ringtone);
ringtonePreference.setSummary(RingtoneManager.getRingtone(requireContext(), ringtoneUri).getTitle(requireContext()));
}
@Override
public boolean onPreferenceTreeClick(Preference preference) {
/*
* There is no RingtonePreference in support library nor androidx, this is the workaround proposed here:
* https://issuetracker.google.com/issues/37057453
*/
if (preference.hasKey() && preference.getKey().equals(preferenceKeyRingtone)) {
Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, Settings.System.DEFAULT_NOTIFICATION_URI);
String existingValue = sharedPreferences.getString(preferenceKeyRingtone, null);
if (existingValue != null) {
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, Uri.parse(existingValue));
} else {
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, Settings.System.DEFAULT_RINGTONE_URI);
}
startActivityForResult(intent, REQUEST_CODE_SELECT_RINGTONE);
return true;
}
return super.onPreferenceTreeClick(preference);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_SELECT_RINGTONE && resultCode == Activity.RESULT_OK) {
Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
if (uri != null) {
sharedPreferences.edit()
.putString(preferenceKeyRingtone, uri.toString())
.apply();
setRingtoneSummary();
}
}
}
}

View File

@ -48,6 +48,7 @@ import java.util.List;
import androidx.appcompat.app.AppCompatActivity;
//TODO: Turn this into a PluginSettingsFragment
public class NotificationFilterActivity extends AppCompatActivity {
private AppDatabase appDatabase;

View File

@ -44,8 +44,8 @@ import android.util.Log;
import org.kde.kdeconnect.Helpers.AppsHelper;
import org.kde.kdeconnect.NetworkPacket;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.UserInterface.DeviceSettingsActivity;
import org.kde.kdeconnect.UserInterface.MainActivity;
import org.kde.kdeconnect.UserInterface.PluginSettingsFragment;
import org.kde.kdeconnect_tp.R;
import java.io.ByteArrayOutputStream;
@ -89,13 +89,14 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
}
@Override
public void startPreferencesActivity(final DeviceSettingsActivity parentActivity) {
public PluginSettingsFragment getSettingsFragment() {
if (hasPermission()) {
Intent intent = new Intent(parentActivity, NotificationFilterActivity.class);
parentActivity.startActivity(intent);
} else {
getErrorDialog(parentActivity).show();
Context context = device.getContext();
Intent intent = new Intent(context, NotificationFilterActivity.class);
context.startActivity(intent);
}
return null;
}
private boolean hasPermission() {
@ -488,10 +489,8 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
return true;
}
@Override
public AlertDialog getErrorDialog(final Activity deviceActivity) {
return new AlertDialog.Builder(deviceActivity)
.setTitle(R.string.pref_plugin_notifications)
.setMessage(R.string.no_permissions)
@ -503,7 +502,6 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
//Do nothing
})
.create();
}
@Override

View File

@ -23,7 +23,6 @@ package org.kde.kdeconnect.Plugins;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.Build;
@ -31,8 +30,7 @@ import android.widget.Button;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.NetworkPacket;
import org.kde.kdeconnect.UserInterface.DeviceSettingsActivity;
import org.kde.kdeconnect.UserInterface.PluginSettingsActivity;
import org.kde.kdeconnect.UserInterface.PluginSettingsFragment;
import org.kde.kdeconnect_tp.R;
import androidx.annotation.StringRes;
@ -40,7 +38,6 @@ import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
public abstract class Plugin {
protected Device device;
protected Context context;
protected int permissionExplanation = R.string.permission_explanation;
@ -125,15 +122,13 @@ public abstract class Plugin {
/**
* If hasSettings returns true, this will be called when the user
* wants to access this plugin preferences and should launch some
* kind of interface. The default implementation will launch a
* PluginSettingsActivity with content from "yourplugin"_preferences.xml.
* wants to access this plugin's preferences. The default implementation
* will return a PluginSettingsFragment with content from "yourplugin"_preferences.xml
*
* @return The PluginSettingsFragment used to display this plugins settings
*/
public void startPreferencesActivity(DeviceSettingsActivity parentActivity) {
Intent intent = new Intent(parentActivity, PluginSettingsActivity.class);
intent.putExtra("plugin_display_name", getDisplayName());
intent.putExtra("plugin_key", getPluginKey());
parentActivity.startActivity(intent);
public PluginSettingsFragment getSettingsFragment() {
return PluginSettingsFragment.newInstance(getPluginKey());
}
/**

View File

@ -387,4 +387,8 @@ public class RemoteKeyboardPlugin extends Plugin {
np.set("state", state);
device.sendPacket(np);
}
String getDeviceId() {
return device.getDeviceId();
}
}

View File

@ -33,8 +33,8 @@ import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;
import org.kde.kdeconnect.UserInterface.DeviceSettingsActivity;
import org.kde.kdeconnect.UserInterface.MainActivity;
import org.kde.kdeconnect.UserInterface.PluginSettingsActivity;
import org.kde.kdeconnect_tp.R;
import java.util.ArrayList;
@ -162,10 +162,10 @@ public class RemoteKeyboardService
if (instances.size() == 1) { // single instance of RemoteKeyboardPlugin -> access its settings
RemoteKeyboardPlugin plugin = instances.get(0);
if (plugin != null) {
Intent intent = new Intent(this, PluginSettingsActivity.class);
Intent intent = new Intent(this, DeviceSettingsActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("plugin_display_name", plugin.getDisplayName());
intent.putExtra("plugin_key", plugin.getPluginKey());
intent.putExtra(DeviceSettingsActivity.EXTRA_DEVICE_ID, plugin.getDeviceId());
intent.putExtra(DeviceSettingsActivity.EXTRA_PLUGIN_KEY, plugin.getPluginKey());
startActivity(intent);
}
} else { // != 1 instance of plugin -> show main activity view

View File

@ -47,7 +47,7 @@ import org.kde.kdeconnect.Helpers.MediaStoreHelper;
import org.kde.kdeconnect.Helpers.NotificationHelper;
import org.kde.kdeconnect.NetworkPacket;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.UserInterface.DeviceSettingsActivity;
import org.kde.kdeconnect.UserInterface.PluginSettingsFragment;
import org.kde.kdeconnect_tp.R;
import java.io.BufferedOutputStream;
@ -227,12 +227,12 @@ public class SharePlugin extends Plugin implements ReceiveFileRunnable.CallBack
//We need to check for already existing files only when storing in the default path.
//User-defined paths use the new Storage Access Framework that already handles this.
//If the file should be opened immediately store it in the standard location to avoid the FileProvider trouble (See ShareNotification::setURI)
if (np.getBoolean("open") || !ShareSettingsActivity.isCustomDestinationEnabled(context)) {
final String defaultPath = ShareSettingsActivity.getDefaultDestinationDirectory().getAbsolutePath();
if (np.getBoolean("open") || !ShareSettingsFragment.isCustomDestinationEnabled(context)) {
final String defaultPath = ShareSettingsFragment.getDefaultDestinationDirectory().getAbsolutePath();
filename = FilesHelper.findNonExistingNameForNewFile(defaultPath, filename);
destinationFolderDocument = DocumentFile.fromFile(new File(defaultPath));
} else {
destinationFolderDocument = ShareSettingsActivity.getDestinationDirectory(context);
destinationFolderDocument = ShareSettingsFragment.getDestinationDirectory(context);
}
String displayName = FilesHelper.getFileNameWithoutExt(filename);
String mimeType = FilesHelper.getMimeTypeFromFile(filename);
@ -273,11 +273,8 @@ public class SharePlugin extends Plugin implements ReceiveFileRunnable.CallBack
}
@Override
public void startPreferencesActivity(DeviceSettingsActivity parentActivity) {
Intent intent = new Intent(parentActivity, ShareSettingsActivity.class);
intent.putExtra("plugin_display_name", getDisplayName());
intent.putExtra("plugin_key", getPluginKey());
parentActivity.startActivity(intent);
public PluginSettingsFragment getSettingsFragment() {
return ShareSettingsFragment.newInstance(getPluginKey());
}
void queuedSendUriList(final ArrayList<Uri> uriList) {
@ -489,7 +486,7 @@ public class SharePlugin extends Plugin implements ReceiveFileRunnable.CallBack
context.startActivity(intent);
} else {
if (!ShareSettingsActivity.isCustomDestinationEnabled(context)) {
if (!ShareSettingsFragment.isCustomDestinationEnabled(context)) {
Log.i("SharePlugin", "Adding to downloads");
DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
manager.addCompletedDownload(info.fileDocument.getUri().getLastPathSegment(), device.getName(), true, info.fileDocument.getType(), info.fileDocument.getUri().getPath(), info.fileSize, false);

View File

@ -1,3 +1,23 @@
/*
* Copyright 2016 Richard Wagler <riwag@posteo.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.kde.kdeconnect.Plugins.SharePlugin;
import android.annotation.TargetApi;
@ -9,18 +29,20 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceManager;
import android.util.Log;
import org.kde.kdeconnect.UserInterface.PluginSettingsActivity;
import org.kde.kdeconnect.UserInterface.PluginSettingsFragment;
import java.io.File;
import androidx.annotation.NonNull;
import androidx.documentfile.provider.DocumentFile;
import androidx.preference.CheckBoxPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
public class ShareSettingsActivity extends PluginSettingsActivity {
public class ShareSettingsFragment extends PluginSettingsFragment {
private final static String PREFERENCE_CUSTOMIZE_DESTINATION = "share_destination_custom";
private final static String PREFERENCE_DESTINATION = "share_destination_folder_uri";
@ -29,12 +51,20 @@ public class ShareSettingsActivity extends PluginSettingsActivity {
private Preference filePicker;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
public static ShareSettingsFragment newInstance(@NonNull String pluginKey) {
ShareSettingsFragment fragment = new ShareSettingsFragment();
fragment.setArguments(pluginKey);
final CheckBoxPreference customDownloads = (CheckBoxPreference) findPreference("share_destination_custom");
filePicker = findPreference("share_destination_folder_preference");
return fragment;
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
super.onCreatePreferences(savedInstanceState, rootKey);
PreferenceScreen preferenceScreen = getPreferenceScreen();
final CheckBoxPreference customDownloads = (CheckBoxPreference) preferenceScreen.findPreference("share_destination_custom");
filePicker = preferenceScreen.findPreference("share_destination_folder_preference");
if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)) {
customDownloads.setOnPreferenceChangeListener((preference, newValue) -> {
@ -51,13 +81,19 @@ public class ShareSettingsActivity extends PluginSettingsActivity {
filePicker.setEnabled(false);
}
boolean customized = PreferenceManager.getDefaultSharedPreferences(this).getBoolean(PREFERENCE_CUSTOMIZE_DESTINATION, false);
boolean customized = PreferenceManager
.getDefaultSharedPreferences(requireContext())
.getBoolean(PREFERENCE_CUSTOMIZE_DESTINATION, false);
updateFilePickerStatus(customized);
}
private void updateFilePickerStatus(boolean enabled) {
filePicker.setEnabled(enabled);
String path = PreferenceManager.getDefaultSharedPreferences(this).getString(PREFERENCE_DESTINATION, null);
String path = PreferenceManager
.getDefaultSharedPreferences(requireContext())
.getString(PREFERENCE_DESTINATION, null);
if (enabled && path != null) {
filePicker.setSummary(Uri.parse(path).getPath());
} else {
@ -99,22 +135,20 @@ public class ShareSettingsActivity extends PluginSettingsActivity {
@TargetApi(Build.VERSION_CODES.KITKAT)
@Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
if (requestCode == RESULT_PICKER
&& resultCode == Activity.RESULT_OK
&& resultData != null) {
Uri uri = resultData.getData();
getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION |
requireContext().getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION |
Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
Preference filePicker = findPreference("share_destination_folder_preference");
filePicker.setSummary(uri.getPath());
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(requireContext());
prefs.edit().putString(PREFERENCE_DESTINATION, uri.toString()).apply();
}
}
}

View File

@ -21,52 +21,79 @@
package org.kde.kdeconnect.UserInterface;
import android.os.Bundle;
import android.preference.PreferenceScreen;
import android.view.MenuItem;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect_tp.R;
import java.util.List;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
public class DeviceSettingsActivity extends AppCompatPreferenceActivity {
public class DeviceSettingsActivity
extends AppCompatActivity
implements PluginPreference.PluginPreferenceCallback {
public static final String EXTRA_DEVICE_ID = "deviceId";
public static final String EXTRA_PLUGIN_KEY = "pluginKey";
//TODO: Save/restore state
static private String deviceId; //Static because if we get here by using the back button in the action bar, the extra deviceId will not be set.
@Override
public void onCreate(Bundle savedInstanceState) {
ThemeUtil.setUserPreferredTheme(this);
super.onCreate(savedInstanceState);
final PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(this);
setPreferenceScreen(preferenceScreen);
setContentView(R.layout.activity_device_settings);
if (getIntent().hasExtra("deviceId")) {
deviceId = getIntent().getStringExtra("deviceId");
if (getSupportActionBar() != null) {
getSupportActionBar().setDefaultDisplayHomeAsUpEnabled(true);
}
BackgroundService.RunCommand(getApplicationContext(), service -> {
final Device device = service.getDevice(deviceId);
if (device == null) {
DeviceSettingsActivity.this.runOnUiThread(DeviceSettingsActivity.this::finish);
return;
String pluginKey = null;
if (getIntent().hasExtra(EXTRA_DEVICE_ID)) {
deviceId = getIntent().getStringExtra(EXTRA_DEVICE_ID);
if (getIntent().hasExtra(EXTRA_PLUGIN_KEY)) {
pluginKey = getIntent().getStringExtra(EXTRA_PLUGIN_KEY);
}
List<String> plugins = device.getSupportedPlugins();
for (final String pluginKey : plugins) {
PluginPreference pref = new PluginPreference(DeviceSettingsActivity.this, pluginKey, device);
preferenceScreen.addPreference(pref);
} else if (deviceId == null) {
throw new RuntimeException("You must start DeviceSettingActivity using an intent that has a " + EXTRA_DEVICE_ID + " extra");
}
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragmentPlaceHolder);
if (fragment == null) {
if (pluginKey == null) {
fragment = DeviceSettingsFragment.newInstance(deviceId);
} else {
Device device = BackgroundService.getInstance().getDevice(deviceId);
Plugin plugin = device.getPlugin(pluginKey, true);
fragment = plugin.getSettingsFragment();
}
});
getSupportFragmentManager()
.beginTransaction()
.add(R.id.fragmentPlaceHolder, fragment)
.commit();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
//ActionBar's back button
if (item.getItemId() == android.R.id.home) {
finish();
return true;
} else {
return super.onOptionsItemSelected(item);
FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 0) {
fm.popBackStack();
return true;
}
}
return super.onOptionsItemSelected(item);
}
@Override
@ -81,4 +108,27 @@ public class DeviceSettingsActivity extends AppCompatPreferenceActivity {
BackgroundService.removeGuiInUseCounter(this);
}
@Override
public void onStartPluginSettingsFragment(Plugin plugin) {
setTitle(getString(R.string.plugin_settings_with_name, plugin.getDisplayName()));
PluginSettingsFragment fragment = plugin.getSettingsFragment();
//TODO: Remove when NotificationFilterActivity has been turned into a PluginSettingsFragment
if (fragment == null) {
return;
}
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragmentPlaceHolder, fragment)
.addToBackStack(null)
.commit();
}
@Override
public void onFinish() {
finish();
}
}

View File

@ -0,0 +1,175 @@
/*
* Copyright 2018 Erik Duisters <e.duisters1@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.kde.kdeconnect.UserInterface;
import android.os.Bundle;
import android.os.Parcelable;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect_tp.R;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentActivity;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceScreen;
import androidx.recyclerview.widget.RecyclerView;
public class DeviceSettingsFragment extends PreferenceFragmentCompat {
private static final String ARG_DEVICE_ID = "deviceId";
private static final String KEY_RECYCLERVIEW_LAYOUTMANAGER_STATE = "RecyclerViewLayoutmanagerState";
private PluginPreference.PluginPreferenceCallback callback;
private Parcelable recyclerViewLayoutManagerState;
/*
https://bricolsoftconsulting.com/state-preservation-in-backstack-fragments/
When adding a fragment to the backstack the fragments onDestroyView is called (which releases
the RecyclerView) but the fragments onSaveInstanceState is not called. When the fragment is destroyed later
on, its onSaveInstanceState() is called but I don't have access to the RecyclerView or it's LayoutManager any more
*/
private boolean stateSaved;
public static DeviceSettingsFragment newInstance(@NonNull String deviceId) {
DeviceSettingsFragment fragment = new DeviceSettingsFragment();
Bundle args = new Bundle();
args.putString(ARG_DEVICE_ID, deviceId);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
if (requireActivity() instanceof PluginPreference.PluginPreferenceCallback) {
callback = (PluginPreference.PluginPreferenceCallback) getActivity();
} else {
throw new RuntimeException(requireActivity().getClass().getSimpleName()
+ " must implement PluginPreference.PluginPreferenceCallback");
}
super.onCreate(savedInstanceState);
if (savedInstanceState != null && savedInstanceState.containsKey(KEY_RECYCLERVIEW_LAYOUTMANAGER_STATE)) {
recyclerViewLayoutManagerState = savedInstanceState.getParcelable(KEY_RECYCLERVIEW_LAYOUTMANAGER_STATE);
}
}
@Override
public void onDestroy() {
super.onDestroy();
callback = null;
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(requireContext());
setPreferenceScreen(preferenceScreen);
final String deviceId = getArguments().getString(ARG_DEVICE_ID);
BackgroundService.RunCommand(requireContext(), service -> {
final Device device = service.getDevice(deviceId);
if (device == null) {
final FragmentActivity activity = requireActivity();
activity.runOnUiThread(() -> activity.finish());
return;
}
List<String> plugins = device.getSupportedPlugins();
for (final String pluginKey : plugins) {
PluginPreference pref = new PluginPreference(requireContext(), pluginKey, device, callback);
preferenceScreen.addPreference(pref);
}
});
}
@Override
protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) {
RecyclerView.Adapter adapter = super.onCreateAdapter(preferenceScreen);
/*
The layoutmanager's state (e.g. scroll position) can only be restored when the recyclerView's
adapter has been re-populated with data.
*/
if (recyclerViewLayoutManagerState != null) {
adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
RecyclerView.LayoutManager layoutManager = getListView().getLayoutManager();
if (layoutManager != null) {
layoutManager.onRestoreInstanceState(recyclerViewLayoutManagerState);
}
recyclerViewLayoutManagerState = null;
adapter.unregisterAdapterDataObserver(this);
}
});
}
return adapter;
}
@Override
public void onPause() {
super.onPause();
stateSaved = false;
}
@Override
public void onResume() {
super.onResume();
requireActivity().setTitle(getString(R.string.device_menu_plugins));
}
@Override
public void onDestroyView() {
if (!stateSaved && getListView() != null && getListView().getLayoutManager() != null) {
recyclerViewLayoutManagerState = getListView().getLayoutManager().onSaveInstanceState();
}
super.onDestroyView();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Parcelable layoutManagerState = recyclerViewLayoutManagerState;
if (getListView() != null && getListView().getLayoutManager() != null) {
layoutManagerState = getListView().getLayoutManager().onSaveInstanceState();
}
if (layoutManagerState != null) {
outState.putParcelable(KEY_RECYCLERVIEW_LAYOUTMANAGER_STATE, layoutManagerState);
}
stateSaved = true;
}
}

View File

@ -1,6 +1,6 @@
package org.kde.kdeconnect.UserInterface;
import android.preference.CheckBoxPreference;
import android.content.Context;
import android.view.View;
import org.kde.kdeconnect.Device;
@ -8,23 +8,28 @@ import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.Plugins.PluginFactory;
import org.kde.kdeconnect_tp.R;
class PluginPreference extends CheckBoxPreference {
import androidx.annotation.NonNull;
import androidx.preference.CheckBoxPreference;
import androidx.preference.PreferenceViewHolder;
class PluginPreference extends CheckBoxPreference {
private final Device device;
private final String pluginKey;
private final View.OnClickListener listener;
public PluginPreference(final DeviceSettingsActivity activity, final String pluginKey, final Device device) {
super(activity);
public PluginPreference(@NonNull final Context context, @NonNull final String pluginKey,
@NonNull final Device device, @NonNull PluginPreferenceCallback callback) {
super(context);
setLayoutResource(R.layout.preference_with_button);
setLayoutResource(R.layout.preference_with_button/*R.layout.preference_with_button_androidx*/);
this.device = device;
this.pluginKey = pluginKey;
PluginFactory.PluginInfo info = PluginFactory.getPluginInfo(activity, pluginKey);
PluginFactory.PluginInfo info = PluginFactory.getPluginInfo(context, pluginKey);
setTitle(info.getDisplayName());
setSummary(info.getDescription());
setIcon(android.R.color.transparent);
setChecked(device.isPluginEnabled(pluginKey));
Plugin plugin = device.getPlugin(pluginKey, true);
@ -32,30 +37,31 @@ class PluginPreference extends CheckBoxPreference {
this.listener = v -> {
Plugin plugin1 = device.getPlugin(pluginKey, true);
if (plugin1 != null) {
plugin1.startPreferencesActivity(activity);
callback.onStartPluginSettingsFragment(plugin1);
} else { //Could happen if the device is not connected anymore
activity.finish(); //End this activity so we go to the "device not reachable" screen
callback.onFinish();
}
};
} else {
this.listener = null;
}
}
@Override
protected void onBindView(View root) {
super.onBindView(root);
final View button = root.findViewById(R.id.settingsButton);
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
final View button = holder.findViewById(R.id.settingsButton);
if (listener == null) {
button.setVisibility(View.GONE);
} else {
button.setEnabled(isChecked());
button.setVisibility(View.VISIBLE);
button.setOnClickListener(listener);
}
root.setOnClickListener(v -> {
holder.itemView.setOnClickListener(v -> {
boolean newState = !device.isPluginEnabled(pluginKey);
setChecked(newState); //It actually works on API<14
button.setEnabled(newState);
@ -63,4 +69,8 @@ class PluginPreference extends CheckBoxPreference {
});
}
interface PluginPreferenceCallback {
void onStartPluginSettingsFragment(Plugin plugin);
void onFinish();
}
}

View File

@ -1,68 +0,0 @@
/*
* Copyright 2014 Ronny Yabar Aizcorbe <ronnycontacto@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.kde.kdeconnect.UserInterface;
import android.os.Bundle;
import android.view.MenuItem;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect_tp.R;
import java.util.Locale;
public class PluginSettingsActivity extends AppCompatPreferenceActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String pluginDisplayName = getIntent().getStringExtra("plugin_display_name");
setTitle(getString(R.string.plugin_settings_with_name, pluginDisplayName));
String pluginKey = getIntent().getStringExtra("plugin_key");
int resFile = getResources().getIdentifier(pluginKey.toLowerCase(Locale.ENGLISH) + "_preferences", "xml", getPackageName());
addPreferencesFromResource(resFile);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
//ActionBar's back button
if (item.getItemId() == android.R.id.home) {
finish();
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
@Override
protected void onStart() {
super.onStart();
BackgroundService.addGuiInUseCounter(this);
}
@Override
protected void onStop() {
super.onStop();
BackgroundService.removeGuiInUseCounter(this);
}
}

View File

@ -0,0 +1,81 @@
/*
* Copyright 2018 Erik Duisters <e.duisters1@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.kde.kdeconnect.UserInterface;
import android.os.Bundle;
import org.kde.kdeconnect.Plugins.PluginFactory;
import org.kde.kdeconnect_tp.R;
import java.util.Locale;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceFragmentCompat;
public class PluginSettingsFragment extends PreferenceFragmentCompat {
public static final String ARG_PLUGIN_KEY = "plugin_key";
private String pluginKey;
public static PluginSettingsFragment newInstance(@NonNull String pluginKey) {
PluginSettingsFragment fragment = new PluginSettingsFragment();
fragment.setArguments(pluginKey);
return fragment;
}
public PluginSettingsFragment() {}
protected Bundle setArguments(@NonNull String pluginKey) {
Bundle args = new Bundle();
args.putString(ARG_PLUGIN_KEY, pluginKey);
setArguments(args);
return args;
}
@Override
public void onCreate(Bundle savedInstanceState) {
if (getArguments() == null || !getArguments().containsKey(ARG_PLUGIN_KEY)) {
throw new RuntimeException("You must provide a pluginKey by calling setArguments(@NonNull String pluginKey)");
}
pluginKey = getArguments().getString(ARG_PLUGIN_KEY);
super.onCreate(savedInstanceState);
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
int resFile = getResources().getIdentifier(pluginKey.toLowerCase(Locale.ENGLISH) + "_preferences", "xml",
requireContext().getPackageName());
addPreferencesFromResource(resFile);
}
@Override
public void onResume() {
super.onResume();
PluginFactory.PluginInfo info = PluginFactory.getPluginInfo(requireContext(), pluginKey);
requireActivity().setTitle(getString(R.string.plugin_settings_with_name, info.getDisplayName()));
}
}