diff --git a/ring-android/app/src/main/AndroidManifest.xml b/ring-android/app/src/main/AndroidManifest.xml index 1e0a91bc1b2c79f0f212bc3b116f15f5a65316bb..aa73e5a8c930143215eef27d79cad20f4fcfea4e 100644 --- a/ring-android/app/src/main/AndroidManifest.xml +++ b/ring-android/app/src/main/AndroidManifest.xml @@ -100,12 +100,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. <meta-data android:name="google_analytics_adid_collection_enabled" android:value="false" /> + <activity + android:name=".client.LogsActivity" + android:label="@string/pref_logs_title" + android:theme="@style/AppTheme.Navigation" + /> <activity android:name=".client.HomeActivity" android:configChanges="screenSize|screenLayout|smallestScreenSize" - android:label="@string/app_name" android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" android:launchMode="singleTask" android:theme="@style/AppTheme.Navigation" android:windowSoftInputMode="adjustResize"> @@ -139,14 +144,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. <meta-data android:name="android.app.searchable" android:resource="@xml/searchable" /> - - <meta-data android:name="android.app.shortcuts" - android:resource="@xml/shortcuts"/> + <meta-data + android:name="android.app.shortcuts" + android:resource="@xml/shortcuts" /> </activity> <activity android:name=".client.ShareActivity" - android:label="@string/title_share_with" android:icon="@mipmap/ic_launcher" + android:label="@string/title_share_with" android:parentActivityName=".client.HomeActivity" android:theme="@style/AppTheme"> <intent-filter> @@ -164,10 +169,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. <activity android:name=".account.AccountWizardActivity" android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize" - android:theme="@style/AppThemeBase.Light" android:icon="@mipmap/ic_launcher" android:resizeableActivity="true" - android:windowSoftInputMode="adjustResize"/> + android:theme="@style/AppThemeBase.Light" + android:windowSoftInputMode="adjustResize" /> <activity android:name=".client.RingtoneActivity" android:configChanges="screenSize|screenLayout|smallestScreenSize" @@ -186,7 +191,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.QUICKBOOT_POWERON" /> - <action android:name="android.intent.action.ACTION_MY_PACKAGE_REPLACED"/> + <action android:name="android.intent.action.ACTION_MY_PACKAGE_REPLACED" /> </intent-filter> </receiver> @@ -207,8 +212,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. android:foregroundServiceType="dataSync" /> <service android:name=".services.LocationSharingService" - android:foregroundServiceType="location" - android:exported="false" /> + android:exported="false" + android:foregroundServiceType="location" /> <activity android:name=".client.CallActivity" @@ -280,8 +285,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. android:name=".client.ConversationActivity" android:allowEmbedded="true" android:configChanges="screenSize|screenLayout|smallestScreenSize" - android:label="@string/app_name" android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" android:parentActivityName=".client.HomeActivity" android:resizeableActivity="true" android:theme="@style/AppTheme.Fullscreen" @@ -293,34 +298,37 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. <intent-filter> <action android:name=".service.DRingService" /> </intent-filter> - </service> - - <!-- AndroidTV section --> + </service> <!-- AndroidTV section --> <activity android:name=".tv.main.HomeActivity" - android:label="@string/app_name" android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" android:theme="@style/Theme.Jami.Leanback.Main"> <intent-filter> <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LEANBACK_LAUNCHER" /> </intent-filter> <intent-filter> <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.DEFAULT" /> + <data - android:scheme="jamitv" android:host="cx.ring" - android:path="/home" /> + android:path="/home" + android:scheme="jamitv" /> </intent-filter> </activity> + <receiver android:name=".tv.main.ChannelActionHandler" android:enabled="true" android:exported="true"> <intent-filter> <category android:name="android.intent.category.DEFAULT" /> + <action android:name="android.media.tv.ACTION_INITIALIZE_PROGRAMS" /> <action android:name="android.media.tv.ACTION_PREVIEW_PROGRAM_BROWSABLE_DISABLED" /> <action android:name="android.media.tv.ACTION_PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT" /> @@ -330,30 +338,30 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. <activity android:name=".tv.account.TVAccountWizard" - android:label="@string/app_name" - android:icon="@mipmap/ic_launcher" android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" android:theme="@style/AppTheme" /> <activity android:name=".tv.search.SearchActivity" - android:label="@string/app_name" android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" android:theme="@style/Theme.Leanback" /> <activity android:name=".tv.about.AboutActivity" - android:label="@string/app_name" android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" android:theme="@style/Theme.Leanback" /> <activity android:name=".tv.account.TVShareActivity" - android:label="@string/app_name" android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" android:theme="@style/Theme.Ring.Leanback" /> <activity android:name=".tv.call.TVCallActivity" - android:label="@string/app_name" - android:icon="@mipmap/ic_launcher" android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" android:resizeableActivity="true" android:showOnLockScreen="true" android:supportsPictureInPicture="true" @@ -366,9 +374,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. </activity> <activity android:name=".tv.camera.CustomCameraActivity" - android:theme="@style/AppTheme" + android:icon="@mipmap/ic_launcher" android:label="@string/app_name" - android:icon="@mipmap/ic_launcher"> + android:theme="@style/AppTheme"> <intent-filter> <action android:name="cx.ring.action.CALL" /> @@ -377,20 +385,21 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. </activity> <activity android:name=".tv.contact.TVContactActivity" - android:theme="@style/Theme.Leanback.Details" + android:icon="@mipmap/ic_launcher" android:label="@string/app_name" - android:icon="@mipmap/ic_launcher"> + android:theme="@style/Theme.Leanback.Details"> <intent-filter> <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.DEFAULT" /> + <data - android:scheme="jamitv" android:host="cx.ring" - android:pathPrefix="/conversation/" /> + android:pathPrefix="/conversation/" + android:scheme="jamitv" /> </intent-filter> </activity> - <activity android:name=".tv.account.TVSettingsActivity" android:theme="@style/LeanbackPreferences" /> @@ -408,8 +417,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. <activity android:name=".client.MediaViewerActivity" android:exported="false" - android:label="@string/title_media_viewer" android:icon="@mipmap/ic_launcher" + android:label="@string/title_media_viewer" android:theme="@style/AppThemeBase.Dark" /> <service @@ -419,16 +428,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. <activity android:name=".client.ContactDetailsActivity" android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize" - android:label="@string/conversation_details" android:icon="@mipmap/ic_launcher" + android:label="@string/conversation_details" android:resizeableActivity="true" android:theme="@style/AppTheme.Fullscreen" android:windowSoftInputMode="adjustResize" /> <activity android:name=".client.ConversationSelectionActivity" android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize" - android:label="Choose a contact or conversation" android:icon="@mipmap/ic_launcher" + android:label="Choose a contact or conversation" android:theme="@style/Theme.MaterialComponents.DayNight.Dialog.Alert" android:windowSoftInputMode="adjustResize" /> </application> diff --git a/ring-android/app/src/main/java/cx/ring/client/LogsActivity.java b/ring-android/app/src/main/java/cx/ring/client/LogsActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..07f7d2cd6b513e8ef11e378f4b0af56c307410cd --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/client/LogsActivity.java @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Adrien Beraud <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.client; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.content.ContextCompat; + +import com.google.android.material.snackbar.Snackbar; + +import net.jami.services.HardwareService; +import net.jami.utils.StringUtils; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import cx.ring.R; +import cx.ring.application.JamiApplication; +import cx.ring.databinding.ActivityLogsBinding; +import cx.ring.utils.AndroidFileUtils; +import cx.ring.utils.ContentUriHandler; +import io.reactivex.Maybe; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; + +public class LogsActivity extends AppCompatActivity { + private static final String TAG = LogsActivity.class.getSimpleName(); + + private ActivityLogsBinding binding; + + private final CompositeDisposable compositeDisposable = new CompositeDisposable(); + private Disposable disposable; + + private ActivityResultLauncher<String> fileSaver; + private File mCurrentFile = null; + + @Inject + @Singleton + HardwareService mHardwareService; + + public LogsActivity() { + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + JamiApplication.getInstance().startDaemon(); + JamiApplication.getInstance().getInjectionComponent().inject(this); + binding = ActivityLogsBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + setSupportActionBar(binding.toolbar); + ActionBar ab = getSupportActionBar(); + if (ab != null) + ab.setDisplayHomeAsUpEnabled(true); + + fileSaver = registerForActivityResult(new ActivityResultContracts.CreateDocument(), result -> compositeDisposable.add( + AndroidFileUtils.copyFileToUri(getContentResolver(), mCurrentFile, result). + observeOn(AndroidSchedulers.mainThread()). + subscribe(() -> { + if (!mCurrentFile.delete()) + Log.w(TAG, "Can't delete temp file"); + mCurrentFile = null; + Snackbar.make(binding.getRoot(), R.string.file_saved_successfully, Snackbar.LENGTH_SHORT).show(); + }, error -> Snackbar.make(binding.getRoot(), R.string.generic_error, Snackbar.LENGTH_SHORT).show()))); + + binding.fab.setOnClickListener(view -> { + if (disposable == null) + startLogging(); + else + stopLogging(); + }); + if (mHardwareService.isLogging()) + startLogging(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.logs_menu, menu); + return super.onCreateOptionsMenu(menu); + } + + private Maybe<String> getLog() { + if (mHardwareService.isLogging()) + return mHardwareService.startLogs() + .firstElement(); + CharSequence log = binding.logView.getText(); + if (StringUtils.isEmpty(log)) + return Maybe.empty(); + return Maybe.just(log.toString()); + } + + private Maybe<File> getLogFile() { + return getLog() + .observeOn(Schedulers.io()) + .map(log -> { + File file = AndroidFileUtils.createLogFile(this); + OutputStream os = new FileOutputStream(file); + os.write(log.getBytes()); + return file; + }); + } + + private Maybe<Uri> getLogUri() { + return getLogFile().map(file -> ContentUriHandler.getUriForFile(this, ContentUriHandler.AUTHORITY_FILES, file)); + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + int id = item.getItemId(); + if (id == android.R.id.home) { + finish(); + return true; + } else if (id == R.id.menu_log_share) { + compositeDisposable.add(getLogUri() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(uri -> { + Log.w(TAG, "saved logs to " + uri); + Intent sendIntent = new Intent(Intent.ACTION_SEND); + sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + String type = getContentResolver().getType(uri); + sendIntent.setDataAndType(uri, type); + sendIntent.putExtra(Intent.EXTRA_STREAM, uri); + startActivity(Intent.createChooser(sendIntent, null)); + }, e -> Snackbar.make(binding.getRoot(), "Error sharing logs: " + e.getLocalizedMessage(), Snackbar.LENGTH_SHORT).show())); + return true; + } else if (id == R.id.menu_log_save) { + compositeDisposable.add(getLogFile() + .subscribe(file -> { + mCurrentFile = file; + fileSaver.launch(file.getName()); + })); + return true; + } + return super.onOptionsItemSelected(item); + } + + void startLogging() { + binding.logView.setText(""); + disposable = mHardwareService.startLogs() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(message -> { + binding.logView.setText(message); + binding.scroll.post(() -> binding.scroll.fullScroll(View.FOCUS_DOWN)); + }, e -> Log.w(TAG, "Error in logger", e)); + compositeDisposable.add(disposable); + setButtonState(true); + } + + void stopLogging() { + disposable.dispose(); + disposable = null; + mHardwareService.stopLogs(); + setButtonState(false); + } + + void setButtonState(boolean logging) { + binding.fab.setText(logging ? R.string.pref_logs_stop : R.string.pref_logs_start); + binding.fab.setBackgroundColor(ContextCompat.getColor(this, logging ? R.color.red_400 : R.color.colorSecondary)); + } + + @Override + protected void onDestroy() { + if (disposable != null) { + disposable.dispose(); + disposable = null; + } + compositeDisposable.clear(); + super.onDestroy(); + } +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/dependencyinjection/JamiInjectionComponent.java b/ring-android/app/src/main/java/cx/ring/dependencyinjection/JamiInjectionComponent.java index 74375259aa0f83d33e22cb0ab44f9ee9c08f68f9..c600b3296fb2db6fcc89770c519a496543d97a2b 100755 --- a/ring-android/app/src/main/java/cx/ring/dependencyinjection/JamiInjectionComponent.java +++ b/ring-android/app/src/main/java/cx/ring/dependencyinjection/JamiInjectionComponent.java @@ -41,6 +41,7 @@ import cx.ring.application.JamiApplication; import cx.ring.client.ContactDetailsActivity; import cx.ring.client.ConversationSelectionActivity; import cx.ring.client.HomeActivity; +import cx.ring.client.LogsActivity; import cx.ring.client.RingtoneActivity; import cx.ring.contactrequests.BlockListFragment; import cx.ring.contactrequests.ContactRequestsFragment; @@ -232,4 +233,7 @@ public interface JamiInjectionComponent { void inject(LinkDeviceFragment linkDeviceFragment); void inject(ContactPickerFragment contactPickerFragment); + + void inject(LogsActivity logsActivity); + } 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 471451c79bdb5e2210420a578149f6430dd12525..df685ca7afc82928b999a9671f12eaf00751d575 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 @@ -20,10 +20,9 @@ package cx.ring.settings; import android.app.Activity; -import android.content.DialogInterface; +import android.content.Intent; import android.os.Bundle; -import androidx.annotation.ArrayRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -43,6 +42,7 @@ import android.widget.Toast; import cx.ring.R; import cx.ring.application.JamiApplication; import cx.ring.client.HomeActivity; +import cx.ring.client.LogsActivity; import cx.ring.databinding.FragSettingsBinding; import net.jami.daemon.JamiService; @@ -142,18 +142,15 @@ public class SettingsFragment extends BaseSupportFragment<SettingsPresenter> imp binding.settingsNotification.setOnClickListener(v -> new MaterialAlertDialogBuilder(view.getContext()) .setTitle(getString(R.string.pref_notification_title)) - .setSingleChoiceItems(singleItems, mNotificationVisibility, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - checkedItem[0] = i; - } - }) + .setSingleChoiceItems(singleItems, mNotificationVisibility, (dialogInterface, i) -> checkedItem[0] = i) .setPositiveButton(android.R.string.ok, (dialog, id) -> { mNotificationVisibility = checkedItem[0]; saveSettings(); }) .setNegativeButton(android.R.string.cancel, (dialog, id) -> {}) .show()); + + binding.settingsLogs.setOnClickListener(v -> startActivity(new Intent(v.getContext(), LogsActivity.class))); } @Override diff --git a/ring-android/app/src/main/java/cx/ring/utils/AndroidFileUtils.java b/ring-android/app/src/main/java/cx/ring/utils/AndroidFileUtils.java index f57bf02cb87b4babbaf0475c0f08c2af8370fb76..73455d21b64d7395b1fdaf3eacbaee591ac78574 100644 --- a/ring-android/app/src/main/java/cx/ring/utils/AndroidFileUtils.java +++ b/ring-android/app/src/main/java/cx/ring/utils/AndroidFileUtils.java @@ -295,6 +295,14 @@ public class AndroidFileUtils { // Save a file: path for use with ACTION_VIEW intents return File.createTempFile(imageFileName, ".webm", getTempShareDir(context)); } + public static File createLogFile(@NonNull Context context) throws IOException { + // Create an image file name + String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date()); + String imageFileName = "log_" + timeStamp + "_"; + + // Save a file: path for use with ACTION_VIEW intents + return File.createTempFile(imageFileName, ".log", getTempShareDir(context)); + } /** * Copies a file from a uri whether locally on a remote location to the local cache diff --git a/ring-android/app/src/main/res/drawable/baseline_article_24.xml b/ring-android/app/src/main/res/drawable/baseline_article_24.xml new file mode 100644 index 0000000000000000000000000000000000000000..2dce23f5599ba962e66e5da72a97820e795719e6 --- /dev/null +++ b/ring-android/app/src/main/res/drawable/baseline_article_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" + android:viewportHeight="24" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM14,17L7,17v-2h7v2zM17,13L7,13v-2h10v2zM17,9L7,9L7,7h10v2z"/> +</vector> diff --git a/ring-android/app/src/main/res/layout/activity_logs.xml b/ring-android/app/src/main/res/layout/activity_logs.xml new file mode 100644 index 0000000000000000000000000000000000000000..699066adc6553a93bdbc31c2715d303a4366a7d5 --- /dev/null +++ b/ring-android/app/src/main/res/layout/activity_logs.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".client.LogsActivity"> + + <com.google.android.material.appbar.AppBarLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:liftOnScroll="true"> + + <com.google.android.material.appbar.MaterialToolbar + android:id="@+id/toolbar" + style="@style/Widget.MaterialComponents.Toolbar.Surface" + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize" + android:background="@color/background" + tools:menu="@menu/logs_menu" + tools:title="Logs"/> + + </com.google.android.material.appbar.AppBarLayout> + + <androidx.core.widget.NestedScrollView + android:id="@+id/scroll" + android:layout_width="match_parent" + android:layout_height="match_parent" + app:layout_behavior="@string/appbar_scrolling_view_behavior"> + + <TextView + android:id="@+id/log_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="@dimen/padding_medium" + android:textIsSelectable="true" + app:layout_behavior="@string/appbar_scrolling_view_behavior" + tools:text="@tools:sample/lorem/random" /> + </androidx.core.widget.NestedScrollView> + + <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton + android:id="@+id/fab" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom|center_horizontal" + android:layout_margin="@dimen/fab_margin" + android:gravity="center" + android:text="@string/pref_logs_start" + app:icon="@drawable/baseline_article_24" /> + +</androidx.coordinatorlayout.widget.CoordinatorLayout> \ No newline at end of file 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 3d1fcde966023e656d6acf0a039b76d82bdad466..d65e18c4d2e0057826ef0482a828bda79b480062 100644 --- a/ring-android/app/src/main/res/layout/frag_settings.xml +++ b/ring-android/app/src/main/res/layout/frag_settings.xml @@ -71,7 +71,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. android:layout_toEndOf="@id/theme_image" android:layout_below="@id/theme_title" /> - <Switch + <com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/settings_dark_theme" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -165,7 +165,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. </LinearLayout> - <Switch + <com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/settings_plugins_switch" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -285,7 +285,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. </LinearLayout> - <Switch + <com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/settings_typing" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -336,7 +336,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. </LinearLayout> - <Switch + <com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/settings_read" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -387,7 +387,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. </LinearLayout> - <Switch + <com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/settings_block_record" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -525,7 +525,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. </LinearLayout> - <Switch + <com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/settings_push_notifications" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -575,7 +575,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. </LinearLayout> - <Switch + <com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/settings_place_call" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -625,7 +625,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. </LinearLayout> - <Switch + <com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/settings_startup" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -675,7 +675,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. </LinearLayout> - <Switch + <com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/settings_persistNotification" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -686,6 +686,56 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. </RelativeLayout> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:text="@string/pref_category_diagnostic" + android:textColor="@color/textColorAccent" + android:textSize="18sp" + android:layout_marginStart="64dp" /> + + <RelativeLayout + android:id="@+id/settings_logs" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="@dimen/padding_large"> + + <ImageView + android:id="@+id/system_diagnostics_image" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:contentDescription="@string/pref_persistNotification_summary" + android:src="@drawable/baseline_article_24" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:layout_marginEnd="32dp"/> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_toEndOf="@+id/system_diagnostics_image" + android:orientation="vertical"> + + <TextView + style="@style/ListPrimary" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ellipsize="end" + android:lines="1" + android:text="@string/pref_logs_title" /> + + <TextView + style="@style/ListSecondary" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/pref_logs_summary" /> + + </LinearLayout> + + </RelativeLayout> + </LinearLayout> </cx.ring.views.BoundedScrollView> </RelativeLayout> diff --git a/ring-android/app/src/main/res/menu/logs_menu.xml b/ring-android/app/src/main/res/menu/logs_menu.xml new file mode 100644 index 0000000000000000000000000000000000000000..e698f3671203f3ab39ed8fba22b8ae2a98af0edd --- /dev/null +++ b/ring-android/app/src/main/res/menu/logs_menu.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + tools:context=".client.HomeActivity"> + + <item + android:id="@+id/menu_log_share" + android:icon="@drawable/baseline_share_24" + android:title="@string/share_label" + app:showAsAction="collapseActionView|always" /> + + <item + android:id="@+id/menu_log_save" + android:icon="@drawable/baseline_file_download_24" + android:title="@string/dial_number" + app:showAsAction="always" /> + +</menu> \ No newline at end of file diff --git a/ring-android/app/src/main/res/menu/smartlist_menu.xml b/ring-android/app/src/main/res/menu/smartlist_menu.xml index e59954d0259319421b71c9067b8af8509321e210..21fb4e1f888e0b1c1c8f0b8b5b3438a4edee8cd2 100644 --- a/ring-android/app/src/main/res/menu/smartlist_menu.xml +++ b/ring-android/app/src/main/res/menu/smartlist_menu.xml @@ -19,13 +19,6 @@ android:visible="false" app:showAsAction="always" /> - <!-- - Created by Rohith M S from Noun Project - Licensed under Creative Commons : - http://creativecommons.org/licenses/by/3.0/us/legalcode - Used without any modification. - --> - <item android:id="@+id/menu_overflow" android:orderInCategory="101" 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 dd797a66189ca691286182f22bace2f55167c66b..acf4b887dcecdf22c4907508e5e88bb4dad4963e 100644 --- a/ring-android/app/src/main/res/values/strings_preferences.xml +++ b/ring-android/app/src/main/res/values/strings_preferences.xml @@ -5,6 +5,7 @@ <string name="pref_category_video">Video</string> <string name="pref_category_system">System</string> <string name="pref_category_privacy">Privacy</string> + <string name="pref_category_diagnostic">Diagnostics</string> <string name="pref_mobileData_title">Mobile data</string> <string name="pref_mobileData_summary">Allow Jami on 3G/LTE networks additionally to Wi-Fi</string> @@ -53,6 +54,11 @@ <string name="pref_persistNotification_title">Run in background</string> <string name="pref_persistNotification_summary">Allow running in background to receive calls and messages.</string> + <string name="pref_logs_title">Diagnostic logs</string> + <string name="pref_logs_summary">Open diagnostic log settings</string> + <string name="pref_logs_start">Start Logging</string> + <string name="pref_logs_stop">Stop logging</string> + <string name="pref_typing_title">Enable typing indicators</string> <string name="pref_typing_summary">Send and receive typing indicators showing that a message is being typed.</string> diff --git a/ring-android/libringclient/src/main/java/net/jami/services/DaemonService.java b/ring-android/libringclient/src/main/java/net/jami/services/DaemonService.java index b50d6cb425e4af15225421667fd603e3b238a294..bbe45d2b925f26553577d9e0853871d13c12ee91 100644 --- a/ring-android/libringclient/src/main/java/net/jami/services/DaemonService.java +++ b/ring-android/libringclient/src/main/java/net/jami/services/DaemonService.java @@ -248,6 +248,11 @@ public class DaemonService { public void contactRemoved(String accountId, String uri, boolean banned) { mExecutor.submit(() -> mAccountService.contactRemoved(accountId, uri, banned)); } + + @Override + public void messageSend(String message) { + mHardwareService.logMessage(message); + } } class DaemonCallAndConferenceCallback extends Callback { diff --git a/ring-android/libringclient/src/main/java/net/jami/services/HardwareService.java b/ring-android/libringclient/src/main/java/net/jami/services/HardwareService.java index c39fd8b3328ec57d0594424e687fdb0a2e0830f1..037fb623536bb2f522b457cdbbb918440ed712f3 100644 --- a/ring-android/libringclient/src/main/java/net/jami/services/HardwareService.java +++ b/ring-android/libringclient/src/main/java/net/jami/services/HardwareService.java @@ -23,6 +23,7 @@ package net.jami.services; import java.util.List; import java.util.Map; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import javax.inject.Inject; import javax.inject.Named; @@ -34,10 +35,14 @@ import net.jami.daemon.UintVect; import net.jami.model.Conference; import net.jami.model.Call; import net.jami.utils.Log; +import net.jami.utils.StringUtils; import net.jami.utils.Tuple; import io.reactivex.Completable; +import io.reactivex.Emitter; import io.reactivex.Observable; +import io.reactivex.ObservableOnSubscribe; import io.reactivex.Scheduler; +import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.BehaviorSubject; import io.reactivex.subjects.PublishSubject; import io.reactivex.subjects.Subject; @@ -209,4 +214,52 @@ public abstract class HardwareService { public abstract void setDeviceOrientation(int rotation); protected abstract List<String> getVideoDevices(); + + private Observable<String> logs = null; + private Emitter<String> logEmitter = null; + + synchronized public boolean isLogging() { + return logs != null; + } + + synchronized public Observable<String> startLogs() { + if (logs == null) { + logs = Observable.create((ObservableOnSubscribe<String>) emitter -> { + Log.w(TAG, "ObservableOnSubscribe JamiService.monitor(true)"); + logEmitter = emitter; + JamiService.monitor(true); + emitter.setCancellable(() -> { + Log.w(TAG, "ObservableOnSubscribe CANCEL JamiService.monitor(false)"); + synchronized (HardwareService.this) { + JamiService.monitor(false); + logEmitter = null; + logs = null; + } + }); + }) + .observeOn(Schedulers.io()) + .scan(new StringBuffer(1024), (sb, message) -> sb.append(message).append('\n')) + .throttleLatest(500, TimeUnit.MILLISECONDS) + .map(StringBuffer::toString) + .replay(1) + .autoConnect(); + } + return logs; + } + + synchronized public void stopLogs() { + if (logEmitter != null) { + Log.w(TAG, "stopLogs JamiService.monitor(false)"); + JamiService.monitor(false); + logEmitter.onComplete(); + logEmitter = null; + logs = null; + } + } + + void logMessage(String message) { + if (logEmitter != null && !StringUtils.isEmpty(message)) { + logEmitter.onNext(message); + } + } }