diff --git a/ring-android/app/src/main/AndroidManifest.xml b/ring-android/app/src/main/AndroidManifest.xml index 03ec0896a0cce8d6ef6b408a018c189f37129c6c..26424f6d036cc15657f2469e247b8ff83aaa2985 100644 --- a/ring-android/app/src/main/AndroidManifest.xml +++ b/ring-android/app/src/main/AndroidManifest.xml @@ -49,6 +49,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> + <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-feature android:name="android.hardware.wifi" diff --git a/ring-android/app/src/main/java/cx/ring/application/RingApplication.java b/ring-android/app/src/main/java/cx/ring/application/RingApplication.java index da60bb5f90cbee622b3b6555043e1bb71b384ed3..046e96389b831f90d1367a7b473ed31cd5f4cb76 100644 --- a/ring-android/app/src/main/java/cx/ring/application/RingApplication.java +++ b/ring-android/app/src/main/java/cx/ring/application/RingApplication.java @@ -44,7 +44,6 @@ import javax.inject.Inject; import javax.inject.Named; import androidx.annotation.RequiresApi; -import androidx.appcompat.app.AppCompatDelegate; import cx.ring.BuildConfig; import cx.ring.contacts.AvatarFactory; import cx.ring.daemon.Ringservice; @@ -235,7 +234,12 @@ public abstract class RingApplication extends Application { public void startDaemon() { if (!DRingService.isRunning) { try { - startService(new Intent(this, DRingService.class)); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O + && mPreferencesService.getSettings().isAllowPersistentNotification()) { + startForegroundService(new Intent(this, DRingService.class)); + } else { + startService(new Intent(this, DRingService.class)); + } } catch (Exception e) { Log.w(TAG, "Error starting daemon service"); } diff --git a/ring-android/app/src/main/java/cx/ring/service/DRingService.java b/ring-android/app/src/main/java/cx/ring/service/DRingService.java index be443d88e12243a7f3392f22d1782a7d2fe42c60..82a425ee123ebeb5fda6a2f4bcaf634a68f901b6 100644 --- a/ring-android/app/src/main/java/cx/ring/service/DRingService.java +++ b/ring-android/app/src/main/java/cx/ring/service/DRingService.java @@ -23,6 +23,7 @@ */ package cx.ring.service; +import android.app.Notification; import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; @@ -54,6 +55,7 @@ import java.util.concurrent.ScheduledExecutorService; import javax.inject.Inject; import javax.inject.Named; +import javax.inject.Singleton; import cx.ring.BuildConfig; import cx.ring.application.RingApplication; @@ -63,6 +65,7 @@ import cx.ring.facades.ConversationFacade; import cx.ring.fragments.ConversationFragment; import cx.ring.model.Account; import cx.ring.model.Codec; +import cx.ring.model.Settings; import cx.ring.model.SipCall; import cx.ring.model.Uri; import cx.ring.services.AccountService; @@ -74,13 +77,13 @@ import cx.ring.services.DeviceRuntimeService; import cx.ring.services.HardwareService; import cx.ring.services.HistoryService; import cx.ring.services.NotificationService; -import cx.ring.services.NotificationServiceImpl; import cx.ring.services.PreferencesService; import cx.ring.tv.call.TVCallActivity; import cx.ring.utils.DeviceUtils; import io.reactivex.disposables.CompositeDisposable; public class DRingService extends Service { + private static final String TAG = DRingService.class.getSimpleName(); public static final String ACTION_TRUST_REQUEST_ACCEPT = BuildConfig.APPLICATION_ID + ".action.TRUST_REQUEST_ACCEPT"; public static final String ACTION_TRUST_REQUEST_REFUSE = BuildConfig.APPLICATION_ID + ".action.TRUST_REQUEST_REFUSE"; @@ -107,20 +110,40 @@ public class DRingService extends Service { public static final String PUSH_RECEIVED_FIELD_DATA = "data"; public static final String PUSH_TOKEN_FIELD_TOKEN = "token"; - private static final String TAG = DRingService.class.getSimpleName(); + private static final int NOTIFICATION_ID = 1; + private final ContactsContentObserver contactContentObserver = new ContactsContentObserver(); - @Inject + @Inject @Singleton protected DaemonService mDaemonService; - @Inject + @Inject @Singleton protected CallService mCallService; - @Inject + @Inject @Singleton protected ConferenceService mConferenceService; - @Inject + @Inject @Singleton protected AccountService mAccountService; - @Inject + @Inject @Singleton protected HardwareService mHardwareService; - @Inject + @Inject @Singleton protected HistoryService mHistoryService; + @Inject @Singleton + protected DeviceRuntimeService mDeviceRuntimeService; + @Inject @Singleton + protected NotificationService mNotificationService; + @Inject @Singleton + protected ContactService mContactService; + @Inject @Singleton + protected PreferencesService mPreferencesService; + @Inject @Singleton + protected ConversationFacade mConversationFacade; + @Inject @Singleton + @Named("DaemonExecutor") + protected ScheduledExecutorService mExecutor; + + private final Handler mHandler = new Handler(); + private final CompositeDisposable mDisposableBag = new CompositeDisposable(); + private final Runnable mConnectivityChecker = this::updateConnectivityState; + public static boolean isRunning = false; + protected final IDRingService.Stub mBinder = new IDRingService.Stub() { @Override @@ -499,29 +522,6 @@ public class DRingService extends Service { mAccountService.registerName(account, password, name); } }; - @Inject - protected DeviceRuntimeService mDeviceRuntimeService; - @Inject - protected NotificationService mNotificationService; - @Inject - protected ContactService mContactService; - @Inject - protected PreferencesService mPreferencesService; - @Inject - protected ConversationFacade mConversationFacade; - @Inject - @Named("DaemonExecutor") - protected ScheduledExecutorService mExecutor; - - private final Handler mHandler = new Handler(); - private final Runnable mConnectivityChecker = new Runnable() { - @Override - public void run() { - updateConnectivityState(); - } - }; - - public static boolean isRunning = false; private final BroadcastReceiver receiver = new BroadcastReceiver() { @Override @@ -550,8 +550,6 @@ public class DRingService extends Service { } }; - private final CompositeDisposable mDisposableBag = new CompositeDisposable(); - @Override public void onCreate() { Log.i(TAG, "onCreated"); @@ -578,6 +576,7 @@ public class DRingService extends Service { mDisposableBag.add(mPreferencesService.getSettingsSubject().subscribe(s -> { refreshContacts(); updateConnectivityState(); + showSystemNotification(s); })); mDisposableBag.add(mCallService.getCallSubject().subscribe(call -> { SipCall.State newState = call.getCallState(); @@ -608,6 +607,14 @@ public class DRingService extends Service { isRunning = false; } + private void showSystemNotification(Settings settings) { + if (settings.isAllowPersistentNotification()) { + startForeground(NOTIFICATION_ID, (Notification) mNotificationService.getServiceNotification()); + } else { + stopForeground(true); + } + } + @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i(TAG, "onStartCommand " + (intent == null ? "null" : intent.getAction()) + " " + flags + " " + startId); @@ -732,8 +739,8 @@ public class DRingService extends Service { } private void handleTrustRequestAction(String action, Bundle extras) { - String account = extras.getString(NotificationServiceImpl.TRUST_REQUEST_NOTIFICATION_ACCOUNT_ID); - Uri from = new Uri(extras.getString(NotificationServiceImpl.TRUST_REQUEST_NOTIFICATION_FROM)); + String account = extras.getString(NotificationService.TRUST_REQUEST_NOTIFICATION_ACCOUNT_ID); + Uri from = new Uri(extras.getString(NotificationService.TRUST_REQUEST_NOTIFICATION_FROM)); if (account != null) { mNotificationService.cancelTrustRequestNotification(account); switch (action) { @@ -752,7 +759,7 @@ public class DRingService extends Service { } private void handleCallAction(String action, Bundle extras) { - String callId = extras.getString(NotificationServiceImpl.KEY_CALL_ID); + String callId = extras.getString(NotificationService.KEY_CALL_ID); if (callId == null || callId.isEmpty()) { return; diff --git a/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.java b/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.java index 55ef1aa075c63f1009303a5e56800087eda615eb..e0c7fa93a059d94fb1f7f1a8be6764b3948a47fc 100644 --- a/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.java +++ b/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.java @@ -70,7 +70,7 @@ import cx.ring.utils.FileUtils; import cx.ring.utils.Log; import cx.ring.utils.ResourceMapper; -public class NotificationServiceImpl extends NotificationService { +public class NotificationServiceImpl implements NotificationService { private static final String TAG = NotificationServiceImpl.class.getSimpleName(); @@ -84,6 +84,7 @@ public class NotificationServiceImpl extends NotificationService { private static final String NOTIF_CHANNEL_REQUEST = "requests"; private static final String NOTIF_CHANNEL_FILE_TRANSFER = "file_transfer"; private static final String NOTIF_CHANNEL_MISSED_CALL = "missed_call"; + private static final String NOTIF_CHANNEL_SERVICE = "service"; private final SparseArray<NotificationCompat.Builder> mNotificationBuilders = new SparseArray<>(); @Inject @@ -152,6 +153,15 @@ public class NotificationServiceImpl extends NotificationService { missedCallsChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); missedCallsChannel.setSound(null, null); notificationManager.createNotificationChannel(missedCallsChannel); + + // Background service channel + NotificationChannel backgroundChannel = new NotificationChannel(NOTIF_CHANNEL_SERVICE, mContext.getString(R.string.notif_channel_background_service), NotificationManager.IMPORTANCE_LOW); + backgroundChannel.setDescription(mContext.getString(R.string.notif_channel_background_service_descr)); + backgroundChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET); + backgroundChannel.enableLights(false); + backgroundChannel.enableVibration(false); + backgroundChannel.setShowBadge(false); + notificationManager.createNotificationChannel(backgroundChannel); } @Override @@ -543,6 +553,25 @@ public class NotificationServiceImpl extends NotificationService { notificationManager.notify(notificationId, messageNotificationBuilder.build()); } + @Override + public Object getServiceNotification() { + Intent intentHome = new Intent(Intent.ACTION_VIEW) + .setClass(mContext, HomeActivity.class) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + PendingIntent pendIntent = PendingIntent.getActivity(mContext, 0, intentHome, PendingIntent.FLAG_UPDATE_CURRENT); + NotificationCompat.Builder messageNotificationBuilder = new NotificationCompat.Builder(mContext, NotificationServiceImpl.NOTIF_CHANNEL_SERVICE); + messageNotificationBuilder + .setContentTitle(mContext.getText(R.string.app_name)) + .setContentText(mContext.getText(R.string.notif_background_service)) + .setSmallIcon(R.drawable.ic_ring_logo_white) + .setContentIntent(pendIntent) + .setVisibility(NotificationCompat.VISIBILITY_SECRET) + .setPriority(NotificationCompat.PRIORITY_MIN) + .setOngoing(true) + .setCategory(NotificationCompat.CATEGORY_SERVICE); + return messageNotificationBuilder.build(); + } + @Override public void cancelTextNotification(Uri contact) { if (contact == null) { diff --git a/ring-android/app/src/main/java/cx/ring/services/SharedPreferencesServiceImpl.java b/ring-android/app/src/main/java/cx/ring/services/SharedPreferencesServiceImpl.java index 7260c312449779cef8e2eff93f4e574853b317ce..bd01b9598dbabddfda6b22a9ddcbc8bf75557682 100644 --- a/ring-android/app/src/main/java/cx/ring/services/SharedPreferencesServiceImpl.java +++ b/ring-android/app/src/main/java/cx/ring/services/SharedPreferencesServiceImpl.java @@ -42,6 +42,7 @@ public class SharedPreferencesServiceImpl extends PreferencesService { private static final String RING_REQUESTS = "ring_requests"; private static final String RING_MOBILE_DATA = "mobile_data"; private static final String RING_PUSH_NOTIFICATIONS = "push_notifs"; + private static final String RING_PERSISTENT_NOTIFICATION = "persistent_notif"; private static final String RING_HD = "hd_upload"; private static final String RING_SYSTEM_CONTACTS = "system_contacts"; private static final String RING_PLACE_CALLS = "place_calls"; @@ -61,6 +62,7 @@ public class SharedPreferencesServiceImpl extends PreferencesService { edit.putBoolean(RING_PLACE_CALLS, settings.isAllowPlaceSystemCalls()); edit.putBoolean(RING_ON_STARTUP, settings.isAllowRingOnStartup()); edit.putBoolean(RING_PUSH_NOTIFICATIONS, settings.isAllowPushNotifications()); + edit.putBoolean(RING_PERSISTENT_NOTIFICATION, settings.isAllowPersistentNotification()); edit.putBoolean(RING_HD, settings.isHD()); edit.apply(); } @@ -77,6 +79,7 @@ public class SharedPreferencesServiceImpl extends PreferencesService { settings.setAllowPlaceSystemCalls(appPrefs.getBoolean(RING_PLACE_CALLS, false)); settings.setAllowRingOnStartup(appPrefs.getBoolean(RING_ON_STARTUP, true)); settings.setAllowPushNotifications(appPrefs.getBoolean(RING_PUSH_NOTIFICATIONS, false)); + settings.setAllowPersistentNotification(appPrefs.getBoolean(RING_PERSISTENT_NOTIFICATION, false)); settings.setHD(appPrefs.getBoolean(RING_HD, false)); return settings; } diff --git a/ring-android/app/src/main/java/cx/ring/settings/SettingsFragment.java b/ring-android/app/src/main/java/cx/ring/settings/SettingsFragment.java index 3bbbf798f384224dc597245021f635890bf10e23..b88958ae33ae8815dd74a0ab8a38443b96c37089 100644 --- a/ring-android/app/src/main/java/cx/ring/settings/SettingsFragment.java +++ b/ring-android/app/src/main/java/cx/ring/settings/SettingsFragment.java @@ -65,6 +65,8 @@ public class SettingsFragment extends BaseFragment<SettingsPresenter> implements Switch mViewPlaceCall; @BindView(R.id.settings_startup) Switch mViewStartup; + @BindView(R.id.settings_persistNotification) + Switch mViewPersistNotif; private boolean mIsRefreshingViewFromPresenter; @BindView(R.id.settings_hd) Switch mViewHD; @@ -107,7 +109,7 @@ public class SettingsFragment extends BaseFragment<SettingsPresenter> implements menu.clear(); } - @OnCheckedChanged({R.id.settings_mobile_data, R.id.settings_push_notifications, R.id.settings_hd, R.id.settings_contacts, R.id.settings_place_call, R.id.settings_startup}) + @OnCheckedChanged({R.id.settings_mobile_data, R.id.settings_push_notifications, R.id.settings_hd, R.id.settings_contacts, R.id.settings_place_call, R.id.settings_startup, R.id.settings_persistNotification}) public void onSettingsCheckedChanged(CompoundButton button, boolean isChecked) { String neededPermission = null; @@ -156,6 +158,7 @@ public class SettingsFragment extends BaseFragment<SettingsPresenter> implements newSettings.setAllowPlaceSystemCalls(mViewPlaceCall.isChecked()); newSettings.setAllowRingOnStartup(mViewStartup.isChecked()); newSettings.setAllowPushNotifications(mViewPushNotifications.isChecked()); + newSettings.setAllowPersistentNotification(mViewPersistNotif.isChecked()); newSettings.setHD(mViewHD.isChecked()); // save settings according to UI inputs @@ -236,6 +239,7 @@ public class SettingsFragment extends BaseFragment<SettingsPresenter> implements mIsRefreshingViewFromPresenter = true; mViewMobileData.setChecked(viewModel.isAllowMobileData()); mViewPushNotifications.setChecked(viewModel.isAllowPushNotifications()); + mViewPersistNotif.setChecked(viewModel.isAllowPersistentNotification()); mViewContacts.setChecked(viewModel.isAllowSystemContacts()); mViewPlaceCall.setChecked(viewModel.isAllowPlaceSystemCalls()); mViewStartup.setChecked(viewModel.isAllowRingOnStartup()); diff --git a/ring-android/app/src/main/res/drawable-hdpi/baseline_notification_important_black_24.png b/ring-android/app/src/main/res/drawable-hdpi/baseline_notification_important_black_24.png new file mode 100755 index 0000000000000000000000000000000000000000..8cbb5f6e737e90094b5ed4210e743b5e97384ba9 Binary files /dev/null and b/ring-android/app/src/main/res/drawable-hdpi/baseline_notification_important_black_24.png differ diff --git a/ring-android/app/src/main/res/drawable-mdpi/baseline_notification_important_black_24.png b/ring-android/app/src/main/res/drawable-mdpi/baseline_notification_important_black_24.png new file mode 100755 index 0000000000000000000000000000000000000000..267af2dc9cdf1b1b786a188cd4e0915ffff472a8 Binary files /dev/null and b/ring-android/app/src/main/res/drawable-mdpi/baseline_notification_important_black_24.png differ diff --git a/ring-android/app/src/main/res/drawable-xhdpi/baseline_notification_important_black_24.png b/ring-android/app/src/main/res/drawable-xhdpi/baseline_notification_important_black_24.png new file mode 100755 index 0000000000000000000000000000000000000000..e6b9a7bdaab985bb2ad1ad1cc0efc310cec3013e Binary files /dev/null and b/ring-android/app/src/main/res/drawable-xhdpi/baseline_notification_important_black_24.png differ diff --git a/ring-android/app/src/main/res/drawable-xxhdpi/baseline_notification_important_black_24.png b/ring-android/app/src/main/res/drawable-xxhdpi/baseline_notification_important_black_24.png new file mode 100755 index 0000000000000000000000000000000000000000..41719ffa0d8d01048f46778d97f7c9a4f4069755 Binary files /dev/null and b/ring-android/app/src/main/res/drawable-xxhdpi/baseline_notification_important_black_24.png differ diff --git a/ring-android/app/src/main/res/drawable-xxxhdpi/baseline_notification_important_black_24.png b/ring-android/app/src/main/res/drawable-xxxhdpi/baseline_notification_important_black_24.png new file mode 100755 index 0000000000000000000000000000000000000000..d1c18282036a7c83d30e907b8f91a100f169f130 Binary files /dev/null and b/ring-android/app/src/main/res/drawable-xxxhdpi/baseline_notification_important_black_24.png differ diff --git a/ring-android/app/src/main/res/drawable/baseline_notification_important_24.xml b/ring-android/app/src/main/res/drawable/baseline_notification_important_24.xml new file mode 100755 index 0000000000000000000000000000000000000000..0c7c03b6d91e2914280e89301debba495fc2d207 --- /dev/null +++ b/ring-android/app/src/main/res/drawable/baseline_notification_important_24.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2zM13,16h-2v-2h2v2zM13,12h-2L11,8h2v4zM12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2z"/> +</vector> diff --git a/ring-android/app/src/main/res/layout/frag_settings.xml b/ring-android/app/src/main/res/layout/frag_settings.xml index edac7fbb8224a113255d866e0f4aa254c4263756..45edd2d9e128192dfa52aa1500c4317b8830e114 100644 --- a/ring-android/app/src/main/res/layout/frag_settings.xml +++ b/ring-android/app/src/main/res/layout/frag_settings.xml @@ -407,6 +407,64 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. <!-- Privacy --> + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal" + android:padding="8dp" + android:weightSum="1"> + + <LinearLayout + android:id="@+id/system_persistNotification_image" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:layout_centerVertical="true"> + + + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="start" + android:contentDescription="@string/pref_persistNotification_summary" + app:srcCompat="@drawable/baseline_notification_important_black_24" /> + + </LinearLayout> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_toStartOf="@+id/settings_persistNotification" + android:layout_toEndOf="@+id/system_persistNotification_image" + android:orientation="vertical" + android:paddingLeft="16dp" + android:paddingRight="16dp"> + + <TextView + style="@style/ListPrimary" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ellipsize="end" + android:lines="1" + android:text="@string/pref_persistNotification_title" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/pref_persistNotification_summary" /> + + </LinearLayout> + + <Switch + android:id="@+id/settings_persistNotification" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:checked="false" /> + + </RelativeLayout> + <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/ring-android/app/src/main/res/values/strings.xml b/ring-android/app/src/main/res/values/strings.xml index 8ef4a966317df79720aaa9a771cd0e7c20387783..e2dcf240f0daacf569e234dd550be4206f8bdd8f 100644 --- a/ring-android/app/src/main/res/values/strings.xml +++ b/ring-android/app/src/main/res/values/strings.xml @@ -107,6 +107,9 @@ along with this program; if not, write to the Free Software <string name="notif_channel_messages">Messages</string> <string name="notif_channel_requests">Requests</string> <string name="notif_channel_file_transfer">File transfer</string> + <string name="notif_channel_background_service">Background service</string> + <string name="notif_channel_background_service_descr">Allows receiving calls and messages anytime.</string> + <string name="notif_background_service">Ring is currently running in background</string> <string name="notif_mark_as_read">Mark as read</string> <string name="notif_dismiss">Dismiss</string> <string name="notif_reply">Reply</string> @@ -267,95 +270,5 @@ along with this program; if not, write to the Free Software <string name="menu_file_download">Download file</string> <string name="menu_file_delete">Delete file</string> <string name="menu_file_share">Share file</string> - <string name="large_text" translatable="false"> - "Material is the metaphor.\n\n" - - "A material metaphor is the unifying theory of a rationalized space and a system of motion." - "The material is grounded in tactile reality, inspired by the study of paper and ink, yet " - "technologically advanced and open to imagination and magic.\n" - "Surfaces and edges of the material provide visual cues that are grounded in reality. The " - "use of familiar tactile attributes helps users quickly understand affordances. Yet the " - "flexibility of the material creates new affordances that supercede those in the physical " - "world, without breaking the rules of physics.\n" - "The fundamentals of light, surface, and movement are key to conveying how objects move, " - "interact, and exist in space and in relation to each other. Realistic lighting shows " - "seams, divides space, and indicates moving parts.\n\n" - - "Bold, graphic, intentional.\n\n" - - "The foundational elements of print based design typography, grids, space, scale, color, " - "and use of imagery guide visual treatments. These elements do far more than please the " - "eye. They create hierarchy, meaning, and focus. Deliberate color choices, edge to edge " - "imagery, large scale typography, and intentional white space create a bold and graphic " - "interface that immerse the user in the experience.\n" - "An emphasis on user actions makes core functionality immediately apparent and provides " - "waypoints for the user.\n\n" - - "Motion provides meaning.\n\n" - - "Motion respects and reinforces the user as the prime mover. Primary user actions are " - "inflection points that initiate motion, transforming the whole design.\n" - "All action takes place in a single environment. Objects are presented to the user without " - "breaking the continuity of experience even as they transform and reorganize.\n" - "Motion is meaningful and appropriate, serving to focus attention and maintain continuity. " - "Feedback is subtle yet clear. Transitions are efficient yet coherent.\n\n" - - "3D world.\n\n" - - "The material environment is a 3D space, which means all objects have x, y, and z " - "dimensions. The z-axis is perpendicularly aligned to the plane of the display, with the " - "positive z-axis extending towards the viewer. Every sheet of material occupies a single " - "position along the z-axis and has a standard 1dp thickness.\n" - "On the web, the z-axis is used for layering and not for perspective. The 3D world is " - "emulated by manipulating the y-axis.\n\n" - - "Light and shadow.\n\n" - - "Within the material environment, virtual lights illuminate the scene. Key lights create " - "directional shadows, while ambient light creates soft shadows from all angles.\n" - "Shadows in the material environment are cast by these two light sources. In Android " - "development, shadows occur when light sources are blocked by sheets of material at " - "various positions along the z-axis. On the web, shadows are depicted by manipulating the " - "y-axis only. The following example shows the card with a height of 6dp.\n\n" - - "Resting elevation.\n\n" - - "All material objects, regardless of size, have a resting elevation, or default elevation " - "that does not change. If an object changes elevation, it should return to its resting " - "elevation as soon as possible.\n\n" - - "Component elevations.\n\n" - - "The resting elevation for a component type is consistent across apps (e.g., FAB elevation " - "does not vary from 6dp in one app to 16dp in another app).\n" - "Components may have different resting elevations across platforms, depending on the depth " - "of the environment (e.g., TV has a greater depth than mobile or desktop).\n\n" - - "Responsive elevation and dynamic elevation offsets.\n\n" - - "Some component types have responsive elevation, meaning they change elevation in response " - "to user input (e.g., normal, focused, and pressed) or system events. These elevation " - "changes are consistently implemented using dynamic elevation offsets.\n" - "Dynamic elevation offsets are the goal elevation that a component moves towards, relative " - "to the component’s resting state. They ensure that elevation changes are consistent " - "across actions and component types. For example, all components that lift on press have " - "the same elevation change relative to their resting elevation.\n" - "Once the input event is completed or cancelled, the component will return to its resting " - "elevation.\n\n" - - "Avoiding elevation interference.\n\n" - - "Components with responsive elevations may encounter other components as they move between " - "their resting elevations and dynamic elevation offsets. Because material cannot pass " - "through other material, components avoid interfering with one another any number of ways, " - "whether on a per component basis or using the entire app layout.\n" - "On a component level, components can move or be removed before they cause interference. " - "For example, a floating action button (FAB) can disappear or move off screen before a " - "user picks up a card, or it can move if a snackbar appears.\n" - "On the layout level, design your app layout to minimize opportunities for interference. " - "For example, position the FAB to one side of stream of a cards so the FAB won’t interfere " - "when a user tries to pick up one of cards.\n\n" - </string> - <string name="action_settings">Settings</string> </resources> diff --git a/ring-android/app/src/main/res/values/strings_account.xml b/ring-android/app/src/main/res/values/strings_account.xml index 8f54b3133d00434fe29261660ced99dff2293e27..e730486b2b343d14e86e7be1d535d05d78cd4a23 100644 --- a/ring-android/app/src/main/res/values/strings_account.xml +++ b/ring-android/app/src/main/res/values/strings_account.xml @@ -37,6 +37,8 @@ along with this program; if not, write to the Free Software <string name="action_create">Create account</string> <string name="action_create_short">Create</string> <string name="action_register">Register</string> + <string name="action_settings">Settings</string> + <string name="error_field_required">This field is required</string> <string name="dialog_wait_create">Adding account</string> <string name="dialog_wait_create_details">Please wait while your new account is added…</string> diff --git a/ring-android/app/src/main/res/values/strings_preferences.xml b/ring-android/app/src/main/res/values/strings_preferences.xml index f7708d1123934a8705b4351fb602214abd6312b4..bc91ced6e958557c2adf17b9e3f4910519c5b466 100644 --- a/ring-android/app/src/main/res/values/strings_preferences.xml +++ b/ring-android/app/src/main/res/values/strings_preferences.xml @@ -30,4 +30,7 @@ <string name="pref_clearHistory_title">Clear history</string> <string name="pref_clearHistory_summary">Clear all the conversations history. This action can not be undone.</string> + <string name="pref_persistNotification_title">Run in background</string> + <string name="pref_persistNotification_summary">Allow running in background to receive calls and messages.</string> + </resources> diff --git a/ring-android/libringclient/src/main/java/cx/ring/model/Settings.java b/ring-android/libringclient/src/main/java/cx/ring/model/Settings.java index 3b4d345cb9567319eb92c637c80f44f99d5ec406..3434f10851b3f8e10185f9b69171ba7e18bd5d35 100644 --- a/ring-android/libringclient/src/main/java/cx/ring/model/Settings.java +++ b/ring-android/libringclient/src/main/java/cx/ring/model/Settings.java @@ -22,6 +22,7 @@ package cx.ring.model; public class Settings { private boolean mAllowMobileData; private boolean mAllowPushNotifications; + private boolean mAllowPersistentNotification; private boolean mAllowSystemContacts; private boolean mAllowPlaceSystemCalls; private boolean mAllowRingOnStartup; @@ -32,6 +33,7 @@ public class Settings { public Settings(Settings s) { mAllowMobileData = s.mAllowMobileData; mAllowPushNotifications = s.mAllowPushNotifications; + mAllowPersistentNotification = s.mAllowPersistentNotification; mAllowSystemContacts = s.mAllowSystemContacts; mAllowPlaceSystemCalls = s.mAllowPlaceSystemCalls; mAllowRingOnStartup = s.mAllowRingOnStartup; @@ -84,4 +86,12 @@ public class Settings { public void setHD(boolean hd) { mHdUpload = hd; } + + public void setAllowPersistentNotification(boolean checked) { + this.mAllowPersistentNotification = checked; + } + + public boolean isAllowPersistentNotification() { + return mAllowPersistentNotification; + } } diff --git a/ring-android/libringclient/src/main/java/cx/ring/services/NotificationService.java b/ring-android/libringclient/src/main/java/cx/ring/services/NotificationService.java index 8eb6d8df67544e9c05bfa4b8eabb67843b136387..232708a0fd40d4a190a5bd2c56c57acdb66188dc 100644 --- a/ring-android/libringclient/src/main/java/cx/ring/services/NotificationService.java +++ b/ring-android/libringclient/src/main/java/cx/ring/services/NotificationService.java @@ -2,6 +2,7 @@ * Copyright (C) 2004-2018 Savoir-faire Linux Inc. * * Author: Aline Bonnet <aline.bonnet@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 @@ -17,47 +18,42 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ - package cx.ring.services; -import java.util.TreeMap; - import cx.ring.model.Account; import cx.ring.model.CallContact; import cx.ring.model.Conference; import cx.ring.model.Conversation; import cx.ring.model.DataTransfer; -import cx.ring.model.DataTransferEventCode; import cx.ring.model.SipCall; -import cx.ring.model.TextMessage; import cx.ring.model.Uri; -public abstract class NotificationService { - - public static final String TRUST_REQUEST_NOTIFICATION_ACCOUNT_ID = "trustRequestNotificationAccountId"; - public static final String TRUST_REQUEST_NOTIFICATION_FROM = "trustRequestNotificationFrom"; +public interface NotificationService { + String TRUST_REQUEST_NOTIFICATION_ACCOUNT_ID = "trustRequestNotificationAccountId"; + String TRUST_REQUEST_NOTIFICATION_FROM = "trustRequestNotificationFrom"; + String KEY_CALL_ID = "callId"; - public static final String KEY_CALL_ID = "callId"; + void showCallNotification(Conference conference); - public abstract void showCallNotification(Conference conference); + void showTextNotification(String accountId, Conversation conversation); - public abstract void showTextNotification(String accountId, Conversation conversation); + void cancelCallNotification(int notificationId); - public abstract void cancelCallNotification(int notificationId); + void cancelTextNotification(Uri contact); - public abstract void cancelTextNotification(Uri contact); + void cancelTextNotification(String ringId); - public abstract void cancelTextNotification(String ringId); + void cancelAll(); - public abstract void cancelAll(); + void showIncomingTrustRequestNotification(Account account); - public abstract void showIncomingTrustRequestNotification(Account account); + void cancelTrustRequestNotification(String accountID); - public abstract void cancelTrustRequestNotification(String accountID); + void showFileTransferNotification(DataTransfer info, CallContact contact); - public abstract void showFileTransferNotification(DataTransfer info, CallContact contact); + void showMissedCallNotification(SipCall call); - public abstract void showMissedCallNotification(SipCall call); + void cancelFileNotification(long id); - public abstract void cancelFileNotification(long id); + Object getServiceNotification(); }