Commit 0de9db8c authored by Aline Gondim Santos's avatar Aline Gondim Santos Committed by Adrien Béraud

plugin: add Path preference type

Change-Id: I92371f36045705675d34897d3023c423a9559c64
parent 4f7b5a12
......@@ -69,6 +69,7 @@ import cx.ring.services.NotificationService;
import cx.ring.settings.SettingsFragment;
import cx.ring.settings.VideoSettingsFragment;
import cx.ring.settings.pluginssettings.PluginDetails;
import cx.ring.settings.pluginssettings.PluginPathPreferenceFragment;
import cx.ring.settings.pluginssettings.PluginSettingsFragment;
import cx.ring.settings.pluginssettings.PluginsListSettingsFragment;
import cx.ring.utils.ContentUriHandler;
......@@ -104,6 +105,7 @@ public class HomeActivity extends AppCompatActivity implements BottomNavigationV
public static final String PLUGINS_LIST_SETTINGS_TAG = "PluginsListSettings";
public static final String PLUGIN_SETTINGS_TAG = "PluginSettings";
public static final String PLUGIN_PATH_PREFERENCE_TAG = "PluginPathPreference";
private static final String NAVIGATION_TAG = "Navigation";
......@@ -660,7 +662,7 @@ public class HomeActivity extends AppCompatActivity implements BottomNavigationV
getSupportFragmentManager()
.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.replace(R.id.main_frame, fContent, PLUGINS_LIST_SETTINGS_TAG)
.replace(getFragmentContainerId(), fContent, PLUGINS_LIST_SETTINGS_TAG)
.addToBackStack(PLUGINS_LIST_SETTINGS_TAG).commit();
}
......@@ -676,10 +678,25 @@ public class HomeActivity extends AppCompatActivity implements BottomNavigationV
getSupportFragmentManager()
.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.replace(R.id.main_frame, fContent, PLUGIN_SETTINGS_TAG)
.replace(getFragmentContainerId(), fContent, PLUGIN_SETTINGS_TAG)
.addToBackStack(PLUGIN_SETTINGS_TAG).commit();
}
/**
* Changes the current main fragment to a plugin PATH preference fragment
*/
public void gotToPluginPathPreference(PluginDetails pluginDetails, String preferenceKey){
if (fContent instanceof PluginPathPreferenceFragment) {
return;
}
fContent = PluginPathPreferenceFragment.newInstance(pluginDetails, preferenceKey);
getSupportFragmentManager()
.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.replace(getFragmentContainerId(), fContent, PLUGIN_PATH_PREFERENCE_TAG)
.addToBackStack(PLUGIN_PATH_PREFERENCE_TAG).commit();
}
@Override
public void setColor(int color) {
//mToolbar.setBackground(new ColorDrawable(color));
......
......@@ -130,16 +130,14 @@ public class PluginUtils {
*/
public static String listStringToStringList(List<String> listString) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append('[');
if(!listString.isEmpty()) {
for(int i=0; i< listString.size()-1; i++){
for(int i=0; i< listString.size(); i++){
stringBuilder.append(listString.get(i)).append(",");
}
stringBuilder.append(listString.get(listString.size()-1));
stringBuilder.append(listString.get(listString.size()));
}
stringBuilder.append(']');
return stringBuilder.toString();
}
......@@ -153,7 +151,7 @@ public class PluginUtils {
List<String> listString = new ArrayList<>();
StringBuilder currentWord = new StringBuilder();
if(!stringList.isEmpty()) {
for (int i = 1; i < stringList.length() - 1; i++) {
for (int i = 0; i < stringList.length(); i++) {
char currentChar = stringList.charAt(i);
if (currentChar != ',') {
currentWord.append(currentChar);
......@@ -162,7 +160,7 @@ public class PluginUtils {
currentWord = new StringBuilder();
}
if (i == stringList.length() - 2) {
if (i == stringList.length() - 1) {
listString.add(currentWord.toString());
break;
}
......
/*
* Copyright (C) 2004-2019 Savoir-faire Linux Inc.
*
* Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com>
* Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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 3 of the License, or
* (at your option) any later version.
*
* 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 cx.ring.settings.pluginssettings;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.io.File;
import java.util.List;
import cx.ring.R;
import static cx.ring.utils.AndroidFileUtils.getFileName;
import static cx.ring.utils.AndroidFileUtils.isImage;
public class PathListAdapter extends RecyclerView.Adapter<PathListAdapter.PathViewHolder> {
private List<String> mList;
private PathListItemListener listener;
private Drawable icon;
public static final String TAG = PathListAdapter.class.getSimpleName();
PathListAdapter(List<String> pathList, PathListItemListener listener) {
this.mList = pathList;
this.listener = listener;
}
@NonNull
@Override
public PathViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.frag_path_list_item, parent, false);
return new PathViewHolder(view, listener);
}
@Override
public void onBindViewHolder(@NonNull PathViewHolder holder, int position) {
holder.setDetails(mList.get(position));
}
@Override
public int getItemCount() {
return mList.size();
}
public void updatePluginsList(List<String> listPaths) {
mList = listPaths;
notifyDataSetChanged();
}
class PathViewHolder extends RecyclerView.ViewHolder{
private final ImageView pathIcon;
private final TextView pathTextView;
private String path;
PathViewHolder(@NonNull View itemView, PathListItemListener listener) {
super(itemView);
// Views that should be updated by the update method
pathIcon = itemView.findViewById(R.id.path_item_icon);
pathTextView = itemView.findViewById(R.id.path_item_name);
// Set listeners, we set the listeners on creation so details can be null
itemView.setOnClickListener(v -> listener.onPathItemClicked(path));
}
// update the viewHolder view
public void update(String s) {
// Set the plugin icon
File file = new File(s);
if (file.exists()) {
if (isImage(s)) {
pathTextView.setVisibility(View.GONE);
icon = Drawable.createFromPath(s);
if (icon != null) {
pathIcon.setImageDrawable(icon);
}
} else {
pathTextView.setVisibility(View.VISIBLE);
pathTextView.setText(getFileName(s));
}
}
}
public void setDetails(String path) {
this.path = path;
update(this.path);
}
}
public interface PathListItemListener {
void onPathItemClicked(String path);
}
}
/*
* Copyright (C) 2004-2019 Savoir-faire Linux Inc.
*
* Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.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 3 of the License, or
* (at your option) any later version.
*
* 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 cx.ring.settings.pluginssettings;
import android.graphics.drawable.Drawable;
......@@ -7,7 +25,6 @@ import java.util.List;
import java.util.Map;
import cx.ring.daemon.Ringservice;
import cx.ring.utils.Log;
/**
* Class that contains PluginDetails like name, rootPath
......@@ -16,7 +33,7 @@ public class PluginDetails {
public static final String TAG = PluginDetails.class.getSimpleName();
private String name;
private String rootPath;
private Map<String, String> details;
private final Map<String, String> details;
private Drawable icon;
private boolean enabled;
......@@ -40,17 +57,6 @@ public class PluginDetails {
return rootPath;
}
/**
* @return String: absolute path to the so file
*/
public String getSoPath() {
return details.get("soPath") != null ? details.get("soPath") : "";
}
public void setRootPath(String rootPath) {
this.rootPath = rootPath;
}
/**
* Returns the plugin activation status by the user
* @return boolean
......@@ -65,12 +71,8 @@ public class PluginDetails {
public void setIcon() {
String iconPath = details.get("iconPath");
if(iconPath != null) {
if (iconPath != null) {
File file = new File(iconPath);
Log.i(TAG, "Plugin icon path: " + iconPath);
if(file.exists()) {
icon = Drawable.createFromPath(iconPath);
}
......@@ -90,7 +92,7 @@ public class PluginDetails {
}
public Map<String, String> getPluginPreferencesValues() {
return Ringservice.getPluginPreferencesValues(getRootPath()).toNative();
return Ringservice.getPluginPreferencesValues(getRootPath());
}
public boolean setPluginPreference(String key, String value) {
......
/*
* Copyright (C) 2004-2019 Savoir-faire Linux Inc.
*
* Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.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 3 of the License, or
* (at your option) any later version.
*
* 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 cx.ring.settings.pluginssettings;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import cx.ring.R;
import cx.ring.client.HomeActivity;
import cx.ring.databinding.FragPluginsPathPreferenceBinding;
import cx.ring.utils.AndroidFileUtils;
import io.reactivex.android.schedulers.AndroidSchedulers;
public class PluginPathPreferenceFragment extends Fragment implements PathListAdapter.PathListItemListener {
public static final String TAG = PluginPathPreferenceFragment.class.getSimpleName();
private static final int PATH_REQUEST_CODE = 1;
private final List<String> pathList = new ArrayList<>();
private PluginDetails mPluginDetails;
private String mCurrentKey;
private String mCurrentValue;
private Context mContext;
private String subtitle;
private String supportedMimeTypes = "*/*";
private FragPluginsPathPreferenceBinding binding;
public static PluginPathPreferenceFragment newInstance(PluginDetails pluginDetails, String preferenceKey) {
Bundle args = new Bundle();
args.putString("name", pluginDetails.getName());
args.putString("rootPath", pluginDetails.getRootPath());
args.putBoolean("enabled", pluginDetails.isEnabled());
args.putString("preferenceKey", preferenceKey);
PluginPathPreferenceFragment ppf = new PluginPathPreferenceFragment();
ppf.setArguments(args);
return ppf;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = requireActivity();
Bundle arguments = getArguments();
if (arguments != null && arguments.getString("name") != null
&& arguments.getString("rootPath") != null) {
mPluginDetails = new PluginDetails(arguments.getString("name"), arguments.getString("rootPath"), arguments.getBoolean("enabled"));
List<Map<String, String>> mPreferencesAttributes = mPluginDetails.getPluginPreferences();
if (mPreferencesAttributes != null && !mPreferencesAttributes.isEmpty()) {
mCurrentKey = arguments.getString("preferenceKey");
mCurrentValue = mPluginDetails.getPluginPreferencesValues().get(mCurrentKey);
setHasOptionsMenu(true);
for (Map<String, String> preferenceAttributes : mPreferencesAttributes) {
if (preferenceAttributes.get("key").equals(mCurrentKey)) {
String mimeType = preferenceAttributes.get("mimeType");
if (!TextUtils.isEmpty(mimeType))
supportedMimeTypes = mimeType;
subtitle = mPluginDetails.getName() + " - " + preferenceAttributes.get("title");
String defaultPath = preferenceAttributes.get("defaultValue");
if (!TextUtils.isEmpty(defaultPath)) {
defaultPath = defaultPath.substring(0, defaultPath.lastIndexOf("/"));
for (File file : new File(defaultPath).listFiles()) {
pathList.add(file.toString());
}
break;
}
}
}
}
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
binding = FragPluginsPathPreferenceBinding.inflate(inflater, container, false);
if (!pathList.isEmpty()) {
binding.pathPreferences.setAdapter(new PathListAdapter(pathList, this));
}
((HomeActivity) requireActivity()).setToolbarTitle(R.string.menu_item_plugin_list);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
binding.pluginSettingSubtitle.setText(subtitle);
binding.pluginsPathPreferenceFab.setOnClickListener(v -> {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType(supportedMimeTypes);
startActivityForResult(intent, PATH_REQUEST_CODE);
});
if (!mCurrentValue.isEmpty()) {
binding.currentPathItemIcon.setVisibility(View.VISIBLE);
File file = new File(mCurrentValue);
if (file.exists()) {
if (AndroidFileUtils.isImage(mCurrentValue)) {
binding.currentPathItemName.setVisibility(View.INVISIBLE);
Drawable icon = Drawable.createFromPath(mCurrentValue);
if (icon != null) {
binding.currentPathItemIcon.setImageDrawable(icon);
}
} else {
binding.currentPathItemName.setVisibility(View.VISIBLE);
binding.currentPathItemName.setText(AndroidFileUtils.getFileName(mCurrentValue));
}
}
} else {
binding.currentPathItemIcon.setVisibility(View.INVISIBLE);
binding.currentPathItemName.setVisibility(View.INVISIBLE);
binding.pluginsPathPreferenceFab.performClick();
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == PATH_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
if (data != null) {
Uri uri = data.getData();
if (uri != null) {
AndroidFileUtils.getCacheFile(requireContext(), uri)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(file -> setPreferencePath(file.getAbsolutePath()),
e -> Toast.makeText(mContext, e.getMessage(), Toast.LENGTH_LONG).show());
}
}
}
}
@Override
public void onResume() {
((HomeActivity) requireActivity()).setToolbarTitle(R.string.menu_item_plugin_list);
super.onResume();
}
@Override
public void onDestroyView() {
binding = null;
super.onDestroyView();
}
private void setPreferencePath(String path) {
if (mPluginDetails.setPluginPreference(mCurrentKey, path)) {
mCurrentValue = path;
if (!path.isEmpty()) {
binding.currentPathItemIcon.setVisibility(View.VISIBLE);
if (AndroidFileUtils.isImage(mCurrentValue)) {
Drawable icon = Drawable.createFromPath(mCurrentValue);
binding.currentPathItemIcon.setImageDrawable(icon);
binding.currentPathItemName.setVisibility(View.INVISIBLE);
} else {
binding.currentPathItemName.setText(AndroidFileUtils.getFileName(mCurrentValue));
binding.currentPathItemName.setVisibility(View.VISIBLE);
}
} else {
binding.currentPathItemIcon.setVisibility(View.INVISIBLE);
binding.currentPathItemName.setVisibility(View.INVISIBLE);
}
}
}
@Override
public void onPathItemClicked(String path) {
setPreferencePath(path);
}
}
......@@ -4,6 +4,7 @@ import androidx.annotation.Nullable;
import androidx.preference.PreferenceDataStore;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
......@@ -17,6 +18,7 @@ import static cx.ring.plugins.PluginUtils.stringListToListString;
public class PluginPreferencesDataStore extends PreferenceDataStore {
private PluginDetails mPluginDetails;
private Map<String, String> mPreferenceTypes = new HashMap<>();
private Map<String, String> preferencesValues;
private ReadWriteLock lock = new ReentrantReadWriteLock();
......@@ -25,6 +27,16 @@ public class PluginPreferencesDataStore extends PreferenceDataStore {
preferencesValues = mPluginDetails.getPluginPreferencesValues();
}
public void addTomPreferenceTypes(Map<String, String> preferenceModel) {
Lock writeLock = lock.writeLock();
try {
writeLock.lock();
mPreferenceTypes.put(preferenceModel.get("key"), preferenceModel.get("type"));
} finally {
writeLock.unlock();
}
}
@Override
public void putBoolean(String key, boolean value) {
Boolean boxedValue = value;
......@@ -111,7 +123,7 @@ public class PluginPreferencesDataStore extends PreferenceDataStore {
int returnValue = defValue;
String value = getPreferencesValues().get(key);
if (value != null) {
returnValue = Integer.valueOf(value);
returnValue = Integer.parseInt(value);
}
return returnValue;
}
......@@ -121,7 +133,7 @@ public class PluginPreferencesDataStore extends PreferenceDataStore {
long returnValue = defValue;
String value = getPreferencesValues().get(key);
if (value != null) {
returnValue = Long.valueOf(value);
returnValue = Long.parseLong(value);
}
return returnValue;
}
......@@ -131,7 +143,7 @@ public class PluginPreferencesDataStore extends PreferenceDataStore {
float returnValue = defValue;
String value = getPreferencesValues().get(key);
if (value != null) {
returnValue = Float.valueOf(value);
returnValue = Float.parseFloat(value);
}
return returnValue;
}
......@@ -141,7 +153,7 @@ public class PluginPreferencesDataStore extends PreferenceDataStore {
boolean returnValue = defValue;
String value = getPreferencesValues().get(key);
if (value != null) {
returnValue = Boolean.valueOf(value);
returnValue = Boolean.parseBoolean(value);
}
return returnValue;
}
......@@ -150,7 +162,7 @@ public class PluginPreferencesDataStore extends PreferenceDataStore {
* Updates the preferencesValues map
* Use locks since the PreferenceInteraction is asynchronous
*/
private void notifyPreferencesValuesChange() {
public void notifyPreferencesValuesChange() {
Lock writeLock = lock.writeLock();
try {
writeLock.lock();
......
package cx.ring.settings.pluginssettings;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
......@@ -13,7 +12,6 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.CheckBoxPreference;
import androidx.preference.DialogPreference;
import androidx.preference.DropDownPreference;
......@@ -35,15 +33,12 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.prefs.Preferences;
import cx.ring.R;
import cx.ring.client.HomeActivity;
import cx.ring.daemon.Ringservice;
import cx.ring.plugins.ButtonPreference.ButtonPreference;
import cx.ring.plugins.PluginPreferences;
import static android.content.Context.MODE_PRIVATE;
import static cx.ring.plugins.PluginUtils.getOrElse;
import static cx.ring.plugins.PluginUtils.stringListToListString;
......@@ -52,6 +47,7 @@ public class PluginSettingsFragment extends PreferenceFragmentCompat {
private Context mContext;
private List<Map<String, String>> mPreferencesAttributes;
private PluginDetails pluginDetails;
private PluginPreferencesDataStore ppds;
public static PluginSettingsFragment newInstance(PluginDetails pluginDetails) {
Bundle args = new Bundle();
......@@ -83,7 +79,7 @@ public class PluginSettingsFragment extends PreferenceFragmentCompat {
mPreferencesAttributes = pluginDetails.getPluginPreferences();
PreferenceManager preferenceManager = getPreferenceManager();
PluginPreferencesDataStore ppds = new PluginPreferencesDataStore(pluginDetails);
ppds = new PluginPreferencesDataStore(pluginDetails);
preferenceManager.setPreferenceDataStore(ppds);
setHasOptionsMenu(true);
}
......@@ -138,6 +134,9 @@ public class PluginSettingsFragment extends PreferenceFragmentCompat {
case "List":
preferencesViews.add(createListPreference(preferenceAttributes));
break;
case "Path":
preferencesViews.add(createPathPreference(preferenceAttributes));
break;
case "MultiSelectList":
preferencesViews.
add(createMultiSelectListPreference(preferenceAttributes));
......@@ -161,37 +160,27 @@ public class PluginSettingsFragment extends PreferenceFragmentCompat {
private Preference createHeadPreference()
{
PluginPreferences preference = new PluginPreferences(mContext, pluginDetails);
preference.setResetClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new MaterialAlertDialogBuilder(mContext)
.setTitle(preference.getTitle())
.setMessage(R.string.plugin_reset_preferences_ask)
.setPositiveButton(android.R.string.ok, (dialog, id) -> {
Ringservice.resetPluginPreferencesValues(pluginDetails.getRootPath());
((HomeActivity) requireActivity()).popFragmentImmediate();
})
.setNegativeButton(android.R.string.cancel, (dialog, whichButton) -> {
/* Terminate with no action */
})
.show();
}
});
preference.setInstallClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new MaterialAlertDialogBuilder(mContext)
.setMessage(R.string.account_delete_dialog_message)
.setTitle(R.string.plugin_uninstall_title)
.setPositiveButton(android.R.string.ok, (dialog, whichButton) -> {
pluginDetails.setEnabled(false);
Ringservice.uninstallPlugin(pluginDetails.getRootPath());
((HomeActivity) requireActivity()).popFragmentImmediate();
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
});
preference.setResetClickListener(v -> new MaterialAlertDialogBuilder(mContext)
.setTitle(preference.getTitle())
.setMessage(R.string.plugin_reset_preferences_ask)
.setPositiveButton(android.R.string.ok, (dialog, id) -> {
Ringservice.resetPluginPreferencesValues(pluginDetails.getRootPath());