diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 903ed8ab..72fc9271 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -5,7 +5,7 @@
android:versionName="1.6.2">
+ android:targetSdkVersion="25" />
22 means we have to support the new permissions model
+ targetSdkVersion 25
//multiDexEnabled true
//testInstrumentationRunner "com.android.test.runner.MultiDexTestRunner"
}
@@ -57,7 +57,7 @@ android {
minifyEnabled false
useProguard false
}
- release { //keep on 'releae', set to 'all' when testing to make sure proguard is not deleting important stuff
+ release { //keep on 'release', set to 'all' when testing to make sure proguard is not deleting important stuff
minifyEnabled true
useProguard true
proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
@@ -83,5 +83,6 @@ dependencies {
androidTestCompile 'org.mockito:mockito-core:1.10.19'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.1'// Because mockito has some problems with dex environment
androidTestCompile 'org.skyscreamer:jsonassert:1.3.0'
+ testCompile 'junit:junit:4.12'
}
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f78801be..9d949646 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -206,4 +206,8 @@
Open
Close
+ You need to grant permissions to access the storage
+ Some Plugins need permissions to work (tap for more info):
+ This plugin needs permissions to work
+
diff --git a/src/org/kde/kdeconnect/Device.java b/src/org/kde/kdeconnect/Device.java
index ba91436a..cb234c8e 100644
--- a/src/org/kde/kdeconnect/Device.java
+++ b/src/org/kde/kdeconnect/Device.java
@@ -82,6 +82,7 @@ public class Device implements BaseLink.PackageReceiver {
private List m_supportedPlugins = new ArrayList<>();
private final ConcurrentHashMap plugins = new ConcurrentHashMap<>();
private final ConcurrentHashMap failedPlugins = new ConcurrentHashMap<>();
+ private final ConcurrentHashMap pluginsWithoutPermissions = new ConcurrentHashMap<>();
private Map> pluginsByIncomingInterface = new HashMap<>();
private final SharedPreferences settings;
@@ -722,6 +723,16 @@ public class Device implements BaseLink.PackageReceiver {
failedPlugins.put(pluginKey, plugin);
}
+ if(!plugin.checkRequiredPermissions()){
+ Log.e("KDE/addPlugin", "No permission " + pluginKey);
+ plugins.remove(pluginKey);
+ pluginsWithoutPermissions.put(pluginKey, plugin);
+ success = false;
+ } else {
+ Log.i("KDE/addPlugin", "Permission OK " + pluginKey);
+ pluginsWithoutPermissions.remove(pluginKey);
+ }
+
return success;
}
@@ -812,6 +823,10 @@ public class Device implements BaseLink.PackageReceiver {
return failedPlugins;
}
+ public ConcurrentHashMap getPluginsWithoutPermissions() {
+ return pluginsWithoutPermissions;
+ }
+
public void addPluginsChangedListener(PluginsChangedListener listener) {
pluginsChangedListeners.add(listener);
}
diff --git a/src/org/kde/kdeconnect/Plugins/Plugin.java b/src/org/kde/kdeconnect/Plugins/Plugin.java
index 67650e7f..00b0c68b 100644
--- a/src/org/kde/kdeconnect/Plugins/Plugin.java
+++ b/src/org/kde/kdeconnect/Plugins/Plugin.java
@@ -20,23 +20,33 @@
package org.kde.kdeconnect.Plugins;
+import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
+import android.support.annotation.StringRes;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+import android.util.Log;
import android.view.View;
import android.widget.Button;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.NetworkPackage;
+import org.kde.kdeconnect.UserInterface.MaterialActivity;
import org.kde.kdeconnect.UserInterface.PluginSettingsActivity;
import org.kde.kdeconnect.UserInterface.SettingsActivity;
+import org.kde.kdeconnect_tp.R;
public abstract class Plugin {
protected Device device;
protected Context context;
+ protected int permissionExplanation = R.string.permission_explanation;
public final void setContext(Context context, Device device) {
this.device = device;
@@ -167,14 +177,6 @@ public abstract class Plugin {
*/
public boolean onPackageReceived(NetworkPackage np) { return false; }
- /**
- * If onCreate returns false, should create a dialog explaining
- * the problem (and how to fix it, if possible) to the user.
- */
- public AlertDialog getErrorDialog(Activity deviceActivity) {
- return null;
- }
-
/**
* Should return the list of NetworkPackage types that this plugin can handle
*/
@@ -205,4 +207,70 @@ public abstract class Plugin {
return b;
}
+ public String[] getRequiredPermissions() {
+ return new String[0];
+ }
+
+ public String[] getOptionalPermissions() {
+ return new String[0];
+ }
+
+ //Permission from Manifest.permission.*
+ protected boolean isPermissionGranted(String permission) {
+ int result = ContextCompat.checkSelfPermission(context, permission);
+ return (result == PackageManager.PERMISSION_GRANTED);
+ }
+
+ protected boolean arePermissionsGranted(String[] permissions) {
+ for(String permission: permissions){
+ if(!isPermissionGranted(permission)){
+ return false;
+ }
+ }
+ return true;
+ }
+
+ protected AlertDialog requestPermissionDialog(Activity activity, String permissions, @StringRes int reason) {
+ return requestPermissionDialog(activity, new String[]{permissions}, reason);
+ }
+
+ protected AlertDialog requestPermissionDialog(final Activity activity, final String[] permissions, @StringRes int reason){
+ return new AlertDialog.Builder(activity)
+ .setTitle(getDisplayName())
+ .setMessage(reason)
+ .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ ActivityCompat.requestPermissions(activity, permissions, 0);
+ }
+ })
+ .setNegativeButton(R.string.cancel,new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ //Do nothing
+ }
+ })
+ .create();
+ }
+
+ /**
+ * If onCreate returns false, should create a dialog explaining
+ * the problem (and how to fix it, if possible) to the user.
+ */
+
+ public AlertDialog getErrorDialog(Activity deviceActivity) {
+ return null;
+ }
+
+ public AlertDialog getPermissionExplanationDialog(Activity deviceActivity) {
+ return requestPermissionDialog(deviceActivity,getRequiredPermissions(), permissionExplanation);
+ }
+
+ public boolean checkRequiredPermissions(){
+ if (!arePermissionsGranted(getRequiredPermissions())) {
+ return false;
+ }
+ return true;
+ }
+
}
diff --git a/src/org/kde/kdeconnect/Plugins/SftpPlugin/SftpPlugin.java b/src/org/kde/kdeconnect/Plugins/SftpPlugin/SftpPlugin.java
index 6842c00a..ee002070 100644
--- a/src/org/kde/kdeconnect/Plugins/SftpPlugin/SftpPlugin.java
+++ b/src/org/kde/kdeconnect/Plugins/SftpPlugin/SftpPlugin.java
@@ -20,8 +20,16 @@
package org.kde.kdeconnect.Plugins.SftpPlugin;
+import android.Manifest;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.pm.PackageManager;
import android.os.Environment;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+import android.util.Log;
+import org.json.JSONException;
import org.kde.kdeconnect.Helpers.StorageHelper;
import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.Plugins.Plugin;
@@ -38,6 +46,9 @@ public class SftpPlugin extends Plugin {
private static final SimpleSftpServer server = new SimpleSftpServer();
+
+
+
@Override
public String getDisplayName() {
return context.getResources().getString(R.string.pref_plugin_sftp);
@@ -75,50 +86,48 @@ public class SftpPlugin extends Plugin {
//Kept for compatibility, in case "multiPaths" is not possible or the other end does not support it
np2.set("path", Environment.getExternalStorageDirectory().getAbsolutePath());
- File root = new File("/");
- if (root.canExecute() && root.canRead()) {
- List storageList = StorageHelper.getStorageList();
- ArrayList paths = new ArrayList<>();
- ArrayList pathNames = new ArrayList<>();
+ List storageList = StorageHelper.getStorageList();
+ ArrayList paths = new ArrayList<>();
+ ArrayList pathNames = new ArrayList<>();
- for (StorageHelper.StorageInfo storage : storageList) {
- paths.add(storage.path);
- StringBuilder res = new StringBuilder();
+ for (StorageHelper.StorageInfo storage : storageList) {
+ paths.add(storage.path);
+ StringBuilder res = new StringBuilder();
- if (storageList.size() > 1) {
- if (!storage.removable) {
- res.append(context.getString(R.string.sftp_internal_storage));
- } else if (storage.number > 1) {
- res.append(context.getString(R.string.sftp_sdcard_num, storage.number));
- } else {
- res.append(context.getString(R.string.sftp_sdcard));
- }
+ if (storageList.size() > 1) {
+ if (!storage.removable) {
+ res.append(context.getString(R.string.sftp_internal_storage));
+ } else if (storage.number > 1) {
+ res.append(context.getString(R.string.sftp_sdcard_num, storage.number));
} else {
- res.append(context.getString(R.string.sftp_all_files));
+ res.append(context.getString(R.string.sftp_sdcard));
}
- String pathName = res.toString();
- if (storage.readonly) {
- res.append(" ");
- res.append(context.getString(R.string.sftp_readonly));
- }
- pathNames.add(res.toString());
+ } else {
+ res.append(context.getString(R.string.sftp_all_files));
+ }
+ String pathName = res.toString();
+ if (storage.readonly) {
+ res.append(" ");
+ res.append(context.getString(R.string.sftp_readonly));
+ }
+ pathNames.add(res.toString());
- //Shortcut for users that only want to browse camera pictures
- String dcim = storage.path + "/DCIM/Camera";
- if (new File(dcim).exists()) {
- paths.add(dcim);
- if (storageList.size() > 1) {
- pathNames.add(context.getString(R.string.sftp_camera) + "(" + pathName + ")");
- } else {
- pathNames.add(context.getString(R.string.sftp_camera));
- }
+ //Shortcut for users that only want to browse camera pictures
+ String dcim = storage.path + "/DCIM/Camera";
+ if (new File(dcim).exists()) {
+ paths.add(dcim);
+ if (storageList.size() > 1) {
+ pathNames.add(context.getString(R.string.sftp_camera) + "(" + pathName + ")");
+ } else {
+ pathNames.add(context.getString(R.string.sftp_camera));
}
}
+ }
+
+ if (paths.size() > 0) {
+ np2.set("multiPaths", paths);
+ np2.set("pathNames", pathNames);
- if (paths.size() > 0) {
- np2.set("multiPaths", paths);
- np2.set("pathNames", pathNames);
- }
}
device.sendPackage(np2);
@@ -129,6 +138,12 @@ public class SftpPlugin extends Plugin {
return false;
}
+ @Override
+ public String[] getRequiredPermissions() {
+ String[] perms = {Manifest.permission.READ_EXTERNAL_STORAGE};
+ return perms;
+ }
+
@Override
public String[] getSupportedPackageTypes() {
return new String[]{PACKAGE_TYPE_SFTP_REQUEST};
diff --git a/src/org/kde/kdeconnect/Plugins/SharePlugin/SharePlugin.java b/src/org/kde/kdeconnect/Plugins/SharePlugin/SharePlugin.java
index 62195aa6..8be61db0 100644
--- a/src/org/kde/kdeconnect/Plugins/SharePlugin/SharePlugin.java
+++ b/src/org/kde/kdeconnect/Plugins/SharePlugin/SharePlugin.java
@@ -20,7 +20,9 @@
package org.kde.kdeconnect.Plugins.SharePlugin;
+import android.Manifest;
import android.app.Activity;
+import android.app.AlertDialog;
import android.app.DownloadManager;
import android.app.Notification;
import android.app.NotificationManager;
@@ -30,6 +32,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
@@ -110,6 +113,17 @@ public class SharePlugin extends Plugin {
Log.i("SharePlugin", "hasPayload");
+ int permissionCheck = ContextCompat.checkSelfPermission(context,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE);
+
+ if(permissionCheck == PackageManager.PERMISSION_GRANTED) {
+
+ } else if(permissionCheck == PackageManager.PERMISSION_DENIED){
+ // TODO Request Permission for storage
+ Log.i("SharePlugin", "no Permission for Storage");
+ return false;
+ }
+
final InputStream input = np.getPayload();
final long fileLength = np.getPayloadSize();
final String originalFilename = np.getString("filename", Long.toString(System.currentTimeMillis()));
@@ -132,6 +146,8 @@ public class SharePlugin extends Plugin {
final OutputStream destinationOutput = context.getContentResolver().openOutputStream(destinationDocument.getUri());
final Uri destinationUri = destinationDocument.getUri();
+
+
final int notificationId = (int)System.currentTimeMillis();
Resources res = context.getResources();
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
@@ -192,20 +208,27 @@ public class SharePlugin extends Plugin {
.setAutoCancel(true)
.setProgress(100,100,false)
.setOngoing(false);
- if (successful) {
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setDataAndType(destinationUri, mimeType);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
- stackBuilder.addNextIntent(intent);
- PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
- builder.setContentText(res.getString(R.string.received_file_text, destinationDocument.getName()))
- .setContentIntent(resultPendingIntent);
+
+ // Nougat requires share:// URIs instead of file:// URIs
+ // TODO use FileProvider for >Nougat
+ if(Build.VERSION.SDK_INT < 24) {
+ if (successful) {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setDataAndType(destinationUri, mimeType);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
+ stackBuilder.addNextIntent(intent);
+ PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
+ builder.setContentText(res.getString(R.string.received_file_text, destinationDocument.getName()))
+ .setContentIntent(resultPendingIntent);
+ }
}
+
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (prefs.getBoolean("share_notification_preference", true)) {
builder.setDefaults(Notification.DEFAULT_ALL);
}
+
NotificationHelper.notifyCompat(notificationManager, notificationId, builder.build());
if (successful) {
@@ -409,5 +432,10 @@ public class SharePlugin extends Plugin {
return new String[]{PACKAGE_TYPE_SHARE_REQUEST};
}
+ @Override
+ public String[] getRequiredPermissions() {
+ String[] perms = {Manifest.permission.READ_EXTERNAL_STORAGE};
+ return perms;
+ }
}
diff --git a/src/org/kde/kdeconnect/Plugins/TelepathyPlugin/TelepathyPlugin.java b/src/org/kde/kdeconnect/Plugins/TelepathyPlugin/TelepathyPlugin.java
index 3ebf282f..a3610e1b 100644
--- a/src/org/kde/kdeconnect/Plugins/TelepathyPlugin/TelepathyPlugin.java
+++ b/src/org/kde/kdeconnect/Plugins/TelepathyPlugin/TelepathyPlugin.java
@@ -20,6 +20,10 @@
package org.kde.kdeconnect.Plugins.TelepathyPlugin;
+import android.Manifest;
+import android.content.pm.PackageManager;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
import android.telephony.SmsManager;
import android.util.Log;
@@ -43,11 +47,6 @@ public class TelepathyPlugin extends Plugin {
return context.getResources().getString(R.string.pref_plugin_telepathy_desc);
}
- @Override
- public boolean onCreate() {
- return true;
- }
-
@Override
public void onDestroy() {
}
@@ -63,8 +62,18 @@ public class TelepathyPlugin extends Plugin {
String phoneNo = np.getString("phoneNumber");
String sms = np.getString("messageBody");
try {
- SmsManager smsManager = SmsManager.getDefault();
- smsManager.sendTextMessage(phoneNo, null, sms, null, null);
+
+ int permissionCheck = ContextCompat.checkSelfPermission(context,
+ Manifest.permission.SEND_SMS);
+
+ if(permissionCheck == PackageManager.PERMISSION_GRANTED) {
+ SmsManager smsManager = SmsManager.getDefault();
+ smsManager.sendTextMessage(phoneNo, null, sms, null, null);
+ Log.d("TelepathyPlugin", "SMS sent");
+ } else if(permissionCheck == PackageManager.PERMISSION_DENIED){
+ // TODO Request Permission SEND_SMS
+ }
+
//TODO: Notify other end
} catch (Exception e) {
//TODO: Notify other end
@@ -176,4 +185,8 @@ public class TelepathyPlugin extends Plugin {
return new String[]{};
}
+ @Override
+ public String[] getRequiredPermissions() {
+ return new String[]{Manifest.permission.SEND_SMS/*, Manifest.permission.READ_CONTACTS*/};
+ }
}
diff --git a/src/org/kde/kdeconnect/Plugins/TelephonyPlugin/TelephonyPlugin.java b/src/org/kde/kdeconnect/Plugins/TelephonyPlugin/TelephonyPlugin.java
index 40cbd0b1..fcc04ee8 100644
--- a/src/org/kde/kdeconnect/Plugins/TelephonyPlugin/TelephonyPlugin.java
+++ b/src/org/kde/kdeconnect/Plugins/TelephonyPlugin/TelephonyPlugin.java
@@ -20,13 +20,16 @@
package org.kde.kdeconnect.Plugins.TelephonyPlugin;
+import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
+import android.support.v4.content.ContextCompat;
import android.telephony.SmsMessage;
import android.telephony.TelephonyManager;
import android.util.Log;
@@ -102,32 +105,40 @@ public class TelephonyPlugin extends Plugin {
//Log.e("TelephonyPlugin", "callBroadcastReceived");
- Map contactInfo = ContactsHelper.phoneNumberLookup(context, phoneNumber);
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_TELEPHONY);
- if (phoneNumber != null) {
- np.set("phoneNumber", phoneNumber);
- }
+ int permissionCheck = ContextCompat.checkSelfPermission(context,
+ Manifest.permission.READ_CONTACTS);
+
+ if(permissionCheck==PackageManager.PERMISSION_GRANTED) {
+
+ Map contactInfo = ContactsHelper.phoneNumberLookup(context, phoneNumber);
+
+ if (contactInfo.containsKey("name")) {
+ np.set("contactName", contactInfo.get("name"));
+ }
+
+ if (contactInfo.containsKey("photoID")) {
+ String photoUri = contactInfo.get("photoID");
+ if (photoUri != null) {
+ try {
+ String base64photo = ContactsHelper.photoId64Encoded(context, photoUri);
+ if (base64photo != null && !base64photo.isEmpty()) {
+ np.set("phoneThumbnail", base64photo);
+ }
+ } catch (Exception e) {
+ Log.e("TelephonyPlugin", "Failed to get contact photo");
+ }
+ }
+
+ }
- if (contactInfo.containsKey("name")) {
- np.set("contactName", contactInfo.get("name"));
} else {
np.set("contactName", phoneNumber);
}
- if (contactInfo.containsKey("photoID")) {
- String photoUri = contactInfo.get("photoID");
- if (photoUri != null) {
- try {
- String base64photo = ContactsHelper.photoId64Encoded(context, photoUri);
- if (base64photo != null && !base64photo.isEmpty()) {
- np.set("phoneThumbnail", base64photo);
- }
- } catch (Exception e) {
- Log.e("TelephonyPlugin", "Failed to get contact photo");
- }
- }
-
+ if (phoneNumber != null) {
+ np.set("phoneNumber", phoneNumber);
}
switch (state) {
@@ -208,18 +219,26 @@ public class TelephonyPlugin extends Plugin {
}
String phoneNumber = message.getOriginatingAddress();
- Map contactInfo = ContactsHelper.phoneNumberLookup(context, phoneNumber);
+
+ int permissionCheck = ContextCompat.checkSelfPermission(context,
+ Manifest.permission.READ_CONTACTS);
+
+ if(permissionCheck==PackageManager.PERMISSION_GRANTED) {
+ Map contactInfo = ContactsHelper.phoneNumberLookup(context, phoneNumber);
+
+ if (contactInfo.containsKey("name")) {
+ np.set("contactName", contactInfo.get("name"));
+ }
+
+ if (contactInfo.containsKey("photoID")) {
+ np.set("phoneThumbnail", ContactsHelper.photoId64Encoded(context, contactInfo.get("photoID")));
+ }
+ }
if (phoneNumber != null) {
np.set("phoneNumber", phoneNumber);
}
- if (contactInfo.containsKey("name")) {
- np.set("contactName", contactInfo.get("name"));
- }
- if (contactInfo.containsKey("photoID")) {
- np.set("phoneThumbnail", ContactsHelper.photoId64Encoded(context, contactInfo.get("photoID")));
- }
device.sendPackage(np);
}
@@ -267,4 +286,9 @@ public class TelephonyPlugin extends Plugin {
return new String[]{PACKAGE_TYPE_TELEPHONY};
}
+ @Override
+ public String[] getRequiredPermissions() {
+ return new String[]{Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_CONTACTS, Manifest.permission.SEND_SMS};
+ }
+
}
diff --git a/src/org/kde/kdeconnect/UserInterface/DeviceFragment.java b/src/org/kde/kdeconnect/UserInterface/DeviceFragment.java
index 820e8deb..b8332ba0 100644
--- a/src/org/kde/kdeconnect/UserInterface/DeviceFragment.java
+++ b/src/org/kde/kdeconnect/UserInterface/DeviceFragment.java
@@ -68,6 +68,7 @@ public class DeviceFragment extends Fragment {
Device device;
TextView errorHeader;
+ TextView noPermissionsHeader;
MaterialActivity mActivity;
@@ -389,6 +390,38 @@ public class DeviceFragment extends Fragment {
}
}
+ //Plugins without permissions List
+ final ConcurrentHashMap permissionsNeeded = device.getPluginsWithoutPermissions();
+ if (!permissionsNeeded.isEmpty()) {
+ if (noPermissionsHeader == null) {
+ noPermissionsHeader = new TextView(mActivity);
+ noPermissionsHeader.setPadding(
+ 0,
+ ((int) (28 * getResources().getDisplayMetrics().density)),
+ 0,
+ ((int) (8 * getResources().getDisplayMetrics().density))
+ );
+ noPermissionsHeader.setOnClickListener(null);
+ noPermissionsHeader.setOnLongClickListener(null);
+ noPermissionsHeader.setText(getResources().getString(R.string.plugins_need_permission));
+ }
+ items.add(new CustomItem(noPermissionsHeader));
+ for (Map.Entry entry : permissionsNeeded.entrySet()) {
+ String pluginKey = entry.getKey();
+ final Plugin plugin = entry.getValue();
+ if (plugin == null) {
+ items.add(new SmallEntryItem(pluginKey));
+ } else {
+ items.add(new SmallEntryItem(plugin.getDisplayName(), new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ plugin.getPermissionExplanationDialog(mActivity).show();
+ }
+ }));
+ }
+ }
+ }
+
ListView buttonsList = (ListView) rootView.findViewById(R.id.buttons_list);
ListAdapter adapter = new ListAdapter(mActivity, items);
buttonsList.setAdapter(adapter);
diff --git a/src/org/kde/kdeconnect/UserInterface/MaterialActivity.java b/src/org/kde/kdeconnect/UserInterface/MaterialActivity.java
index 4eeed193..d8201032 100644
--- a/src/org/kde/kdeconnect/UserInterface/MaterialActivity.java
+++ b/src/org/kde/kdeconnect/UserInterface/MaterialActivity.java
@@ -1,14 +1,17 @@
package org.kde.kdeconnect.UserInterface;
+import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.NavigationView;
+import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
@@ -294,6 +297,22 @@ public class MaterialActivity extends AppCompatActivity {
}
}
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ for (int result : grantResults) {
+ if (result == PackageManager.PERMISSION_GRANTED) {
+ //New permission granted, reload plugins
+ BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
+ @Override
+ public void onServiceStart(BackgroundService service) {
+ Device device = service.getDevice(mCurrentDevice);
+ device.reloadPluginsFromSettings();
+ }
+ });
+ }
+ }
+ }
+
public void renameDevice() {
final TextView nameView = (TextView) mNavigationView.findViewById(R.id.device_name);
final EditText deviceNameEdit = new EditText(MaterialActivity.this);