mirror of
https://github.com/etesync/android
synced 2025-02-16 17:42:03 +00:00
Implement Android 6-style permissions
* increase target API level to 23 (Android 6), which makes Android 6-style permissions mandatory * AUTHENTICATE_ACCOUNTS permission is only required up to API level 22 * new activity: PermissionsActivity which shows missing permissions and provides buttons to request them * DavService: Android shouldn't send a null Intent, but sometimes it does, so implement null check * LocalTaskList: tasksProviderAvailable may return true on API level 23+ even if permissions are not sufficient * SyncAdapterService: show a notification (with Intent for PermissionsActivity) when permissions are not sufficient * when creating accounts, set OpenTasks sync always to true if API level is 23+ (even if OpenTasks is not installed [yet]) * update Lombok
This commit is contained in:
parent
59252d7471
commit
61231b4233
@ -15,7 +15,7 @@ android {
|
||||
defaultConfig {
|
||||
applicationId "at.bitfire.davdroid"
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 22
|
||||
targetSdkVersion 23
|
||||
|
||||
versionCode 96
|
||||
versionName "1.0.6"
|
||||
@ -60,7 +60,7 @@ configurations.all {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
provided 'org.projectlombok:lombok:1.16.6'
|
||||
provided 'org.projectlombok:lombok:1.16.8'
|
||||
|
||||
compile project(':dav4android')
|
||||
compile project(':ical4android')
|
||||
|
@ -21,6 +21,10 @@
|
||||
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
|
||||
|
||||
<!-- legacy permissions -->
|
||||
<uses-permission
|
||||
android:name="android.permission.AUTHENTICATE_ACCOUNTS"
|
||||
android:maxSdkVersion="22"
|
||||
tools:ignore="UnusedAttribute"/>
|
||||
<!--
|
||||
for writing external log files; permission only required for SDK <= 18 because since then,
|
||||
writing to app-private directory doesn't require extra permissions
|
||||
@ -35,7 +39,6 @@
|
||||
tools:ignore="UnusedAttribute"/>
|
||||
|
||||
<!-- other permissions -->
|
||||
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS"/>
|
||||
<!-- android.permission-group.CONTACTS -->
|
||||
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
|
||||
@ -137,7 +140,7 @@
|
||||
<action android:name="android.accounts.LOGIN_ACCOUNTS_CHANGED"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
|
||||
<activity
|
||||
android:name=".ui.AccountsActivity"
|
||||
android:label="@string/app_name"
|
||||
@ -159,6 +162,10 @@
|
||||
android:label="@string/app_settings"
|
||||
android:parentActivityName=".ui.AccountsActivity"/>
|
||||
|
||||
<activity android:name=".ui.PermissionsActivity"
|
||||
android:label="@string/permissions_title"
|
||||
android:parentActivityName=".ui.AccountsActivity"/>
|
||||
|
||||
<activity
|
||||
android:name=".ui.setup.LoginActivity"
|
||||
android:label="@string/login_title"
|
||||
|
@ -21,7 +21,8 @@ public class Constants {
|
||||
NOTIFICATION_REFRESH_COLLECTIONS = 2,
|
||||
NOTIFICATION_CONTACTS_SYNC = 10,
|
||||
NOTIFICATION_CALENDAR_SYNC = 11,
|
||||
NOTIFICATION_TASK_SYNC = 12;
|
||||
NOTIFICATION_TASK_SYNC = 12,
|
||||
NOTIFICATION_PERMISSIONS = 20;
|
||||
|
||||
public static final Uri webUri = Uri.parse("https://davdroid.bitfire.at/?pk_campaign=davdroid-app");
|
||||
|
||||
|
@ -72,20 +72,22 @@ public class DavService extends Service {
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
String action = intent.getAction();
|
||||
long id = intent.getLongExtra(EXTRA_DAV_SERVICE_ID, -1);
|
||||
if (intent != null) {
|
||||
String action = intent.getAction();
|
||||
long id = intent.getLongExtra(EXTRA_DAV_SERVICE_ID, -1);
|
||||
|
||||
switch (action) {
|
||||
case ACTION_ACCOUNTS_UPDATED:
|
||||
cleanupAccounts();
|
||||
break;
|
||||
case ACTION_REFRESH_COLLECTIONS:
|
||||
if (runningRefresh.add(id)) {
|
||||
new Thread(new RefreshCollections(id)).start();
|
||||
for (RefreshingStatusListener listener : refreshingStatusListeners)
|
||||
listener.onDavRefreshStatusChanged(id, true);
|
||||
}
|
||||
break;
|
||||
switch (action) {
|
||||
case ACTION_ACCOUNTS_UPDATED:
|
||||
cleanupAccounts();
|
||||
break;
|
||||
case ACTION_REFRESH_COLLECTIONS:
|
||||
if (runningRefresh.add(id)) {
|
||||
new Thread(new RefreshCollections(id)).start();
|
||||
for (RefreshingStatusListener listener : refreshingStatusListeners)
|
||||
listener.onDavRefreshStatusChanged(id, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return START_NOT_STICKY;
|
||||
|
@ -9,10 +9,11 @@
|
||||
package at.bitfire.davdroid.resource;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.RemoteException;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
@ -29,7 +30,6 @@ import at.bitfire.ical4android.AndroidTaskListFactory;
|
||||
import at.bitfire.ical4android.CalendarStorageException;
|
||||
import at.bitfire.ical4android.TaskProvider;
|
||||
import lombok.Cleanup;
|
||||
import okhttp3.HttpUrl;
|
||||
|
||||
public class LocalTaskList extends AndroidTaskList implements LocalCollection {
|
||||
|
||||
@ -134,12 +134,16 @@ public class LocalTaskList extends AndroidTaskList implements LocalCollection {
|
||||
|
||||
// helpers
|
||||
|
||||
public static boolean tasksProviderAvailable(@NonNull ContentResolver resolver) {
|
||||
public static boolean tasksProviderAvailable(@NonNull Context context) {
|
||||
if (tasksProviderAvailable != null)
|
||||
return tasksProviderAvailable;
|
||||
else {
|
||||
@Cleanup TaskProvider provider = TaskProvider.acquire(resolver, TaskProvider.ProviderName.OpenTasks);
|
||||
return tasksProviderAvailable = (provider != null);
|
||||
if (Build.VERSION.SDK_INT >= 23)
|
||||
return context.getPackageManager().resolveContentProvider(TaskProvider.ProviderName.OpenTasks.authority, 0) != null;
|
||||
else {
|
||||
@Cleanup TaskProvider provider = TaskProvider.acquire(context.getContentResolver(), TaskProvider.ProviderName.OpenTasks);
|
||||
return tasksProviderAvailable = (provider != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,13 +9,16 @@
|
||||
package at.bitfire.davdroid.syncadapter;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.AbstractThreadedSyncAdapter;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SyncResult;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.wifi.WifiInfo;
|
||||
@ -24,18 +27,15 @@ import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.net.ConnectivityManagerCompat;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.text.WordUtils;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
import at.bitfire.davdroid.AccountSettings;
|
||||
import at.bitfire.davdroid.App;
|
||||
import at.bitfire.davdroid.InvalidAccountException;
|
||||
import at.bitfire.davdroid.model.ServiceDB;
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.ui.PermissionsActivity;
|
||||
|
||||
public abstract class SyncAdapterService extends Service {
|
||||
|
||||
@ -62,6 +62,27 @@ public abstract class SyncAdapterService extends Service {
|
||||
Thread.currentThread().setContextClassLoader(getContext().getClassLoader());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSecurityException(Account account, Bundle extras, String authority, SyncResult syncResult) {
|
||||
App.log.log(Level.WARNING, "Security exception when opening content provider for " + authority);
|
||||
syncResult.databaseError = true;
|
||||
|
||||
Intent intent = new Intent(getContext(), PermissionsActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
||||
Notification notify = new NotificationCompat.Builder(getContext())
|
||||
.setSmallIcon(R.drawable.ic_error_light)
|
||||
.setLargeIcon(((BitmapDrawable)getContext().getResources().getDrawable(R.drawable.ic_launcher)).getBitmap())
|
||||
.setContentTitle("DAVdroid permissions")
|
||||
.setContentText("Additional permissions are required.")
|
||||
.setContentIntent(PendingIntent.getActivity(getContext(), 0, intent, PendingIntent.FLAG_CANCEL_CURRENT))
|
||||
.setCategory(NotificationCompat.CATEGORY_ERROR)
|
||||
.setLocalOnly(true)
|
||||
.build();
|
||||
NotificationManager nm = (NotificationManager)getContext().getSystemService(NOTIFICATION_SERVICE);
|
||||
nm.notify(Constants.NOTIFICATION_PERMISSIONS, notify);
|
||||
}
|
||||
|
||||
protected boolean checkSyncConditions(@NonNull AccountSettings settings) {
|
||||
if (settings.getSyncWifiOnly()) {
|
||||
ConnectivityManager cm = (ConnectivityManager)getContext().getSystemService(CONNECTIVITY_SERVICE);
|
||||
|
@ -15,6 +15,7 @@ import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SyncResult;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.NotificationCompat;
|
||||
@ -223,7 +224,8 @@ abstract public class SyncManager {
|
||||
detailsIntent.setData(Uri.parse("uri://" + getClass().getName() + "/" + uniqueCollectionId));
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
|
||||
builder .setSmallIcon(R.drawable.ic_launcher)
|
||||
builder .setSmallIcon(R.drawable.ic_error_light)
|
||||
.setLargeIcon(((BitmapDrawable)context.getResources().getDrawable(R.drawable.ic_launcher)).getBitmap())
|
||||
.setContentTitle(getSyncErrorTitle())
|
||||
.setContentIntent(PendingIntent.getActivity(context, 0, detailsIntent, PendingIntent.FLAG_CANCEL_CURRENT))
|
||||
.setCategory(NotificationCompat.CATEGORY_ERROR)
|
||||
|
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.ui;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.View;
|
||||
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.resource.LocalTaskList;
|
||||
|
||||
public class PermissionsActivity extends AppCompatActivity {
|
||||
|
||||
public static final String
|
||||
PERMISSION_READ_TASKS = "org.dmfs.permission.READ_TASKS",
|
||||
PERMISSION_WRITE_TASKS = "org.dmfs.permission.WRITE_TASKS";
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_permissions);
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
protected void refresh() {
|
||||
boolean noCalendarPermissions =
|
||||
ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) != PackageManager.PERMISSION_GRANTED ||
|
||||
ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_CALENDAR) != PackageManager.PERMISSION_GRANTED;
|
||||
findViewById(R.id.calendar_permissions).setVisibility(noCalendarPermissions ? View.VISIBLE : View.GONE);
|
||||
|
||||
boolean noContactsPermissions =
|
||||
ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED ||
|
||||
ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_CONTACTS) != PackageManager.PERMISSION_GRANTED;
|
||||
findViewById(R.id.contacts_permissions).setVisibility(noContactsPermissions ? View.VISIBLE : View.GONE);
|
||||
|
||||
boolean noTaskPermissions;
|
||||
if (LocalTaskList.tasksProviderAvailable(this)) {
|
||||
noTaskPermissions =
|
||||
ActivityCompat.checkSelfPermission(this, PERMISSION_READ_TASKS) != PackageManager.PERMISSION_GRANTED ||
|
||||
ActivityCompat.checkSelfPermission(this, PERMISSION_WRITE_TASKS) != PackageManager.PERMISSION_GRANTED;
|
||||
findViewById(R.id.opentasks_permissions).setVisibility(noTaskPermissions ? View.VISIBLE : View.GONE);
|
||||
} else {
|
||||
findViewById(R.id.opentasks_permissions).setVisibility(View.GONE);
|
||||
noTaskPermissions = false;
|
||||
}
|
||||
|
||||
if (!noCalendarPermissions && !noContactsPermissions && !noTaskPermissions) {
|
||||
NotificationManager nm = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
|
||||
nm.cancel(Constants.NOTIFICATION_PERMISSIONS);
|
||||
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
public void requestCalendarPermissions(View v) {
|
||||
ActivityCompat.requestPermissions(this, new String[] {
|
||||
Manifest.permission.READ_CALENDAR,
|
||||
Manifest.permission.WRITE_CALENDAR
|
||||
}, 0);
|
||||
}
|
||||
|
||||
public void requestContactsPermissions(View v) {
|
||||
ActivityCompat.requestPermissions(this, new String[] {
|
||||
Manifest.permission.READ_CONTACTS,
|
||||
Manifest.permission.WRITE_CONTACTS
|
||||
}, 0);
|
||||
}
|
||||
|
||||
public void requestOpenTasksPermissions(View v) {
|
||||
ActivityCompat.requestPermissions(this, new String[] {
|
||||
PERMISSION_READ_TASKS,
|
||||
PERMISSION_WRITE_TASKS
|
||||
}, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
refresh();
|
||||
}
|
||||
}
|
@ -70,7 +70,7 @@ public class StartupDialogFragment extends DialogFragment {
|
||||
}
|
||||
|
||||
// OpenTasks information
|
||||
if (!LocalTaskList.tasksProviderAvailable(context.getContentResolver()) &&
|
||||
if (!LocalTaskList.tasksProviderAvailable(context) &&
|
||||
settings.getBoolean(HINT_OPENTASKS_NOT_INSTALLED, true))
|
||||
dialogs.add(StartupDialogFragment.instantiate(Mode.OPENTASKS_NOT_INSTALLED));
|
||||
|
||||
@ -177,7 +177,8 @@ public class StartupDialogFragment extends DialogFragment {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=org.dmfs.tasks"));
|
||||
getContext().startActivity(intent);
|
||||
if (intent.resolveActivity(getContext().getPackageManager()) != null)
|
||||
getContext().startActivity(intent);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.startup_dont_show_again, new DialogInterface.OnClickListener() {
|
||||
|
@ -14,6 +14,7 @@ import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Intent;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.CalendarContract;
|
||||
import android.provider.ContactsContract;
|
||||
@ -107,13 +108,13 @@ public class AccountDetailsFragment extends Fragment {
|
||||
App.log.log(Level.INFO, "Writing account configuration to database", config);
|
||||
@Cleanup OpenHelper dbHelper = new OpenHelper(getContext());
|
||||
SQLiteDatabase db = dbHelper.getWritableDatabase();
|
||||
db.beginTransactionNonExclusive();
|
||||
try {
|
||||
AccountSettings settings = new AccountSettings(getContext(), account);
|
||||
|
||||
Intent refreshIntent = new Intent(getActivity(), DavService.class);
|
||||
refreshIntent.setAction(DavService.ACTION_REFRESH_COLLECTIONS);
|
||||
|
||||
db.beginTransactionNonExclusive();
|
||||
if (config.cardDAV != null) {
|
||||
long id = insertService(db, accountName, Services.SERVICE_CARDDAV, config.cardDAV);
|
||||
refreshIntent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, id);
|
||||
@ -132,13 +133,12 @@ public class AccountDetailsFragment extends Fragment {
|
||||
ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, 1);
|
||||
settings.setSyncInterval(CalendarContract.AUTHORITY, DEFAULT_SYNC_INTERVAL);
|
||||
|
||||
if (LocalTaskList.tasksProviderAvailable(getContext().getContentResolver())) {
|
||||
// will only do something if OpenTasks is installed and accessible
|
||||
if (Build.VERSION.SDK_INT >= 23 || LocalTaskList.tasksProviderAvailable(getContext())) {
|
||||
ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 1);
|
||||
settings.setSyncInterval(TaskProvider.ProviderName.OpenTasks.authority, DEFAULT_SYNC_INTERVAL);
|
||||
} else
|
||||
// If OpenTasks is installed after DAVdroid, DAVdroid won't get task permissions and crash at every task sync
|
||||
// unless we disable task sync here (before OpenTasks is available).
|
||||
// Android <6 only: disable OpenTasks sync forever when OpenTasks is not installed
|
||||
// because otherwise, there will be a non-catchable SecurityException as soon as OpenTasks is installed
|
||||
ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 0);
|
||||
} else {
|
||||
ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, 0);
|
||||
|
103
app/src/main/res/layout/activity_permissions.xml
Normal file
103
app/src/main/res/layout/activity_permissions.xml
Normal file
@ -0,0 +1,103 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
~ All rights reserved. This program and the accompanying materials
|
||||
~ are made available under the terms of the GNU Public License v3.0
|
||||
~ which accompanies this distribution, and is available at
|
||||
~ http://www.gnu.org/licenses/gpl.html
|
||||
-->
|
||||
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="@dimen/activity_margin">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/calendar_permissions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginBottom="16dp"
|
||||
tools:ignore="UselessParent">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/TextView.Heading"
|
||||
android:text="@string/permissions_calendar"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/permissions_calendar_details"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/permissions_calendar_request"
|
||||
android:onClick="requestCalendarPermissions"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/contacts_permissions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginBottom="16dp"
|
||||
tools:ignore="UselessParent">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/TextView.Heading"
|
||||
android:text="@string/permissions_contacts"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/permissions_contacts_details"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/permissions_contacts_request"
|
||||
android:onClick="requestContactsPermissions"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/opentasks_permissions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
tools:ignore="UselessParent">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/TextView.Heading"
|
||||
android:text="@string/permissions_opentasks"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/permissions_opentasks_details"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/permissions_opentasks_request"
|
||||
android:onClick="requestOpenTasksPermissions"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
@ -94,6 +94,18 @@
|
||||
<string name="account_refresh_calendar_list">Refresh calendar list</string>
|
||||
<string name="account_create_new_calendar">Create new calendar</string>
|
||||
|
||||
<!-- PermissionsActivity -->
|
||||
<string name="permissions_title">DAVdroid permissions</string>
|
||||
<string name="permissions_calendar">Calendar permissions</string>
|
||||
<string name="permissions_calendar_details">To synchronize CalDAV events with your local calendars, DAVdroid needs to access your calendars.</string>
|
||||
<string name="permissions_calendar_request">Request calendar permissions</string>
|
||||
<string name="permissions_contacts">Contacts permissions</string>
|
||||
<string name="permissions_contacts_details">To synchronize CardDAV address books with your local contacts, DAVdroid needs to access your contacts.</string>
|
||||
<string name="permissions_contacts_request">Request contacts permissions</string>
|
||||
<string name="permissions_opentasks">OpenTasks permissions</string>
|
||||
<string name="permissions_opentasks_details">To synchronize CalDAV tasks with your local task lists, DAVdroid needs to access OpenTasks.</string>
|
||||
<string name="permissions_opentasks_request">Request OpenTasks permissions</string>
|
||||
|
||||
<!-- AddAccountActivity -->
|
||||
<string name="login_title">Add account</string>
|
||||
<string name="login_type_email">Login with email address</string>
|
||||
@ -159,13 +171,6 @@
|
||||
<item>Every 4 hours</item>
|
||||
<item>Once a day</item>
|
||||
</string-array>
|
||||
<string name="settings_sync_time_range_past">Past event time limit</string>
|
||||
<string name="settings_sync_time_range_past_none">All events will be synchronized</string>
|
||||
<plurals name="settings_sync_time_range_past_days">
|
||||
<item quantity="one">Events more than one day in the past will be ignored</item>
|
||||
<item quantity="other">Events more than %d days in the past will be ignored</item>
|
||||
</plurals>
|
||||
<string name="settings_sync_time_range_past_message">Events which are more than this number of days in the past will be ignored (may be 0). Leave blank to synchronize all events.</string>
|
||||
<string name="settings_sync_wifi_only">Sync over WiFi only</string>
|
||||
<string name="settings_sync_wifi_only_on">Synchronization is restricted to WiFi connections</string>
|
||||
<string name="settings_sync_wifi_only_off">Connection type is not taken into consideration</string>
|
||||
@ -173,6 +178,14 @@
|
||||
<string name="settings_sync_wifi_only_ssid_on">Will only synchronize over %s</string>
|
||||
<string name="settings_sync_wifi_only_ssid_off">All WiFi connections may be used</string>
|
||||
<string name="settings_sync_wifi_only_ssid_message">Enter the name of a WiFi network (SSID) to restrict synchronization to this network, or leave blank for all WiFi connections.</string>
|
||||
<string name="settings_caldav">CalDAV</string>
|
||||
<string name="settings_sync_time_range_past">Past event time limit</string>
|
||||
<string name="settings_sync_time_range_past_none">All events will be synchronized</string>
|
||||
<plurals name="settings_sync_time_range_past_days">
|
||||
<item quantity="one">Events more than one day in the past will be ignored</item>
|
||||
<item quantity="other">Events more than %d days in the past will be ignored</item>
|
||||
</plurals>
|
||||
<string name="settings_sync_time_range_past_message">Events which are more than this number of days in the past will be ignored (may be 0). Leave blank to synchronize all events.</string>
|
||||
<string name="settings_manage_calendar_colors">Manage calendar colors</string>
|
||||
<string name="settings_manage_calendar_colors_on">Calendar colors are managed by DAVdroid</string>
|
||||
<string name="settings_manage_calendar_colors_off">Calendar colors are not set by DAVdroid</string>
|
||||
|
@ -74,7 +74,7 @@
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="CalDAV">
|
||||
<PreferenceCategory android:title="@string/settings_caldav">
|
||||
|
||||
<EditTextPreference
|
||||
android:key="caldav_time_range_past_days"
|
||||
|
@ -12,7 +12,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:1.5.0+'
|
||||
classpath 'com.android.tools.build:gradle:2.0.0+'
|
||||
}
|
||||
}
|
||||
|
||||
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
||||
#Fri Oct 30 11:00:03 CET 2015
|
||||
#Sat Apr 09 22:18:11 CEST 2016
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 19ef2603edc7a28bce1b9ea1f7fa11ca89234dc0
|
||||
Subproject commit a9495e136733e44f3276f08c049def2584aac3a4
|
@ -1 +1 @@
|
||||
Subproject commit 4eab186a210f7fdb74f17af2b086fc17ba2291c7
|
||||
Subproject commit 80b0f298076766df5844c5209a260423ebd1a8d3
|
Loading…
Reference in New Issue
Block a user