├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── net │ │ └── emilla │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ │ └── net │ │ │ └── emilla │ │ │ ├── action │ │ │ ├── AssistantSettings.java │ │ │ ├── CursorStart.java │ │ │ ├── FileFetcher.java │ │ │ ├── Flashlight.java │ │ │ ├── Help.java │ │ │ ├── LabeledQuickAction.java │ │ │ ├── MediaFetcher.java │ │ │ ├── NoAction.java │ │ │ ├── PlayPause.java │ │ │ ├── QuickAction.java │ │ │ ├── SelectAll.java │ │ │ ├── box │ │ │ │ ├── ActionBox.kt │ │ │ │ ├── SnippetAdapter.java │ │ │ │ ├── SnippetsFragment.kt │ │ │ │ └── SnippetsViewModel.java │ │ │ └── field │ │ │ │ ├── FieldToggle.java │ │ │ │ ├── LocationField.java │ │ │ │ ├── SubjectField.java │ │ │ │ └── UrlField.java │ │ │ ├── activity │ │ │ ├── AssistActivity.java │ │ │ ├── AssistViewModel.kt │ │ │ ├── DummyActivity.kt │ │ │ └── EmillaActivity.java │ │ │ ├── app │ │ │ ├── AppActions.java │ │ │ ├── AppEntry.java │ │ │ ├── AppList.java │ │ │ ├── AppProperties.java │ │ │ ├── Apps.java │ │ │ └── TaskerIntent.java │ │ │ ├── chime │ │ │ ├── Chimer.kt │ │ │ ├── Custom.kt │ │ │ ├── Nebula.kt │ │ │ ├── Redial.kt │ │ │ └── Silence.java │ │ │ ├── command │ │ │ ├── ActionMap.java │ │ │ ├── ActionYielder.java │ │ │ ├── CommandMap.java │ │ │ ├── CommandYielder.java │ │ │ ├── DataCommand.java │ │ │ ├── DefaultCommandWrapper.java │ │ │ ├── DuplicateCommand.java │ │ │ ├── DuplicateYielder.java │ │ │ ├── EmillaCommand.java │ │ │ ├── Subcommand.java │ │ │ ├── app │ │ │ │ ├── AospContacts.java │ │ │ │ ├── AppCommand.java │ │ │ │ ├── AppSearch.java │ │ │ │ ├── AppSend.java │ │ │ │ ├── AppSendData.java │ │ │ │ ├── Discord.java │ │ │ │ ├── Firefox.java │ │ │ │ ├── Github.java │ │ │ │ ├── Markor.java │ │ │ │ ├── MultilineMessenger.java │ │ │ │ ├── Newpipe.java │ │ │ │ ├── Outlook.java │ │ │ │ ├── Signal.java │ │ │ │ ├── Tasker.java │ │ │ │ ├── Tor.java │ │ │ │ ├── Tubular.java │ │ │ │ ├── VideoSearchBySend.java │ │ │ │ └── Youtube.java │ │ │ └── core │ │ │ │ ├── Alarm.java │ │ │ │ ├── Bits.java │ │ │ │ ├── Calculate.java │ │ │ │ ├── Calendar.java │ │ │ │ ├── Call.java │ │ │ │ ├── CategoryCommand.java │ │ │ │ ├── Celsius.java │ │ │ │ ├── Contact.java │ │ │ │ ├── Copy.java │ │ │ │ ├── CoreCommand.java │ │ │ │ ├── CoreDataCommand.java │ │ │ │ ├── Dial.java │ │ │ │ ├── Email.java │ │ │ │ ├── Fahrenheit.java │ │ │ │ ├── Find.java │ │ │ │ ├── Info.java │ │ │ │ ├── Launch.java │ │ │ │ ├── Navigate.java │ │ │ │ ├── Note.java │ │ │ │ ├── Notify.java │ │ │ │ ├── OpenCommand.java │ │ │ │ ├── Pause.java │ │ │ │ ├── Play.java │ │ │ │ ├── Pomodoro.java │ │ │ │ ├── RandomNumber.java │ │ │ │ ├── Roll.java │ │ │ │ ├── Setting.java │ │ │ │ ├── Share.java │ │ │ │ ├── Sms.java │ │ │ │ ├── Snippets.java │ │ │ │ ├── Time.java │ │ │ │ ├── Timer.java │ │ │ │ ├── Toast.java │ │ │ │ ├── Todo.java │ │ │ │ ├── Torch.java │ │ │ │ ├── Uninstall.java │ │ │ │ ├── Weather.java │ │ │ │ └── Web.java │ │ │ ├── config │ │ │ ├── Aliases.java │ │ │ ├── AssistantFragment.java │ │ │ ├── CommandPreference.java │ │ │ ├── CommandsFragment.java │ │ │ ├── ConfigActivity.java │ │ │ ├── EmillaSettingsFragment.java │ │ │ ├── SettingVals.java │ │ │ └── SettingsFragment.java │ │ │ ├── contact │ │ │ ├── ContactItemView.java │ │ │ ├── MultiSearch.java │ │ │ ├── adapter │ │ │ │ ├── ContactCardAdapter.java │ │ │ │ ├── ContactCursorAdapter.java │ │ │ │ ├── ContactEmailAdapter.java │ │ │ │ └── ContactPhoneAdapter.java │ │ │ └── fragment │ │ │ │ ├── ContactCardsFragment.java │ │ │ │ ├── ContactEmailsFragment.java │ │ │ │ ├── ContactPhonesFragment.java │ │ │ │ └── ContactsFragment.java │ │ │ ├── content │ │ │ ├── receive │ │ │ │ ├── AppChoiceReceiver.java │ │ │ │ ├── ContactCardReceiver.java │ │ │ │ ├── ContactDataReceiver.java │ │ │ │ ├── ContactReceiver.java │ │ │ │ ├── EmailReceiver.java │ │ │ │ ├── FilesReceiver.java │ │ │ │ ├── PhoneReceiver.java │ │ │ │ └── ResultReceiver.java │ │ │ └── retrieve │ │ │ │ ├── AppChoiceRetriever.java │ │ │ │ ├── ContactCardRetriever.java │ │ │ │ ├── ContactDataRetriever.java │ │ │ │ ├── ContactEmailRetriever.java │ │ │ │ ├── ContactPhoneRetriever.java │ │ │ │ ├── FilesRetriever.java │ │ │ │ ├── MediaRetriever.java │ │ │ │ └── ResultRetriever.java │ │ │ ├── event │ │ │ ├── EventScheduler.java │ │ │ ├── PingPlan.java │ │ │ ├── PingReceiver.java │ │ │ ├── PingScheduler.java │ │ │ └── Plan.java │ │ │ ├── exception │ │ │ └── EmillaException.java │ │ │ ├── lang │ │ │ ├── CsvLine.java │ │ │ ├── Lang.java │ │ │ ├── LatinTokens.kt │ │ │ ├── Lines.java │ │ │ ├── Words.java │ │ │ ├── date │ │ │ │ ├── Duration.java │ │ │ │ ├── HourMin.java │ │ │ │ ├── Weekdays.java │ │ │ │ └── impl │ │ │ │ │ ├── DurationEN_US.java │ │ │ │ │ ├── HourMinEN_US.java │ │ │ │ │ └── WeekdaysEN_US.java │ │ │ ├── grammar │ │ │ │ ├── ListPhrase.java │ │ │ │ └── impl │ │ │ │ │ └── ListPhraseEN_US.java │ │ │ ├── measure │ │ │ │ ├── CelsiusConversion.kt │ │ │ │ ├── FahrenheitConversion.kt │ │ │ │ └── impl │ │ │ │ │ ├── CelsiusConversionEN_US.kt │ │ │ │ │ └── FahrenheitConversionEN_US.kt │ │ │ └── phrase │ │ │ │ ├── Dice.java │ │ │ │ ├── Dices.java │ │ │ │ ├── RandRange.java │ │ │ │ └── impl │ │ │ │ ├── DicesEN_US.java │ │ │ │ └── RandRangeEN_US.java │ │ │ ├── math │ │ │ ├── BinaryOperator.kt │ │ │ ├── BitwiseCalculator.java │ │ │ ├── BitwiseOperator.kt │ │ │ ├── BitwiseSign.kt │ │ │ ├── CalcToken.kt │ │ │ ├── Calculator.java │ │ │ ├── FloatingPointNumber.kt │ │ │ ├── IntegerNumber.kt │ │ │ ├── Maths.kt │ │ │ └── UnaryOperator.kt │ │ │ ├── permission │ │ │ └── PermissionRetriever.java │ │ │ ├── ping │ │ │ ├── ClassicPinger.java │ │ │ ├── ModernPinger.java │ │ │ ├── PingChannel.java │ │ │ ├── PingIntent.java │ │ │ ├── Pinger.java │ │ │ └── Pings.kt │ │ │ ├── run │ │ │ ├── AppGift.kt │ │ │ ├── AppSuccess.java │ │ │ ├── BroadcastGift.java │ │ │ ├── BugFailure.java │ │ │ ├── CommandRun.java │ │ │ ├── CopyGift.java │ │ │ ├── DialogRun.java │ │ │ ├── Failure.java │ │ │ ├── Gift.java │ │ │ ├── MessageFailure.java │ │ │ ├── MessageGift.java │ │ │ ├── Offering.java │ │ │ ├── PermissionFailure.java │ │ │ ├── PermissionOffering.java │ │ │ ├── PingGift.java │ │ │ ├── Success.java │ │ │ ├── TimePickerOffering.java │ │ │ └── ToastGift.java │ │ │ ├── system │ │ │ ├── BackgroundServiceUtil.java │ │ │ ├── EmillaA11yService.java │ │ │ └── EmillaFileProvider.java │ │ │ ├── util │ │ │ ├── CalendarDetails.java │ │ │ ├── Chars.kt │ │ │ ├── Contacts.java │ │ │ ├── Dialogs.java │ │ │ ├── Features.kt │ │ │ ├── Files.java │ │ │ ├── IndexWindow.java │ │ │ ├── MediaControl.kt │ │ │ ├── Permissions.java │ │ │ ├── SearchEngineParser.java │ │ │ ├── Services.kt │ │ │ ├── SortedArray.java │ │ │ ├── Strings.kt │ │ │ ├── Time.java │ │ │ ├── Timeit.java │ │ │ ├── TorchManager.java │ │ │ └── trie │ │ │ │ ├── HashTrieMap.java │ │ │ │ ├── SortedTrieMap.java │ │ │ │ └── TrieMap.java │ │ │ └── view │ │ │ └── ActionButton.java │ └── res │ │ ├── color │ │ ├── bg_activatable_item.xml │ │ ├── bg_assistant.xml │ │ └── bg_text_button.xml │ │ ├── drawable │ │ ├── bg_action_button.xml │ │ ├── bg_edit_box.xml │ │ ├── bg_edit_box_focused.xml │ │ ├── bg_edit_box_unfocused.xml │ │ ├── bg_selectable_item.xml │ │ ├── btn_star.xml │ │ ├── btn_star_checked.xml │ │ ├── btn_star_unchecked.xml │ │ ├── ic_alarm.xml │ │ ├── ic_app.xml │ │ ├── ic_assistant.xml │ │ ├── ic_attach.xml │ │ ├── ic_bookmark.xml │ │ ├── ic_calculate.xml │ │ ├── ic_calendar.xml │ │ ├── ic_call.xml │ │ ├── ic_clock.xml │ │ ├── ic_command.xml │ │ ├── ic_contact.xml │ │ ├── ic_copy.xml │ │ ├── ic_cursor_start.xml │ │ ├── ic_dial.xml │ │ ├── ic_email.xml │ │ ├── ic_find.xml │ │ ├── ic_help.xml │ │ ├── ic_hide_data.xml │ │ ├── ic_info.xml │ │ ├── ic_launch.xml │ │ ├── ic_launcher_assistant_foreground.xml │ │ ├── ic_launcher_foreground.xml │ │ ├── ic_location.xml │ │ ├── ic_media.xml │ │ ├── ic_message.xml │ │ ├── ic_navigate.xml │ │ ├── ic_note.xml │ │ ├── ic_notify.xml │ │ ├── ic_pause.xml │ │ ├── ic_person.xml │ │ ├── ic_play.xml │ │ ├── ic_pomodoro.xml │ │ ├── ic_random_number.xml │ │ ├── ic_roll.xml │ │ ├── ic_search.xml │ │ ├── ic_select_all.xml │ │ ├── ic_settings.xml │ │ ├── ic_share.xml │ │ ├── ic_show_data.xml │ │ ├── ic_sms.xml │ │ ├── ic_snippets.xml │ │ ├── ic_subject.xml │ │ ├── ic_temperature.xml │ │ ├── ic_timer.xml │ │ ├── ic_toast.xml │ │ ├── ic_todo.xml │ │ ├── ic_torch.xml │ │ ├── ic_uninstall.xml │ │ ├── ic_weather.xml │ │ ├── ic_web.xml │ │ └── list_divider_emilla.xml │ │ ├── layout │ │ ├── activity_assist.xml │ │ ├── activity_config.xml │ │ ├── alert_dialog_button_bar_emilla.xml │ │ ├── alert_dialog_emilla.xml │ │ ├── btn_action.xml │ │ ├── contact_item.xml │ │ ├── field_extra.xml │ │ ├── fragment_assistant.xml │ │ ├── fragment_commands.xml │ │ ├── fragment_contacts.xml │ │ ├── fragment_settings.xml │ │ ├── preference_category_emilla.xml │ │ ├── prefrence_widget_switch.xml │ │ ├── snippet_item.xml │ │ └── snippet_item_list.xml │ │ ├── menu │ │ └── bottom_nav_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_assistant.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_assistant.webp │ │ ├── ic_launcher_assistant_round.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_assistant.webp │ │ ├── ic_launcher_assistant_round.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_assistant.webp │ │ ├── ic_launcher_assistant_round.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_assistant.webp │ │ ├── ic_launcher_assistant_round.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_assistant.webp │ │ ├── ic_launcher_assistant_round.webp │ │ └── ic_launcher_round.webp │ │ ├── navigation │ │ └── navigation_config.xml │ │ ├── raw │ │ ├── nebula_act.ogg │ │ ├── nebula_exit.ogg │ │ ├── nebula_fail.ogg │ │ ├── nebula_pend.ogg │ │ ├── nebula_resume.ogg │ │ ├── nebula_start.ogg │ │ └── nebula_succeed.ogg │ │ ├── values-notnight │ │ └── themes.xml │ │ ├── values-sw600dp │ │ └── config.xml │ │ ├── values-v31 │ │ └── colors.xml │ │ ├── values │ │ ├── arrays.xml │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── config.xml │ │ ├── dimens.xml │ │ ├── ic_launcher_background.xml │ │ ├── ids.xml │ │ ├── strings.xml │ │ ├── styles.xml │ │ └── themes.xml │ │ └── xml │ │ ├── accessibility_service_config.xml │ │ ├── backup_rules.xml │ │ ├── command_prefs.xml │ │ ├── data_extraction_rules.xml │ │ ├── paths.xml │ │ ├── prefs.xml │ │ └── shortcuts.xml │ └── test │ └── java │ └── net │ └── emilla │ └── ExampleUnitTest.java ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | .idea 4 | gradle/.idea 5 | local.properties 6 | .DS_Store 7 | build 8 | captures 9 | .externalNativeBuild 10 | .cxx 11 | app/build 12 | app/release 13 | docs 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Emilla 2 | A powerful, user-respecting assistant application for Android. 3 | 4 | ## [Releases](https://github.com/devycarol/Emilla/releases) 5 | 6 | ### Why is Emilla awesome? 7 | - You don't need a microphone! You can use it wherever you want. 8 | - It never interrupts you! Emilla will never assume it knows better than you. 9 | - You can turn off sound effects if they're not your thing. 10 | - It never connects to the internet! It won't snoop on you ;) 11 | - It's designed with accessibility at front of mind! 12 | - It uses explicit, configurable commands rather than mysterious language processing. You never have to wonder what your command will do, and it's processed instantly! 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/net/emilla/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package net.emilla; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import android.content.Context; 6 | 7 | import androidx.test.ext.junit.runners.AndroidJUnit4; 8 | import androidx.test.platform.app.InstrumentationRegistry; 9 | 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public final class ExampleInstrumentedTest { 20 | 21 | @Test 22 | public void useAppContext() { 23 | // Context of the app under test. 24 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 25 | assertEquals("net.emilla.nebula", appContext.getPackageName()); 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/action/AssistantSettings.java: -------------------------------------------------------------------------------- 1 | package net.emilla.action; 2 | 3 | import static net.emilla.chime.Chimer.ACT; 4 | 5 | import android.content.Intent; 6 | 7 | import androidx.annotation.DrawableRes; 8 | import androidx.annotation.IdRes; 9 | import androidx.annotation.StringRes; 10 | 11 | import net.emilla.R; 12 | import net.emilla.activity.AssistActivity; 13 | import net.emilla.app.Apps; 14 | import net.emilla.config.ConfigActivity; 15 | import net.emilla.run.AppSuccess; 16 | 17 | public final class AssistantSettings implements LabeledQuickAction { 18 | 19 | private final AssistActivity mActivity; 20 | 21 | public AssistantSettings(AssistActivity act) { 22 | mActivity = act; 23 | } 24 | 25 | @Override @IdRes 26 | public int id() { 27 | return R.id.action_assistant_settings; 28 | } 29 | 30 | @Override @DrawableRes 31 | public int icon() { 32 | return R.drawable.ic_assistant; 33 | } 34 | 35 | @Override @StringRes 36 | public int label() { 37 | return R.string.action_assistant_settings; 38 | } 39 | 40 | @Override @StringRes 41 | public int description() { 42 | return R.string.action_desc_assistant_settings; 43 | } 44 | 45 | @Override 46 | public void perform() { 47 | Intent config = Apps.meTask(mActivity, ConfigActivity.class); 48 | if (mActivity.shouldCancel()) mActivity.succeed(new AppSuccess(mActivity, config)); 49 | else { 50 | mActivity.suppressPendChime(); 51 | mActivity.startActivity(config.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); 52 | mActivity.chime(ACT); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/action/CursorStart.java: -------------------------------------------------------------------------------- 1 | package net.emilla.action; 2 | 3 | import static net.emilla.chime.Chimer.ACT; 4 | import static net.emilla.chime.Chimer.PEND; 5 | import static net.emilla.chime.Chimer.RESUME; 6 | import static java.lang.Math.max; 7 | 8 | import android.widget.EditText; 9 | 10 | import androidx.annotation.StringRes; 11 | 12 | import net.emilla.R; 13 | import net.emilla.activity.AssistActivity; 14 | 15 | public final class CursorStart implements LabeledQuickAction { 16 | 17 | private final AssistActivity mActivity; 18 | 19 | public CursorStart(AssistActivity act) { 20 | mActivity = act; 21 | } 22 | 23 | @Override 24 | public int id() { 25 | return R.id.action_cursor_start; 26 | } 27 | 28 | @Override 29 | public int icon() { 30 | return R.drawable.ic_cursor_start; 31 | } 32 | 33 | @Override @StringRes 34 | public int label() { 35 | return R.string.action_cursor_start; 36 | } 37 | 38 | @Override @StringRes 39 | public int description() { 40 | return R.string.action_desc_cursor_start; 41 | } 42 | 43 | @Override 44 | public void perform() { 45 | EditText box = mActivity.focusedEditBox(); 46 | int len = box.length(); 47 | if (len == 0) { 48 | mActivity.chime(PEND); 49 | return; 50 | } 51 | int start = box.getSelectionStart(), end = box.getSelectionEnd(); 52 | if (max(start, end) == 0) { 53 | box.setSelection(len); 54 | mActivity.chime(RESUME); 55 | } else { 56 | box.setSelection(0); 57 | mActivity.chime(ACT); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/action/FileFetcher.java: -------------------------------------------------------------------------------- 1 | package net.emilla.action; 2 | 3 | import androidx.annotation.DrawableRes; 4 | import androidx.annotation.IdRes; 5 | import androidx.annotation.StringRes; 6 | 7 | import net.emilla.R; 8 | import net.emilla.activity.AssistActivity; 9 | import net.emilla.content.receive.FilesReceiver; 10 | 11 | public final class FileFetcher implements LabeledQuickAction { 12 | 13 | public static final int ID = R.id.action_get_files; 14 | 15 | private final AssistActivity mActivity; 16 | private final FilesReceiver mReceiver; 17 | private final String mMimeType; 18 | 19 | public FileFetcher(AssistActivity act, String commandEntry, String mimeType) { 20 | mActivity = act; 21 | mReceiver = new FilesReceiver(act, commandEntry); 22 | mMimeType = mimeType; 23 | } 24 | 25 | @Override @IdRes 26 | public int id() { 27 | return ID; 28 | } 29 | 30 | @Override @DrawableRes 31 | public int icon() { 32 | return R.drawable.ic_attach; 33 | } 34 | 35 | @Override @StringRes 36 | public int label() { 37 | return R.string.action_attach_files; 38 | } 39 | 40 | @Override @StringRes 41 | public int description() { 42 | return R.string.action_desc_attach_files; 43 | } 44 | 45 | @Override 46 | public void perform() { 47 | mActivity.offerFiles(mReceiver, mMimeType); 48 | // TODO: visual feedback via attachment manager widget 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/action/Flashlight.java: -------------------------------------------------------------------------------- 1 | package net.emilla.action; 2 | 3 | import androidx.annotation.DrawableRes; 4 | import androidx.annotation.IdRes; 5 | import androidx.annotation.StringRes; 6 | 7 | import net.emilla.R; 8 | import net.emilla.activity.AssistActivity; 9 | import net.emilla.exception.EmillaException; 10 | import net.emilla.run.MessageFailure; 11 | import net.emilla.util.TorchManager; 12 | 13 | public final class Flashlight implements LabeledQuickAction { 14 | 15 | @StringRes 16 | private static final int NAME = R.string.action_flashlight; 17 | 18 | private final AssistActivity mActivity; 19 | 20 | public Flashlight(AssistActivity act) { 21 | mActivity = act; 22 | } 23 | 24 | @Override @IdRes 25 | public int id() { 26 | return R.id.action_flashlight; 27 | } 28 | 29 | @Override @DrawableRes 30 | public int icon() { 31 | return R.drawable.ic_torch; 32 | } 33 | 34 | @Override @StringRes 35 | public int label() { 36 | return NAME; 37 | } 38 | 39 | @Override @StringRes 40 | public int description() { 41 | return R.string.action_desc_flashlight; 42 | } 43 | 44 | @Override 45 | public void perform() { try { 46 | TorchManager.toggle(mActivity, NAME); 47 | } catch (EmillaException e) { 48 | mActivity.fail(new MessageFailure(mActivity, e)); 49 | }} 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/action/LabeledQuickAction.java: -------------------------------------------------------------------------------- 1 | package net.emilla.action; 2 | 3 | import android.content.res.Resources; 4 | 5 | import androidx.annotation.StringRes; 6 | 7 | public interface LabeledQuickAction extends QuickAction { 8 | 9 | @StringRes 10 | int label(); 11 | @StringRes 12 | int description(); 13 | 14 | @Override 15 | default String label(Resources res) { 16 | return res.getString(label()); 17 | } 18 | 19 | @Override 20 | default String description(Resources res) { 21 | return res.getString(description()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/action/MediaFetcher.java: -------------------------------------------------------------------------------- 1 | package net.emilla.action; 2 | 3 | import androidx.annotation.DrawableRes; 4 | import androidx.annotation.IdRes; 5 | import androidx.annotation.StringRes; 6 | 7 | import net.emilla.R; 8 | import net.emilla.activity.AssistActivity; 9 | import net.emilla.content.receive.FilesReceiver; 10 | 11 | public final class MediaFetcher implements LabeledQuickAction { 12 | 13 | public static final int ID = R.id.action_get_media; 14 | 15 | private final AssistActivity mActivity; 16 | private final FilesReceiver mReceiver; 17 | 18 | public MediaFetcher(AssistActivity act, String commandEntry) { 19 | mActivity = act; 20 | mReceiver = new FilesReceiver(act, commandEntry); 21 | } 22 | 23 | @Override @IdRes 24 | public int id() { 25 | return ID; 26 | } 27 | 28 | @Override @DrawableRes 29 | public int icon() { 30 | return R.drawable.ic_media; 31 | } 32 | 33 | @Override @StringRes 34 | public int label() { 35 | return R.string.action_attach_media; 36 | } 37 | 38 | @Override @StringRes 39 | public int description() { 40 | return R.string.action_desc_attach_media; 41 | } 42 | 43 | @Override 44 | public void perform() { 45 | mActivity.offerMedia(mReceiver); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/action/NoAction.java: -------------------------------------------------------------------------------- 1 | package net.emilla.action; 2 | 3 | import static net.emilla.chime.Chimer.PEND; 4 | 5 | import androidx.annotation.DrawableRes; 6 | import androidx.annotation.IdRes; 7 | import androidx.annotation.StringRes; 8 | 9 | import net.emilla.R; 10 | import net.emilla.activity.AssistActivity; 11 | 12 | public final class NoAction implements LabeledQuickAction { 13 | 14 | private final AssistActivity mActivity; 15 | 16 | public NoAction(AssistActivity act) { 17 | mActivity = act; 18 | } 19 | 20 | @Override @IdRes 21 | public int id() { 22 | return R.id.action_none; 23 | } 24 | 25 | @Override @DrawableRes 26 | public int icon() { 27 | return R.drawable.ic_assistant; 28 | } 29 | 30 | @Override @StringRes 31 | public int label() { 32 | return R.string.action_none; 33 | } 34 | 35 | @Override @StringRes 36 | public int description() { 37 | return R.string.action_desc_none; 38 | } 39 | 40 | @Override 41 | public void perform() { 42 | mActivity.chime(PEND); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/action/PlayPause.java: -------------------------------------------------------------------------------- 1 | package net.emilla.action; 2 | 3 | import android.media.AudioManager; 4 | 5 | import androidx.annotation.DrawableRes; 6 | import androidx.annotation.IdRes; 7 | import androidx.annotation.StringRes; 8 | 9 | import net.emilla.R; 10 | import net.emilla.activity.AssistActivity; 11 | import net.emilla.util.MediaControl; 12 | import net.emilla.util.Services; 13 | 14 | public final class PlayPause implements LabeledQuickAction { 15 | 16 | private final AssistActivity mActivity; 17 | 18 | public PlayPause(AssistActivity act) { 19 | mActivity = act; 20 | } 21 | 22 | @Override @IdRes 23 | public int id() { 24 | return R.id.action_flashlight; 25 | } 26 | 27 | @Override @DrawableRes 28 | public int icon() { 29 | // TODO: update all feedbacks when media starts and stops playing, either from our actions 30 | // or outside. 31 | return R.drawable.ic_play; 32 | } 33 | 34 | @Override @StringRes 35 | public int label() { 36 | return R.string.action_play_pause; 37 | } 38 | 39 | @Override @StringRes 40 | public int description() { 41 | return R.string.action_desc_play_pause; 42 | } 43 | 44 | @Override 45 | public void perform() { 46 | AudioManager am = Services.audio(mActivity); 47 | MediaControl.sendPlayPauseEvent(am); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/action/QuickAction.java: -------------------------------------------------------------------------------- 1 | package net.emilla.action; 2 | 3 | import android.content.res.Resources; 4 | 5 | import androidx.annotation.DrawableRes; 6 | import androidx.annotation.IdRes; 7 | 8 | public interface QuickAction { 9 | 10 | String // Preference keys 11 | PREF_NO_COMMAND = "action_no_command", 12 | PREF_LONG_SUBMIT = "action_long_submit", 13 | PREF_DOUBLE_ASSIST = "action_double_assist", 14 | PREF_MENU_KEY = "action_menu"; 15 | 16 | String // Action values 17 | NONE = "none", 18 | FLASHLIGHT = "torch", 19 | ASSISTANT_SETTINGS = "config", 20 | CURSOR_START = "cursor_start", 21 | SELECT_ALL = "select_all", 22 | PLAY_PAUSE = "play_pause", 23 | HELP = "help"; 24 | 25 | @IdRes 26 | int id(); 27 | @DrawableRes 28 | int icon(); 29 | String label(Resources res); 30 | String description(Resources res); 31 | void perform(); 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/action/box/ActionBox.kt: -------------------------------------------------------------------------------- 1 | package net.emilla.action.box 2 | 3 | import androidx.annotation.LayoutRes 4 | import androidx.annotation.StringRes 5 | import androidx.appcompat.app.AlertDialog 6 | import androidx.fragment.app.Fragment 7 | import net.emilla.activity.AssistActivity 8 | import net.emilla.run.CopyGift 9 | import net.emilla.run.DialogRun 10 | import net.emilla.run.Gift 11 | import net.emilla.run.MessageGift 12 | import net.emilla.run.Offering 13 | 14 | abstract class ActionBox 15 | protected constructor(@LayoutRes contentLayoutId: Int) 16 | : Fragment (contentLayoutId) { 17 | 18 | protected val activity by lazy { requireActivity() as AssistActivity } 19 | private val res by lazy { activity.resources } 20 | 21 | protected fun chime(id: Byte) = activity.chime(id) 22 | protected fun toast(msg: CharSequence) = activity.toast(msg) 23 | protected fun toast(@StringRes msg: Int, vararg formatArgs: Any) = activity.toast(str(msg, *formatArgs)) 24 | protected fun str(@StringRes id: Int, vararg formatArgs: Any) = res.getString(id, *formatArgs) 25 | private fun offer(offering: Offering) = activity.offer(offering) 26 | protected fun offerDialog(dlg: AlertDialog.Builder) = offer(DialogRun(activity, dlg)) 27 | protected fun give(gift: Gift) = activity.give(gift) 28 | protected fun giveMessage(msg: CharSequence) = give(MessageGift(activity, name(), msg)) 29 | protected fun giveCopy(text: String) = give(CopyGift(activity, text)) 30 | 31 | @StringRes 32 | protected abstract fun name(): Int 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/action/field/LocationField.java: -------------------------------------------------------------------------------- 1 | package net.emilla.action.field; 2 | 3 | import androidx.annotation.IdRes; 4 | 5 | import net.emilla.R; 6 | import net.emilla.activity.AssistActivity; 7 | 8 | public final class LocationField extends FieldToggle { 9 | 10 | @IdRes 11 | public static final int ACTION_ID = R.id.action_field_location; 12 | @IdRes 13 | public static final int FIELD_ID = R.id.field_location; 14 | 15 | public LocationField(AssistActivity act) { 16 | super(act, ACTION_ID, FIELD_ID, R.string.field_location, R.drawable.ic_location); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/action/field/SubjectField.java: -------------------------------------------------------------------------------- 1 | package net.emilla.action.field; 2 | 3 | import androidx.annotation.IdRes; 4 | 5 | import net.emilla.R; 6 | import net.emilla.activity.AssistActivity; 7 | 8 | public final class SubjectField extends FieldToggle { 9 | 10 | @IdRes 11 | public static final int ACTION_ID = R.id.action_field_subject; 12 | @IdRes 13 | public static final int FIELD_ID = R.id.field_subject; 14 | 15 | public SubjectField(AssistActivity act) { 16 | super(act, ACTION_ID, FIELD_ID, R.string.field_subject, R.drawable.ic_subject); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/action/field/UrlField.java: -------------------------------------------------------------------------------- 1 | package net.emilla.action.field; 2 | 3 | import androidx.annotation.IdRes; 4 | 5 | import net.emilla.R; 6 | import net.emilla.activity.AssistActivity; 7 | 8 | public final class UrlField extends FieldToggle { 9 | 10 | @IdRes 11 | public static final int ACTION_ID = R.id.action_field_url; 12 | @IdRes 13 | public static final int FIELD_ID = R.id.field_url; 14 | 15 | public UrlField(AssistActivity act) { 16 | super(act, ACTION_ID, FIELD_ID, R.string.field_url, R.drawable.ic_web); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/activity/DummyActivity.kt: -------------------------------------------------------------------------------- 1 | package net.emilla.activity 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.content.Intent.EXTRA_INTENT 6 | import android.content.SharedPreferences 7 | import android.os.Bundle 8 | import androidx.activity.result.ActivityResult 9 | import androidx.activity.result.ActivityResultCallback 10 | import androidx.activity.result.ActivityResultLauncher 11 | import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult 12 | import androidx.preference.PreferenceManager 13 | import net.emilla.chime.Chimer 14 | import net.emilla.chime.Chimer.Companion.SUCCEED 15 | import net.emilla.config.SettingVals 16 | 17 | class DummyActivity : EmillaActivity() { 18 | 19 | private lateinit var chimer: Chimer 20 | 21 | private val callback = ActivityResultCallback { 22 | finishAndRemoveTask() 23 | chimer.chime(SUCCEED) 24 | } 25 | 26 | private val resultLauncher: ActivityResultLauncher = registerForActivityResult( 27 | StartActivityForResult(), callback 28 | ) 29 | 30 | override fun onCreate(savedInstanceState: Bundle?) { 31 | super.onCreate(savedInstanceState) 32 | 33 | val intent = intent.getParcelableExtra(EXTRA_INTENT) ?: return 34 | 35 | val appCtx: Context = applicationContext 36 | val prefs: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(appCtx) 37 | chimer = SettingVals.chimer(appCtx, prefs) 38 | 39 | resultLauncher.launch(intent) 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/activity/EmillaActivity.java: -------------------------------------------------------------------------------- 1 | package net.emilla.activity; 2 | 3 | import android.widget.Toast; 4 | 5 | import androidx.annotation.StringRes; 6 | import androidx.appcompat.app.AppCompatActivity; 7 | 8 | public abstract class EmillaActivity extends AppCompatActivity { 9 | 10 | public final void toast(CharSequence message) { 11 | Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); 12 | } 13 | 14 | public final void toast(CharSequence message, boolean longToast) { 15 | Toast.makeText(this, message, longToast ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT).show(); 16 | } 17 | 18 | public final void toast(@StringRes int message) { 19 | Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); 20 | } 21 | 22 | public final void toast(@StringRes int message, boolean longToast) { 23 | Toast.makeText(this, message, longToast ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT).show(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/app/AppActions.java: -------------------------------------------------------------------------------- 1 | package net.emilla.app; 2 | 3 | import static net.emilla.app.Apps.searchToApp; 4 | import static net.emilla.app.Apps.sendToApp; 5 | 6 | import android.content.pm.PackageManager; 7 | 8 | import net.emilla.command.app.Tasker; 9 | 10 | public final class AppActions { 11 | 12 | public static final int FLAG_SEND = 0x1, 13 | FLAG_SEARCH = 0x2, 14 | FLAG_SPECIAL = 0x4; 15 | 16 | public static int of(PackageManager pm, String pkg, int mask) { 17 | if (pkg.equals(Tasker.PKG)) return FLAG_SPECIAL; 18 | 19 | int flags = 0; 20 | 21 | if (((mask & FLAG_SEND) != 0) 22 | && Apps.canDo(pm, sendToApp(pkg))) flags |= FLAG_SEND; 23 | 24 | if (((mask & FLAG_SEARCH) != 0) 25 | && Apps.canDo(pm, searchToApp(pkg))) flags |= FLAG_SEARCH; 26 | 27 | return flags; 28 | } 29 | 30 | private AppActions() {} 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/chime/Custom.kt: -------------------------------------------------------------------------------- 1 | package net.emilla.chime 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import android.media.MediaPlayer 6 | import android.net.Uri 7 | 8 | /** 9 | * A custom-sound chimer built from user settings. 10 | * 11 | * @param ctx it's important to use application context to avoid memory leaks! 12 | * @param prefs used to fetch sound URIs from user settings. 13 | */ 14 | class Custom(private val ctx: Context, prefs: SharedPreferences) : Chimer { 15 | private val uris: Array = Chimer.customSounds(prefs) 16 | 17 | override fun chime(id: Byte) { 18 | var player = MediaPlayer.create(ctx, uris[id.toInt()]) 19 | ?: MediaPlayer.create(ctx, Nebula.sound(id)) 20 | // fall back to nebula URI is broken or null 21 | player.setOnCompletionListener(MediaPlayer::release) 22 | player.start() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/chime/Nebula.kt: -------------------------------------------------------------------------------- 1 | package net.emilla.chime 2 | 3 | import android.content.Context 4 | import android.media.MediaPlayer 5 | import androidx.annotation.RawRes 6 | import net.emilla.R 7 | 8 | /** 9 | * The default Nebula chimer. 10 | * 11 | * @param ctx it's important to use application context to avoid memory leaks! 12 | */ 13 | class Nebula(private val ctx: Context) : Chimer { 14 | 15 | override fun chime(id: Byte) { 16 | // TODO: still encountering occasional sound cracking issues 17 | val player: MediaPlayer = MediaPlayer.create(ctx, sound(id)) 18 | player.setOnCompletionListener(MediaPlayer::release) 19 | player.start() 20 | } 21 | 22 | companion object { 23 | @JvmStatic @RawRes 24 | internal fun sound(chime: Byte) = when (chime) { 25 | Chimer.START -> R.raw.nebula_start 26 | Chimer.ACT -> R.raw.nebula_act 27 | Chimer.PEND -> R.raw.nebula_pend 28 | Chimer.RESUME -> R.raw.nebula_resume 29 | Chimer.EXIT -> R.raw.nebula_exit 30 | Chimer.SUCCEED -> R.raw.nebula_succeed 31 | Chimer.FAIL -> R.raw.nebula_fail 32 | else -> 0 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/chime/Redial.kt: -------------------------------------------------------------------------------- 1 | package net.emilla.chime 2 | 3 | import android.media.AudioManager 4 | import android.media.ToneGenerator 5 | 6 | class Redial : Chimer { 7 | private val toneGenerator = ToneGenerator(AudioManager.STREAM_MUSIC, ToneGenerator.MAX_VOLUME) 8 | 9 | override fun chime(id: Byte) { 10 | toneGenerator.startTone(tone(id)) 11 | } 12 | 13 | private fun tone(id: Byte) = when (id) { 14 | Chimer.START, Chimer.PEND, Chimer.RESUME -> ToneGenerator.TONE_PROP_BEEP 15 | Chimer.ACT -> ToneGenerator.TONE_PROP_PROMPT 16 | Chimer.EXIT -> ToneGenerator.TONE_PROP_BEEP2 17 | Chimer.SUCCEED -> ToneGenerator.TONE_PROP_ACK 18 | Chimer.FAIL -> ToneGenerator.TONE_PROP_NACK 19 | else -> -1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/chime/Silence.java: -------------------------------------------------------------------------------- 1 | package net.emilla.chime; 2 | 3 | public final class Silence implements Chimer { 4 | 5 | @Override 6 | public void chime(byte id) {} // do nothing 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/ActionMap.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command; 2 | 3 | import android.content.res.Resources; 4 | 5 | import androidx.annotation.ArrayRes; 6 | 7 | import net.emilla.lang.Lang; 8 | import net.emilla.lang.Words; 9 | import net.emilla.util.trie.HashTrieMap; 10 | import net.emilla.util.trie.TrieMap; 11 | 12 | public final class ActionMap> { 13 | 14 | private final TrieMap> mTrieMap = new HashTrieMap<>(); 15 | private final A mDefaultAction; 16 | 17 | public ActionMap(A defaultAction) { 18 | mDefaultAction = defaultAction; 19 | } 20 | 21 | public void put(Resources res, A action, @ArrayRes int names, boolean usesInstruction) { 22 | var yielder = new ActionYielder<>(action, usesInstruction); 23 | for (String name : res.getStringArray(names)) { 24 | mTrieMap.put(Lang.words(name), yielder); 25 | } 26 | } 27 | 28 | public Subcommand get(String instruction) { 29 | Words words = Lang.words(instruction); 30 | ActionYielder get = mTrieMap.get(words); 31 | return get == null ? new Subcommand<>(mDefaultAction, instruction) : get.action(words); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/ActionYielder.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command; 2 | 3 | import net.emilla.lang.Words; 4 | import net.emilla.util.trie.TrieMap; 5 | 6 | public final class ActionYielder> implements TrieMap.Value> { 7 | 8 | public final A action; 9 | private final boolean mUsesInstruction; 10 | 11 | public ActionYielder(A action, boolean usesInstruction) { 12 | this.action = action; 13 | mUsesInstruction = usesInstruction; 14 | } 15 | 16 | @Override 17 | public boolean isPrefixable() { 18 | return mUsesInstruction; 19 | } 20 | 21 | @Override 22 | public ActionYielder duplicate(ActionYielder discarded) { 23 | return this; 24 | } 25 | 26 | public Subcommand action(Words words) { 27 | if (words.hasRemainingContents()) return new Subcommand<>(action, words.remainingContents()); 28 | return new Subcommand<>(action, null); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/CommandMap.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command; 2 | 3 | import net.emilla.activity.AssistActivity; 4 | import net.emilla.lang.Lang; 5 | import net.emilla.lang.Words; 6 | import net.emilla.util.trie.HashTrieMap; 7 | import net.emilla.util.trie.TrieMap; 8 | 9 | public final class CommandMap { 10 | 11 | private final TrieMap mTrieMap = new HashTrieMap<>(); 12 | private final DefaultCommandWrapper.Yielder mDefaultYielder; 13 | 14 | CommandMap(DefaultCommandWrapper.Yielder defaultYielder) { 15 | mDefaultYielder = defaultYielder; 16 | } 17 | 18 | void put(String command, CommandYielder yielder) { 19 | mTrieMap.put(Lang.words(command), yielder); 20 | } 21 | 22 | /** 23 | *

24 | * Tries to map {@code alias} to a command associated with {@code commandName}.

25 | *

26 | * If no such command is in the map, the alias is discarded.

27 | * 28 | * @param alias name for the custom command. 29 | * @param commandName exact name of command to map {@code alias} to. 30 | */ 31 | void putCustom(String alias, String commandName) { 32 | CommandYielder exact = mTrieMap.getExact(Lang.words(commandName)); 33 | if (exact != null) mTrieMap.put(Lang.words(alias), exact); 34 | } 35 | 36 | public EmillaCommand get(AssistActivity act, String fullCommand) { 37 | Words words = Lang.words(fullCommand); 38 | CommandYielder get = mTrieMap.get(words); 39 | return get == null ? mDefaultYielder.command(act, words) : get.command(act, words); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/DataCommand.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command; 2 | 3 | import androidx.annotation.StringRes; 4 | 5 | public interface DataCommand { 6 | 7 | @StringRes 8 | int dataHint(); 9 | void execute(String data); 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/Subcommand.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command; 2 | 3 | import androidx.annotation.Nullable; 4 | 5 | public record Subcommand
>(A action, @Nullable String instruction) {} 6 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/AospContacts.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import static net.emilla.app.AppProperties.ordinaryFree; 4 | 5 | import androidx.annotation.ArrayRes; 6 | import androidx.annotation.StringRes; 7 | 8 | import net.emilla.R; 9 | import net.emilla.activity.AssistActivity; 10 | import net.emilla.app.AppProperties; 11 | 12 | public final class AospContacts extends AppSearch { 13 | 14 | public static final String PKG = "com.android.contacts"; 15 | @ArrayRes 16 | private static final int ALIASES = R.array.aliases_aosp_contacts; 17 | @StringRes 18 | private static final int SUMMARY = R.string.summary_app_aosp_contacts; 19 | 20 | public static AppProperties meta() { 21 | return ordinaryFree(ALIASES, SUMMARY); 22 | } 23 | 24 | public AospContacts(AssistActivity act, Yielder info) { 25 | super(act, info, 26 | R.string.instruction_contact, 27 | SUMMARY); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/Discord.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import static net.emilla.app.AppProperties.ordinary; 4 | 5 | import androidx.annotation.ArrayRes; 6 | import androidx.annotation.StringRes; 7 | 8 | import net.emilla.R; 9 | import net.emilla.activity.AssistActivity; 10 | import net.emilla.app.AppProperties; 11 | 12 | public final class Discord extends AppSend { 13 | 14 | public static final String PKG = "com.discord"; 15 | @ArrayRes 16 | private static final int ALIASES = R.array.aliases_discord; 17 | @StringRes 18 | private static final int SUMMARY = R.string.summary_messaging; 19 | 20 | public static AppProperties meta() { 21 | return ordinary(ALIASES, SUMMARY); 22 | } 23 | 24 | public Discord(AssistActivity act, Yielder info) { 25 | super(act, info, 26 | R.string.instruction_message, 27 | R.string.summary_messaging); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/Firefox.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import static net.emilla.app.AppProperties.suppressiveFree; 4 | 5 | import androidx.annotation.ArrayRes; 6 | import androidx.annotation.StringRes; 7 | 8 | import net.emilla.R; 9 | import net.emilla.activity.AssistActivity; 10 | import net.emilla.app.AppActions; 11 | import net.emilla.app.AppProperties; 12 | 13 | public final class Firefox extends AppSearch { 14 | 15 | public static final String PKG = "org.mozilla.firefox"; 16 | @ArrayRes 17 | private static final int ALIASES = R.array.aliases_firefox; 18 | @StringRes 19 | private static final int SUMMARY = R.string.summary_web; 20 | 21 | public static AppProperties meta() { 22 | return suppressiveFree(ALIASES, SUMMARY, AppActions.FLAG_SEND); 23 | // 'send' is redundant for Firefox, it just searches. 24 | } 25 | 26 | public Firefox(AssistActivity act, Yielder info) { 27 | super(act, info, 28 | R.string.instruction_web, 29 | SUMMARY); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/Github.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import static net.emilla.app.AppProperties.ordinary; 4 | 5 | import androidx.annotation.ArrayRes; 6 | import androidx.annotation.StringRes; 7 | 8 | import net.emilla.R; 9 | import net.emilla.activity.AssistActivity; 10 | import net.emilla.app.AppProperties; 11 | 12 | public final class Github extends AppSendData { 13 | 14 | public static final String PKG = "com.github.android"; 15 | @ArrayRes 16 | private static final int ALIASES = R.array.aliases_github; 17 | @StringRes 18 | private static final int SUMMARY = R.string.summary_issues; 19 | 20 | public static AppProperties meta() { 21 | return ordinary(ALIASES, SUMMARY); 22 | } 23 | 24 | public Github(AssistActivity act, Yielder info) { 25 | super(act, info, 26 | R.string.instruction_issue, 27 | R.string.summary_issues, 28 | R.string.data_hint_issue); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/Markor.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import static net.emilla.app.AppProperties.ordinaryFree; 4 | import static net.emilla.app.AppProperties.suppressiveFree; 5 | 6 | import androidx.annotation.ArrayRes; 7 | import androidx.annotation.StringRes; 8 | 9 | import net.emilla.R; 10 | import net.emilla.activity.AssistActivity; 11 | import net.emilla.app.AppProperties; 12 | 13 | public final class Markor extends AppSendData { 14 | 15 | public static final String PKG = "net.gsantner.markor"; 16 | @ArrayRes 17 | private static final int ALIASES = R.array.aliases_markor; 18 | @StringRes 19 | private static final int SUMMARY = R.string.summary_note; 20 | 21 | private static final String CLS_MAIN = PKG + ".activity.MainActivity"; 22 | 23 | public static AppProperties meta(String cls) { 24 | return cls.equals(CLS_MAIN) ? ordinaryFree(ALIASES, SUMMARY) : suppressiveFree(); 25 | } 26 | 27 | public static AppCommand instance(AssistActivity act, Yielder info, String cls) { 28 | return cls.equals(CLS_MAIN) ? new Markor(act, info) : new AppCommand(act, info); 29 | // Markor can have multiple launchers, only the main should have the 'send' property. 30 | } 31 | 32 | private Markor(AssistActivity act, Yielder info) { 33 | super(act, info, 34 | R.string.instruction_text, 35 | SUMMARY, 36 | R.string.data_hint_note); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/MultilineMessenger.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import net.emilla.R; 4 | import net.emilla.activity.AssistActivity; 5 | 6 | /*internal*/ abstract class MultilineMessenger extends AppSendData { 7 | 8 | public MultilineMessenger(AssistActivity act, Yielder info) { 9 | super(act, info, 10 | R.string.instruction_message, 11 | R.string.summary_messaging, 12 | R.string.data_hint_message_cont); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/Newpipe.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import static net.emilla.app.AppProperties.ordinaryFree; 4 | 5 | import androidx.annotation.ArrayRes; 6 | import androidx.annotation.StringRes; 7 | 8 | import net.emilla.R; 9 | import net.emilla.activity.AssistActivity; 10 | import net.emilla.app.AppProperties; 11 | 12 | public final class Newpipe extends VideoSearchBySend { 13 | 14 | public static final String PKG = "org.schabi.newpipe"; 15 | @ArrayRes 16 | private static final int ALIASES = R.array.aliases_newpipe; 17 | @StringRes 18 | private static final int SUMMARY = R.string.summary_video; 19 | 20 | public static AppProperties meta() { 21 | return ordinaryFree(ALIASES, SUMMARY); 22 | } 23 | 24 | public Newpipe(AssistActivity act, Yielder info) { 25 | super(act, info); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/Outlook.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import static net.emilla.app.AppProperties.ordinary; 4 | 5 | import androidx.annotation.ArrayRes; 6 | import androidx.annotation.StringRes; 7 | 8 | import net.emilla.R; 9 | import net.emilla.activity.AssistActivity; 10 | import net.emilla.app.AppProperties; 11 | 12 | public final class Outlook extends AppSendData { 13 | 14 | public static final String PKG = "com.microsoft.office.outlook"; 15 | @ArrayRes 16 | private static final int ALIASES = R.array.aliases_outlook; 17 | @StringRes 18 | private static final int SUMMARY = R.string.summary_email; 19 | 20 | public static AppProperties meta() { 21 | return ordinary(ALIASES, SUMMARY); 22 | } 23 | 24 | public Outlook(AssistActivity act, Yielder info) { 25 | super(act, info, 26 | R.string.instruction_app_email, 27 | R.string.summary_email, 28 | R.string.data_hint_email); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/Signal.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import static net.emilla.app.AppProperties.ordinaryFree; 4 | 5 | import androidx.annotation.ArrayRes; 6 | import androidx.annotation.StringRes; 7 | 8 | import net.emilla.R; 9 | import net.emilla.activity.AssistActivity; 10 | import net.emilla.app.AppProperties; 11 | 12 | public final class Signal extends MultilineMessenger { 13 | 14 | public static final String PKG = "org.thoughtcrime.securesms"; 15 | @ArrayRes 16 | private static final int ALIASES = R.array.aliases_signal; 17 | @StringRes 18 | private static final int SUMMARY = R.string.summary_messaging; 19 | 20 | public static AppProperties meta() { 21 | return ordinaryFree(ALIASES, SUMMARY); 22 | } 23 | 24 | public Signal(AssistActivity act, Yielder info) { 25 | super(act, info); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/Tor.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import static net.emilla.app.AppProperties.suppressiveFree; 4 | 5 | import androidx.annotation.ArrayRes; 6 | import androidx.annotation.StringRes; 7 | 8 | import net.emilla.R; 9 | import net.emilla.activity.AssistActivity; 10 | import net.emilla.app.AppActions; 11 | import net.emilla.app.AppProperties; 12 | 13 | public final class Tor extends AppCommand { 14 | 15 | public static final String PKG = "org.torproject.torbrowser"; 16 | @ArrayRes 17 | private static final int ALIASES = R.array.aliases_tor; 18 | @StringRes 19 | private static final int SUMMARY = R.string.summary_web; 20 | 21 | public static AppProperties meta() { 22 | return suppressiveFree(ALIASES, SUMMARY, AppActions.FLAG_SEND | AppActions.FLAG_SEARCH); 23 | // search/send intents are broken. 24 | } 25 | 26 | public Tor(AssistActivity act, Yielder info) { 27 | super(act, info); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/Tubular.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import static net.emilla.app.AppProperties.ordinaryFree; 4 | 5 | import androidx.annotation.ArrayRes; 6 | import androidx.annotation.StringRes; 7 | 8 | import net.emilla.R; 9 | import net.emilla.activity.AssistActivity; 10 | import net.emilla.app.AppProperties; 11 | 12 | public final class Tubular extends VideoSearchBySend { 13 | 14 | public static final String PKG = "org.polymorphicshade.tubular"; 15 | @ArrayRes 16 | private static final int ALIASES = R.array.aliases_tubular; 17 | @StringRes 18 | private static final int SUMMARY = R.string.summary_video; 19 | 20 | public static AppProperties meta() { 21 | return ordinaryFree(ALIASES, SUMMARY); 22 | } 23 | 24 | public Tubular(AssistActivity act, Yielder info) { 25 | super(act, info); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/VideoSearchBySend.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import android.view.inputmethod.EditorInfo; 4 | 5 | import net.emilla.R; 6 | import net.emilla.activity.AssistActivity; 7 | 8 | /*internal*/ abstract class VideoSearchBySend extends AppSend { 9 | 10 | public VideoSearchBySend(AssistActivity act, Yielder info) { 11 | super(act, info, 12 | R.string.instruction_video, 13 | R.string.summary_video, 14 | R.string.manual_app_send, 15 | EditorInfo.IME_ACTION_SEARCH); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/Youtube.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import static net.emilla.app.AppProperties.ordinary; 4 | 5 | import androidx.annotation.ArrayRes; 6 | import androidx.annotation.StringRes; 7 | 8 | import net.emilla.R; 9 | import net.emilla.activity.AssistActivity; 10 | import net.emilla.app.AppProperties; 11 | 12 | public final class Youtube extends AppSearch { 13 | 14 | public static final String PKG = "com.google.android.youtube"; 15 | @ArrayRes 16 | private static final int ALIASES = R.array.aliases_youtube; 17 | @StringRes 18 | private static final int SUMMARY = R.string.summary_video; 19 | 20 | public static AppProperties meta() { 21 | return ordinary(ALIASES, SUMMARY); 22 | } 23 | 24 | public Youtube(AssistActivity act, Yielder info) { 25 | super(act, info, 26 | R.string.instruction_video, 27 | R.string.summary_video); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/Bits.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | import static android.content.Intent.CATEGORY_APP_CALCULATOR; 4 | 5 | import android.content.Intent; 6 | import android.view.inputmethod.EditorInfo; 7 | 8 | import androidx.annotation.ArrayRes; 9 | import androidx.annotation.StringRes; 10 | 11 | import net.emilla.R; 12 | import net.emilla.activity.AssistActivity; 13 | import net.emilla.app.Apps; 14 | import net.emilla.math.BitwiseCalculator; 15 | 16 | public final class Bits extends CategoryCommand { 17 | 18 | public static final String ENTRY = "bits"; 19 | @StringRes 20 | public static final int NAME = R.string.command_bits; 21 | @ArrayRes 22 | public static final int ALIASES = R.array.aliases_bits; 23 | 24 | public static Yielder yielder() { 25 | return new Yielder(true, Bits::new, ENTRY, NAME, ALIASES); 26 | } 27 | 28 | public static boolean possible() { 29 | return true; 30 | } 31 | 32 | private Bits(AssistActivity act) { 33 | super(act, NAME, 34 | R.string.instruction_calculate, 35 | R.drawable.ic_command, 36 | R.string.summary_bits, 37 | R.string.manual_bits, 38 | EditorInfo.IME_ACTION_DONE); 39 | } 40 | 41 | @Override 42 | protected Intent makeFilter() { 43 | return Apps.categoryTask(CATEGORY_APP_CALCULATOR); 44 | } 45 | 46 | @Override 47 | protected void run(String expression) { 48 | giveMessage(String.valueOf(BitwiseCalculator.compute(expression, NAME))); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/Calculate.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | import static android.content.Intent.CATEGORY_APP_CALCULATOR; 4 | 5 | import android.content.Intent; 6 | import android.view.inputmethod.EditorInfo; 7 | 8 | import androidx.annotation.ArrayRes; 9 | import androidx.annotation.StringRes; 10 | 11 | import net.emilla.R; 12 | import net.emilla.activity.AssistActivity; 13 | import net.emilla.app.Apps; 14 | import net.emilla.math.Calculator; 15 | import net.emilla.math.Maths; 16 | 17 | public final class Calculate extends CategoryCommand { 18 | 19 | public static final String ENTRY = "calculate"; 20 | @StringRes 21 | public static final int NAME = R.string.command_calculate; 22 | @ArrayRes 23 | public static final int ALIASES = R.array.aliases_calculate; 24 | 25 | public static Yielder yielder() { 26 | return new Yielder(true, Calculate::new, ENTRY, NAME, ALIASES); 27 | } 28 | 29 | public static boolean possible() { 30 | return true; 31 | } 32 | 33 | private Calculate(AssistActivity act) { 34 | super(act, NAME, 35 | R.string.instruction_calculate, 36 | R.drawable.ic_calculate, 37 | R.string.summary_calculate, 38 | R.string.manual_calculate, 39 | EditorInfo.IME_ACTION_DONE); 40 | } 41 | 42 | @Override 43 | protected Intent makeFilter() { 44 | return Apps.categoryTask(CATEGORY_APP_CALCULATOR); 45 | } 46 | 47 | @Override 48 | protected void run(String expression) { 49 | giveMessage(Maths.prettyNumber(Calculator.compute(expression, NAME))); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/Dial.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | import static android.content.Intent.ACTION_DIAL; 4 | 5 | import android.content.Intent; 6 | import android.content.pm.PackageManager; 7 | import android.net.Uri; 8 | import android.view.inputmethod.EditorInfo; 9 | 10 | import androidx.annotation.ArrayRes; 11 | import androidx.annotation.StringRes; 12 | 13 | import net.emilla.R; 14 | import net.emilla.activity.AssistActivity; 15 | import net.emilla.app.Apps; 16 | 17 | public final class Dial extends CoreCommand { 18 | 19 | public static final String ENTRY = "dial"; 20 | @StringRes 21 | public static final int NAME = R.string.command_dial; 22 | @ArrayRes 23 | public static final int ALIASES = R.array.aliases_dial; 24 | 25 | public static Yielder yielder() { 26 | return new Yielder(true, Dial::new, ENTRY, NAME, ALIASES); 27 | } 28 | 29 | public static boolean possible(PackageManager pm) { 30 | return Apps.canDo(pm, new Intent(ACTION_DIAL)); 31 | } 32 | 33 | private Dial(AssistActivity act) { 34 | super(act, NAME, 35 | R.string.instruction_dial, 36 | R.drawable.ic_dial, 37 | R.string.summary_dial, 38 | R.string.manual_dial, 39 | EditorInfo.IME_ACTION_GO); 40 | } 41 | 42 | @Override 43 | protected void run() { 44 | appSucceed(new Intent(ACTION_DIAL)); 45 | } 46 | 47 | @Override 48 | protected void run(String numberOrPhoneword) { 49 | appSucceed(new Intent(ACTION_DIAL).setData(Uri.parse("tel:" + numberOrPhoneword))); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/Find.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | import android.view.inputmethod.EditorInfo; 4 | 5 | import androidx.annotation.ArrayRes; 6 | import androidx.annotation.StringRes; 7 | 8 | import net.emilla.R; 9 | import net.emilla.activity.AssistActivity; 10 | 11 | public final class Find extends CoreCommand { 12 | 13 | public static final String ENTRY = "find"; 14 | @StringRes 15 | public static final int NAME = R.string.command_find; 16 | @ArrayRes 17 | public static final int ALIASES = R.array.aliases_find; 18 | 19 | public static Yielder yielder() { 20 | return new Yielder(true, Find::new, ENTRY, NAME, ALIASES); 21 | } 22 | 23 | public static boolean possible() { 24 | return true; 25 | } 26 | 27 | private Find(AssistActivity act) { 28 | super(act, NAME, 29 | R.string.instruction_find, 30 | R.drawable.ic_find, 31 | R.string.summary_find, 32 | R.string.manual_find, 33 | EditorInfo.IME_ACTION_SEARCH); 34 | } 35 | 36 | @Override 37 | protected void run() { 38 | // todo: select file manager? 39 | throw badCommand(R.string.error_unfinished_file_search); 40 | } 41 | 42 | @Override 43 | protected void run(String fileOrFolder) { 44 | throw badCommand(R.string.error_unfinished_file_search); 45 | // where all should I be searching for files? shared storage? external drives? 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/Launch.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | import android.view.inputmethod.EditorInfo; 4 | 5 | import androidx.annotation.ArrayRes; 6 | import androidx.annotation.StringRes; 7 | import androidx.appcompat.app.AlertDialog; 8 | 9 | import net.emilla.R; 10 | import net.emilla.activity.AssistActivity; 11 | import net.emilla.app.Apps; 12 | import net.emilla.util.Dialogs; 13 | 14 | public final class Launch extends OpenCommand { 15 | 16 | public static final String ENTRY = "launch"; 17 | @StringRes 18 | public static final int NAME = R.string.command_launch; 19 | @ArrayRes 20 | public static final int ALIASES = R.array.aliases_launch; 21 | 22 | public static Yielder yielder() { 23 | return new Yielder(true, Launch::new, ENTRY, NAME, ALIASES); 24 | } 25 | 26 | public static boolean possible() { 27 | return true; 28 | } 29 | 30 | private Launch(AssistActivity act) { 31 | super(act, NAME, 32 | R.string.instruction_app, 33 | R.drawable.ic_launch, 34 | R.string.summary_launch, 35 | R.string.manual_launch, 36 | EditorInfo.IME_ACTION_GO); 37 | } 38 | 39 | @Override 40 | protected void run() { 41 | offerDialog(appChooser); 42 | } 43 | 44 | @Override 45 | protected void run(String app) { 46 | appSearchRun(app, Apps::launchIntent); 47 | } 48 | 49 | @Override 50 | protected AlertDialog.Builder makeChooser() { 51 | return Dialogs.appLaunches(activity); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/Navigate.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | import android.content.Intent; 4 | import android.content.pm.PackageManager; 5 | import android.net.Uri; 6 | import android.view.inputmethod.EditorInfo; 7 | 8 | import androidx.annotation.ArrayRes; 9 | import androidx.annotation.StringRes; 10 | 11 | import net.emilla.R; 12 | import net.emilla.activity.AssistActivity; 13 | import net.emilla.app.Apps; 14 | 15 | public final class Navigate extends CategoryCommand { 16 | 17 | public static final String ENTRY = "navigate"; 18 | @StringRes 19 | public static final int NAME = R.string.command_navigate; 20 | @ArrayRes 21 | public static final int ALIASES = R.array.aliases_navigate; 22 | 23 | public static Yielder yielder() { 24 | return new Yielder(true, Navigate::new, ENTRY, NAME, ALIASES); 25 | } 26 | 27 | public static boolean possible(PackageManager pm) { 28 | return Apps.canDo(pm, Apps.viewTask("geo:")); 29 | } 30 | 31 | private Navigate(AssistActivity act) { 32 | super(act, NAME, 33 | R.string.instruction_location, 34 | R.drawable.ic_navigate, 35 | R.string.summary_navigate, 36 | R.string.manual_navigate, 37 | EditorInfo.IME_ACTION_GO); 38 | } 39 | 40 | @Override 41 | protected Intent makeFilter() { 42 | return Apps.viewTask("geo:"); 43 | } 44 | 45 | @Override 46 | protected void run(String location) { 47 | // Todo: location bookmarks, navigate to contacts' addresses 48 | appSucceed(Apps.viewTask(Uri.parse("geo:0,0?q=" + location))); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/Note.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | import androidx.annotation.ArrayRes; 4 | import androidx.annotation.StringRes; 5 | 6 | import net.emilla.R; 7 | import net.emilla.activity.AssistActivity; 8 | 9 | public final class Note extends CoreDataCommand { 10 | 11 | public static final String ENTRY = "note"; 12 | @StringRes 13 | public static final int NAME = R.string.command_note; 14 | @ArrayRes 15 | public static final int ALIASES = R.array.aliases_note; 16 | 17 | public static Yielder yielder() { 18 | return new Yielder(true, Note::new, ENTRY, NAME, ALIASES); 19 | } 20 | 21 | public static boolean possible() { 22 | return true; 23 | } 24 | 25 | private Note(AssistActivity act) { 26 | super(act, NAME, 27 | R.string.instruction_file, 28 | R.drawable.ic_note, 29 | R.string.summary_note, 30 | R.string.manual_note, 31 | R.string.data_hint_note); 32 | } 33 | 34 | @Override 35 | protected void run() { 36 | throw badCommand(R.string.error_unfinished_notes); 37 | } 38 | 39 | @Override 40 | protected void run(String title) { 41 | throw badCommand(R.string.error_unfinished_notes); 42 | } 43 | 44 | @Override 45 | protected void runWithData(String text) { 46 | throw badCommand(R.string.error_unfinished_notes); 47 | } 48 | 49 | @Override 50 | protected void runWithData(String title, String text) { 51 | throw badCommand(R.string.error_unfinished_notes); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/Pause.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | import android.media.AudioManager; 4 | import android.view.inputmethod.EditorInfo; 5 | 6 | import androidx.annotation.ArrayRes; 7 | import androidx.annotation.StringRes; 8 | 9 | import net.emilla.R; 10 | import net.emilla.activity.AssistActivity; 11 | import net.emilla.util.MediaControl; 12 | import net.emilla.util.Services; 13 | 14 | public final class Pause extends CoreCommand { 15 | 16 | public static final String ENTRY = "pause"; 17 | @StringRes 18 | public static final int NAME = R.string.command_pause; 19 | @ArrayRes 20 | public static final int ALIASES = R.array.aliases_pause; 21 | 22 | public static Yielder yielder() { 23 | return new Yielder(false, Pause::new, ENTRY, NAME, ALIASES); 24 | } 25 | 26 | public static boolean possible() { 27 | return true; 28 | } 29 | 30 | private Pause(AssistActivity act) { 31 | super(act, NAME, 32 | R.string.instruction_pause, 33 | R.drawable.ic_pause, 34 | R.string.summary_pause, 35 | R.string.manual_pause, 36 | EditorInfo.IME_ACTION_DONE); 37 | } 38 | 39 | @Override 40 | protected void run() { 41 | AudioManager am = Services.audio(activity); 42 | MediaControl.sendPauseEvent(am); 43 | give(() -> {}); 44 | } 45 | 46 | @Override 47 | protected void run(String ignored) { 48 | run(); // Todo: remove this from the interface for non-instructables. 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/Play.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | import android.media.AudioManager; 4 | import android.view.inputmethod.EditorInfo; 5 | 6 | import androidx.annotation.ArrayRes; 7 | import androidx.annotation.StringRes; 8 | 9 | import net.emilla.R; 10 | import net.emilla.activity.AssistActivity; 11 | import net.emilla.util.MediaControl; 12 | import net.emilla.util.Services; 13 | 14 | public final class Play extends CoreCommand { 15 | 16 | public static final String ENTRY = "play"; 17 | @StringRes 18 | public static final int NAME = R.string.command_play; 19 | @ArrayRes 20 | public static final int ALIASES = R.array.aliases_play; 21 | 22 | public static Yielder yielder() { 23 | return new Yielder(true, Play::new, ENTRY, NAME, ALIASES); 24 | } 25 | 26 | public static boolean possible() { 27 | return true; 28 | } 29 | 30 | private Play(AssistActivity act) { 31 | super(act, NAME, 32 | R.string.instruction_play, 33 | R.drawable.ic_play, 34 | R.string.summary_play, 35 | R.string.manual_play, 36 | EditorInfo.IME_ACTION_GO); 37 | } 38 | 39 | @Override 40 | protected void run() { 41 | AudioManager am = Services.audio(activity); 42 | MediaControl.sendPlayEvent(am); 43 | give(() -> {}); 44 | } 45 | 46 | @Override 47 | protected void run(String media) { 48 | throw badCommand(R.string.error_unfinished_feature); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/Roll.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | import android.view.inputmethod.EditorInfo; 4 | 5 | import androidx.annotation.ArrayRes; 6 | import androidx.annotation.StringRes; 7 | 8 | import net.emilla.R; 9 | import net.emilla.activity.AssistActivity; 10 | import net.emilla.lang.Lang; 11 | import net.emilla.util.Dialogs; 12 | 13 | import java.util.Random; 14 | 15 | public final class Roll extends CoreCommand { 16 | 17 | public static final String ENTRY = "roll"; 18 | @StringRes 19 | public static final int NAME = R.string.command_roll; 20 | @ArrayRes 21 | public static final int ALIASES = R.array.aliases_roll; 22 | 23 | public static Yielder yielder() { 24 | return new Yielder(true, Roll::new, ENTRY, NAME, ALIASES); 25 | } 26 | 27 | public static boolean possible() { 28 | return true; 29 | } 30 | 31 | private Roll(AssistActivity act) { 32 | super(act, NAME, 33 | R.string.instruction_text, 34 | R.drawable.ic_roll, 35 | R.string.summary_roll, 36 | R.string.manual_roll, 37 | EditorInfo.IME_ACTION_DONE); 38 | } 39 | 40 | @Override 41 | protected void run() { 42 | var rand = new Random(); 43 | @StringRes int msg = rand.nextBoolean() ? R.string.heads : R.string.tails; 44 | giveDialog(Dialogs.message(activity, NAME, msg)); 45 | } 46 | 47 | @Override 48 | protected void run(String roll) { 49 | var dices = Lang.dices(roll, NAME); 50 | var rand = new Random(); 51 | var msg = String.valueOf(dices.roll(rand)); 52 | giveDialog(Dialogs.message(activity, NAME, msg)); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/Setting.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | import android.view.inputmethod.EditorInfo; 4 | 5 | import androidx.annotation.ArrayRes; 6 | import androidx.annotation.StringRes; 7 | 8 | import net.emilla.R; 9 | import net.emilla.activity.AssistActivity; 10 | 11 | public final class Setting extends CoreCommand { 12 | 13 | public static final String ENTRY = "setting"; 14 | @StringRes 15 | public static final int NAME = R.string.command_setting; 16 | @ArrayRes 17 | public static final int ALIASES = R.array.aliases_setting; 18 | 19 | public static Yielder yielder() { 20 | return new Yielder(true, Setting::new, ENTRY, NAME, ALIASES); 21 | } 22 | 23 | public static boolean possible() { 24 | return true; 25 | } 26 | 27 | private Setting(AssistActivity act) { 28 | super(act, NAME, 29 | R.string.instruction_setting, 30 | R.drawable.ic_settings, 31 | R.string.summary_setting, 32 | R.string.manual_setting, 33 | EditorInfo.IME_ACTION_DONE); 34 | } 35 | 36 | @Override 37 | protected void run() { 38 | throw badCommand(R.string.error_unfinished_setting); 39 | } 40 | 41 | @Override 42 | protected void run(String query) { 43 | throw badCommand(R.string.error_unfinished_setting); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/Torch.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | import android.content.pm.PackageManager; 4 | import android.view.inputmethod.EditorInfo; 5 | 6 | import androidx.annotation.ArrayRes; 7 | import androidx.annotation.StringRes; 8 | 9 | import net.emilla.R; 10 | import net.emilla.activity.AssistActivity; 11 | import net.emilla.util.Features; 12 | import net.emilla.util.TorchManager; 13 | 14 | public final class Torch extends CoreCommand { 15 | 16 | public static final String ENTRY = "torch"; 17 | @StringRes 18 | public static final int NAME = R.string.command_torch; 19 | @ArrayRes 20 | public static final int ALIASES = R.array.aliases_torch; 21 | 22 | public static Yielder yielder() { 23 | return new Yielder(false, Torch::new, ENTRY, NAME, ALIASES); 24 | } 25 | 26 | public static boolean possible(PackageManager pm) { 27 | return Features.torch(pm); 28 | } 29 | 30 | private Torch(AssistActivity act) { 31 | super(act, NAME, 32 | R.string.instruction_torch, 33 | R.drawable.ic_torch, 34 | R.string.summary_torch, 35 | R.string.manual_torch, 36 | EditorInfo.IME_ACTION_DONE); 37 | } 38 | 39 | @Override 40 | protected void run() { 41 | TorchManager.toggle(activity, NAME); 42 | } 43 | 44 | @Override 45 | protected void run(String ignored) { 46 | run(); // Todo: remove this from the interface for non-instructables. 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/Weather.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | import static android.content.Intent.CATEGORY_APP_WEATHER; 4 | 5 | import android.content.Intent; 6 | import android.content.pm.PackageManager; 7 | import android.view.inputmethod.EditorInfo; 8 | 9 | import androidx.annotation.ArrayRes; 10 | import androidx.annotation.StringRes; 11 | 12 | import net.emilla.R; 13 | import net.emilla.activity.AssistActivity; 14 | import net.emilla.app.Apps; 15 | 16 | public final class Weather extends CategoryCommand { 17 | 18 | public static final String ENTRY = "weather"; 19 | @StringRes 20 | public static final int NAME = R.string.command_weather; 21 | @ArrayRes 22 | public static final int ALIASES = R.array.aliases_weather; 23 | 24 | public static Yielder yielder() { 25 | return new Yielder(true, Weather::new, ENTRY, NAME, ALIASES); 26 | } 27 | 28 | public static boolean possible(PackageManager pm) { 29 | return Apps.canDo(pm, Apps.categoryTask(CATEGORY_APP_WEATHER)); 30 | } 31 | 32 | private Weather(AssistActivity act) { 33 | super(act, NAME, 34 | R.string.instruction_app, 35 | R.drawable.ic_weather, 36 | R.string.summary_weather, 37 | R.string.manual_weather, 38 | EditorInfo.IME_ACTION_GO); 39 | } 40 | 41 | @Override 42 | protected Intent makeFilter() { 43 | return Apps.categoryTask(CATEGORY_APP_WEATHER); 44 | // TODO: figure out what's up with API level stuff here. 45 | } 46 | 47 | @Override 48 | protected void run(String location) { 49 | throw badCommand(R.string.error_unfinished_categorical_app_search); // Todo 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/config/Aliases.java: -------------------------------------------------------------------------------- 1 | package net.emilla.config; 2 | 3 | import android.content.SharedPreferences; 4 | import android.content.res.Resources; 5 | 6 | import androidx.annotation.ArrayRes; 7 | import androidx.annotation.Nullable; 8 | 9 | import net.emilla.app.AppEntry; 10 | 11 | import java.util.Set; 12 | 13 | public final class Aliases { 14 | 15 | @Nullable 16 | public static Set appSet( 17 | SharedPreferences prefs, 18 | Resources res, 19 | AppEntry app 20 | ) { 21 | @ArrayRes int setId = app.aliases(); 22 | String entry = app.entry(); 23 | return setId == 0 ? prefs.getStringSet(setKey(entry), null) 24 | : coreSet(prefs, res, entry, setId); 25 | } 26 | 27 | @Nullable 28 | public static Set coreSet( 29 | SharedPreferences prefs, 30 | Resources res, 31 | String entry, 32 | @ArrayRes int setId 33 | ) { 34 | Set set = Set.of(res.getStringArray(setId)); 35 | set = prefs.getStringSet(setKey(entry), set); 36 | return set.isEmpty() ? null : set; 37 | } 38 | 39 | public static String setKey(String entry) { 40 | return "aliases_" + entry; 41 | } 42 | 43 | public static String textKey(String entry) { 44 | return "aliases_" + entry + "_text"; 45 | } 46 | 47 | private Aliases() {} 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/config/AssistantFragment.java: -------------------------------------------------------------------------------- 1 | package net.emilla.config; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import androidx.annotation.Nullable; 9 | import androidx.fragment.app.Fragment; 10 | 11 | import net.emilla.databinding.FragmentAssistantBinding; 12 | 13 | public final class AssistantFragment extends Fragment { 14 | 15 | @Nullable 16 | private FragmentAssistantBinding mBinding; 17 | 18 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 19 | mBinding = FragmentAssistantBinding.inflate(inflater, container, false); 20 | return mBinding.getRoot(); 21 | } 22 | 23 | @Override 24 | public void onDestroyView() { 25 | super.onDestroyView(); 26 | mBinding = null; 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/config/ConfigActivity.java: -------------------------------------------------------------------------------- 1 | package net.emilla.config; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.navigation.fragment.NavHostFragment; 6 | import androidx.navigation.ui.AppBarConfiguration; 7 | import androidx.navigation.ui.NavigationUI; 8 | 9 | import net.emilla.R; 10 | import net.emilla.activity.AssistActivity; 11 | import net.emilla.activity.EmillaActivity; 12 | import net.emilla.app.Apps; 13 | import net.emilla.databinding.ActivityConfigBinding; 14 | 15 | public final class ConfigActivity extends EmillaActivity { 16 | 17 | private ActivityConfigBinding mBinding; 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | 23 | mBinding = ActivityConfigBinding.inflate(getLayoutInflater()); 24 | setContentView(mBinding.getRoot()); 25 | 26 | mBinding.navView.setOnItemReselectedListener(item -> { 27 | boolean assistantItem = item.getItemId() == R.id.nav_assistant; 28 | if (assistantItem) startActivity(Apps.meTask(this, AssistActivity.class)); 29 | }); 30 | var navHostFragment = (NavHostFragment) getSupportFragmentManager() 31 | .findFragmentById(R.id.nav_host_fragment_activity_config); 32 | if (navHostFragment == null) return; 33 | var navController = navHostFragment.getNavController(); 34 | var appBarConfiguration = new AppBarConfiguration.Builder(R.id.nav_commands, 35 | R.id.nav_assistant, R.id.nav_settings).build(); 36 | NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration); 37 | NavigationUI.setupWithNavController(mBinding.navView, navController); 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/config/EmillaSettingsFragment.java: -------------------------------------------------------------------------------- 1 | package net.emilla.config; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import android.content.SharedPreferences; 6 | 7 | import androidx.preference.Preference; 8 | import androidx.preference.PreferenceFragmentCompat; 9 | 10 | import net.emilla.activity.EmillaActivity; 11 | 12 | public abstract class EmillaSettingsFragment extends PreferenceFragmentCompat { 13 | 14 | protected final T preferenceOf(String key) { 15 | return requireNonNull(findPreference(key)); 16 | } 17 | 18 | protected final EmillaActivity emillaActivity() { 19 | return (EmillaActivity) requireActivity(); 20 | } 21 | 22 | protected final SharedPreferences prefs() { 23 | return requireNonNull(getPreferenceManager().getSharedPreferences()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/contact/MultiSearch.java: -------------------------------------------------------------------------------- 1 | package net.emilla.contact; 2 | 3 | import net.emilla.lang.Lang; 4 | 5 | public record MultiSearch(String selection, String[] selectionArgs, boolean hasMultipleTerms) { 6 | 7 | public static MultiSearch instance(String baseSelection, String search) { 8 | String[] terms = Lang.list(search).items(); 9 | 10 | var selection = new StringBuilder(baseSelection); 11 | terms[0] = "%" + terms[0] + "%"; 12 | for (int i = 1; i < terms.length; i++) { 13 | terms[i] = "%" + terms[i] + "%"; 14 | selection.append(" OR ").append(baseSelection); 15 | } 16 | 17 | return new MultiSearch(selection.toString(), terms, terms.length > 1); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/contact/adapter/ContactCardAdapter.java: -------------------------------------------------------------------------------- 1 | package net.emilla.contact.adapter; 2 | 3 | import android.content.Context; 4 | import android.database.Cursor; 5 | import android.net.Uri; 6 | import android.provider.ContactsContract.Contacts; 7 | import android.view.View; 8 | 9 | import net.emilla.contact.ContactItemView; 10 | 11 | public final class ContactCardAdapter extends ContactCursorAdapter { 12 | 13 | public ContactCardAdapter(Context ctx) { 14 | super(ctx); 15 | } 16 | 17 | @Override 18 | public Uri contentUri() { 19 | return Contacts.CONTENT_URI; 20 | } 21 | 22 | @Override 23 | public Uri filterUri(String search) { 24 | return Contacts.CONTENT_FILTER_URI.buildUpon() 25 | .appendPath(search) 26 | .build(); 27 | } 28 | 29 | @Override 30 | public String[] projection() { 31 | return BASE_COLS; 32 | } 33 | 34 | @Override 35 | public void bindView(View view, Context ctx, Cursor cur) { 36 | var item = (ContactItemView) view; 37 | item.setContactInfo(cur.getLong(IDX_ID), cur.getString(IDX_KEY), cur.getString(IDX_NAME), 38 | cur.getString(IDX_PHOTO), cur.getInt(IDX_STARRED) != 0); 39 | item.setSelected(true); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/contact/fragment/ContactCardsFragment.java: -------------------------------------------------------------------------------- 1 | package net.emilla.contact.fragment; 2 | 3 | import static net.emilla.contact.adapter.ContactCursorAdapter.IDX_ID; 4 | import static net.emilla.contact.adapter.ContactCursorAdapter.IDX_KEY; 5 | 6 | import android.database.Cursor; 7 | import android.net.Uri; 8 | import android.provider.ContactsContract.Contacts; 9 | import android.widget.ListView; 10 | 11 | import androidx.annotation.Nullable; 12 | 13 | import net.emilla.contact.adapter.ContactCardAdapter; 14 | import net.emilla.contact.adapter.ContactCursorAdapter; 15 | 16 | public final class ContactCardsFragment extends ContactsFragment { 17 | 18 | public static ContactCardsFragment newInstance() { 19 | return newInstance(new ContactCardsFragment(), false); 20 | } 21 | 22 | @Override 23 | protected ContactCursorAdapter cursorAdapter() { 24 | return new ContactCardAdapter(requireContext()); 25 | } 26 | 27 | @Override @Nullable 28 | protected Uri selectedContactsInternal(ListView contactList, Cursor cur) { 29 | if (cur.getCount() == 1) { 30 | cur.moveToFirst(); 31 | return Contacts.getLookupUri(cur.getLong(IDX_ID), cur.getString(IDX_KEY)); 32 | } 33 | 34 | int pos = contactList.getCheckedItemPosition(); 35 | if (cur.moveToPosition(pos)) return Contacts.getLookupUri(cur.getLong(IDX_ID), 36 | cur.getString(IDX_KEY)); 37 | 38 | return null; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/contact/fragment/ContactEmailsFragment.java: -------------------------------------------------------------------------------- 1 | package net.emilla.contact.fragment; 2 | 3 | import static net.emilla.contact.adapter.ContactEmailAdapter.IDX_ADDRESS; 4 | 5 | import android.database.Cursor; 6 | import android.widget.ListView; 7 | 8 | import androidx.annotation.Nullable; 9 | 10 | import net.emilla.contact.adapter.ContactCursorAdapter; 11 | import net.emilla.contact.adapter.ContactEmailAdapter; 12 | 13 | public final class ContactEmailsFragment extends ContactsFragment { 14 | 15 | public static ContactEmailsFragment newInstance(boolean multiSelect) { 16 | return newInstance(new ContactEmailsFragment(), multiSelect); 17 | } 18 | 19 | @Override 20 | protected ContactCursorAdapter cursorAdapter() { 21 | return new ContactEmailAdapter(requireContext()); 22 | } 23 | 24 | @Override @Nullable 25 | protected String selectedContactsInternal(ListView contactList, Cursor cur) { 26 | return multiSelectedCsv(contactList, cur, IDX_ADDRESS); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/contact/fragment/ContactPhonesFragment.java: -------------------------------------------------------------------------------- 1 | package net.emilla.contact.fragment; 2 | 3 | import static net.emilla.contact.adapter.ContactPhoneAdapter.IDX_NUMBER; 4 | 5 | import android.database.Cursor; 6 | import android.widget.ListView; 7 | 8 | import androidx.annotation.Nullable; 9 | 10 | import net.emilla.contact.adapter.ContactCursorAdapter; 11 | import net.emilla.contact.adapter.ContactPhoneAdapter; 12 | 13 | public final class ContactPhonesFragment extends ContactsFragment { 14 | 15 | public static ContactPhonesFragment newInstance(boolean multiSelect) { 16 | return newInstance(new ContactPhonesFragment(), multiSelect); 17 | } 18 | 19 | @Override 20 | protected ContactCursorAdapter cursorAdapter() { 21 | return new ContactPhoneAdapter(requireContext()); 22 | } 23 | 24 | @Override @Nullable 25 | protected String selectedContactsInternal(ListView contactList, Cursor cur) { 26 | return multiSelectedCsv(contactList, cur, IDX_NUMBER); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/content/receive/AppChoiceReceiver.java: -------------------------------------------------------------------------------- 1 | package net.emilla.content.receive; 2 | 3 | @FunctionalInterface 4 | public interface AppChoiceReceiver extends ResultReceiver { 5 | 6 | /** 7 | * Notifies the object of whether the chooser was accepted or dismissed. 8 | * 9 | * @param chosen true if an app was chosen, false if the chooser was dismissed. 10 | */ 11 | void provide(boolean chosen); 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/content/receive/ContactCardReceiver.java: -------------------------------------------------------------------------------- 1 | package net.emilla.content.receive; 2 | 3 | import static net.emilla.contact.adapter.ContactCursorAdapter.IDX_ID; 4 | import static net.emilla.contact.adapter.ContactCursorAdapter.IDX_KEY; 5 | 6 | import android.database.Cursor; 7 | import android.net.Uri; 8 | import android.provider.ContactsContract; 9 | 10 | @FunctionalInterface 11 | public interface ContactCardReceiver extends ContactReceiver { 12 | 13 | @Override 14 | default void useContact(Cursor cur) { 15 | long id = cur.getLong(IDX_ID); 16 | var key = cur.getString(IDX_KEY); 17 | 18 | provide(ContactsContract.Contacts.getLookupUri(id, key)); 19 | } 20 | 21 | /** 22 | * @param contact is provided to the object. 23 | */ 24 | void provide(Uri contact); 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/content/receive/ContactDataReceiver.java: -------------------------------------------------------------------------------- 1 | package net.emilla.content.receive; 2 | 3 | public interface ContactDataReceiver extends ContactReceiver { 4 | 5 | /** 6 | * @param data is provided to the object. 7 | */ 8 | void provide(String data); 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/content/receive/ContactReceiver.java: -------------------------------------------------------------------------------- 1 | package net.emilla.content.receive; 2 | 3 | import android.database.Cursor; 4 | 5 | @FunctionalInterface 6 | public interface ContactReceiver extends ResultReceiver { 7 | 8 | void useContact(Cursor cur); 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/content/receive/EmailReceiver.java: -------------------------------------------------------------------------------- 1 | package net.emilla.content.receive; 2 | 3 | import static net.emilla.contact.adapter.ContactEmailAdapter.IDX_ADDRESS; 4 | 5 | import android.database.Cursor; 6 | 7 | @FunctionalInterface 8 | public interface EmailReceiver extends ContactDataReceiver { 9 | 10 | @Override 11 | default void useContact(Cursor cur) { 12 | provide(cur.getString(IDX_ADDRESS)); 13 | } 14 | 15 | /** 16 | * @param emailAddress is provided to the object. 17 | */ 18 | void provide(String emailAddress); 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/content/receive/FilesReceiver.java: -------------------------------------------------------------------------------- 1 | package net.emilla.content.receive; 2 | 3 | import android.net.Uri; 4 | 5 | import net.emilla.R; 6 | import net.emilla.activity.AssistActivity; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | public final class FilesReceiver implements ResultReceiver { 12 | 13 | private final AssistActivity mActivity; 14 | private final String mCommandEntry; 15 | 16 | public FilesReceiver(AssistActivity act, String commandEntry) { 17 | mActivity = act; 18 | mCommandEntry = commandEntry; 19 | } 20 | 21 | public void provide(List attachments) { 22 | if (attachments.isEmpty()) return; 23 | 24 | ArrayList attaches = mActivity.attachments(mCommandEntry); 25 | if (attaches == null) attaches = new ArrayList<>(attachments); 26 | else for (Uri attachment : attachments) { 27 | int index = attaches.indexOf(attachment); 28 | if (index == -1) attaches.add(attachment); 29 | else attaches.remove(index); // TODO: better attachment UI. 30 | } 31 | 32 | int size = attaches.size(); 33 | if (size == 0) attaches = null; 34 | 35 | mActivity.putAttachments(mCommandEntry, attaches); 36 | 37 | var res = mActivity.getResources(); 38 | mActivity.toast(res.getQuantityString(R.plurals.toast_files_attached, size, size)); 39 | // Todo: better feedback. 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/content/receive/PhoneReceiver.java: -------------------------------------------------------------------------------- 1 | package net.emilla.content.receive; 2 | 3 | import static net.emilla.contact.adapter.ContactPhoneAdapter.IDX_NUMBER; 4 | 5 | import android.database.Cursor; 6 | 7 | @FunctionalInterface 8 | public interface PhoneReceiver extends ContactDataReceiver { 9 | 10 | @Override 11 | default void useContact(Cursor cur) { 12 | provide(cur.getString(IDX_NUMBER)); 13 | } 14 | 15 | /** 16 | * @param phoneNumber is provided to the object. 17 | */ 18 | void provide(String phoneNumber); 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/content/receive/ResultReceiver.java: -------------------------------------------------------------------------------- 1 | package net.emilla.content.receive; 2 | 3 | public interface ResultReceiver {} 4 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/content/retrieve/ContactCardRetriever.java: -------------------------------------------------------------------------------- 1 | package net.emilla.content.retrieve; 2 | 3 | import android.net.Uri; 4 | 5 | import androidx.activity.result.contract.ActivityResultContracts.PickContact; 6 | 7 | import net.emilla.R; 8 | import net.emilla.activity.AssistActivity; 9 | import net.emilla.content.receive.ContactCardReceiver; 10 | 11 | public final class ContactCardRetriever extends ResultRetriever { 12 | 13 | public ContactCardRetriever(AssistActivity act) { 14 | super(act, new PickContact()); 15 | } 16 | 17 | public void retrieve(ContactCardReceiver receiver) { 18 | if (alreadyHas(receiver)) return; 19 | launch(null); 20 | } 21 | 22 | @Override 23 | protected ResultCallback makeCallback() { 24 | return new ContactCallback(); 25 | } 26 | 27 | private /*inner*/ final class ContactCallback extends ResultCallback { 28 | 29 | @Override 30 | protected void onActivityResult(Uri contact, ContactCardReceiver receiver) { 31 | if (contact != null) { 32 | activity.suppressResumeChime(); 33 | receiver.provide(contact); 34 | } else activity.toast(R.string.toast_contact_not_selected); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/content/retrieve/ContactEmailRetriever.java: -------------------------------------------------------------------------------- 1 | package net.emilla.content.retrieve; 2 | 3 | import android.content.ContentResolver; 4 | import android.content.Context; 5 | import android.net.Uri; 6 | import android.provider.ContactsContract.CommonDataKinds.Email; 7 | 8 | import androidx.annotation.Nullable; 9 | 10 | import net.emilla.activity.AssistActivity; 11 | import net.emilla.util.Contacts; 12 | 13 | public final class ContactEmailRetriever extends ContactDataRetriever { 14 | 15 | private static final class PickContactEmail extends PickContactData { 16 | 17 | PickContactEmail(Context ctx) { 18 | super(ctx); 19 | } 20 | 21 | @Override 22 | protected String contentType() { 23 | return Email.CONTENT_TYPE; 24 | } 25 | 26 | @Override @Nullable 27 | protected String parseData(Uri contact, ContentResolver cr) { 28 | return Contacts.emailAddress(contact, cr); 29 | } 30 | } 31 | 32 | public ContactEmailRetriever(AssistActivity act) { 33 | super(act, new PickContactEmail(act)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/content/retrieve/ContactPhoneRetriever.java: -------------------------------------------------------------------------------- 1 | package net.emilla.content.retrieve; 2 | 3 | import android.content.ContentResolver; 4 | import android.content.Context; 5 | import android.net.Uri; 6 | import android.provider.ContactsContract.CommonDataKinds.Phone; 7 | 8 | import androidx.annotation.Nullable; 9 | 10 | import net.emilla.activity.AssistActivity; 11 | import net.emilla.util.Contacts; 12 | 13 | public final class ContactPhoneRetriever extends ContactDataRetriever { 14 | 15 | private static final class PickContactPhone extends PickContactData { 16 | 17 | PickContactPhone(Context ctx) { 18 | super(ctx); 19 | } 20 | 21 | @Override 22 | protected String contentType() { 23 | return Phone.CONTENT_TYPE; 24 | } 25 | 26 | @Override @Nullable 27 | protected String parseData(Uri contact, ContentResolver cr) { 28 | return Contacts.phoneNumber(contact, cr); 29 | } 30 | } 31 | 32 | public ContactPhoneRetriever(AssistActivity act) { 33 | super(act, new PickContactPhone(act)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/content/retrieve/FilesRetriever.java: -------------------------------------------------------------------------------- 1 | package net.emilla.content.retrieve; 2 | 3 | import android.net.Uri; 4 | 5 | import androidx.activity.result.contract.ActivityResultContracts.GetMultipleContents; 6 | 7 | import net.emilla.R; 8 | import net.emilla.activity.AssistActivity; 9 | import net.emilla.content.receive.FilesReceiver; 10 | 11 | import java.util.List; 12 | 13 | public final class FilesRetriever extends ResultRetriever, FilesReceiver> { 14 | 15 | public FilesRetriever(AssistActivity act) { 16 | super(act, new GetMultipleContents()); 17 | } 18 | 19 | public void retrieve(FilesReceiver receiver, String mimeType) { 20 | if (alreadyHas(receiver)) return; 21 | launch(mimeType); 22 | } 23 | 24 | @Override 25 | protected ResultCallback makeCallback() { 26 | return new FileCallback(); 27 | } 28 | 29 | private /*inner*/ final class FileCallback extends ResultCallback { 30 | 31 | @Override 32 | protected void onActivityResult(List files, FilesReceiver receiver) { 33 | if (files.isEmpty()) activity.toast(R.string.toast_files_not_selected); 34 | else receiver.provide(files); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/content/retrieve/MediaRetriever.java: -------------------------------------------------------------------------------- 1 | package net.emilla.content.retrieve; 2 | 3 | import static net.emilla.chime.Chimer.RESUME; 4 | 5 | import android.net.Uri; 6 | 7 | import androidx.activity.result.PickVisualMediaRequest; 8 | import androidx.activity.result.contract.ActivityResultContracts.PickMultipleVisualMedia; 9 | import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia; 10 | 11 | import net.emilla.R; 12 | import net.emilla.activity.AssistActivity; 13 | import net.emilla.content.receive.FilesReceiver; 14 | 15 | import java.util.List; 16 | 17 | public final class MediaRetriever extends ResultRetriever, FilesReceiver> { 18 | 19 | public MediaRetriever(AssistActivity act) { 20 | super(act, new PickMultipleVisualMedia()); 21 | } 22 | 23 | public void retrieve(FilesReceiver receiver) { 24 | if (alreadyHas(receiver)) return; 25 | launch(new PickVisualMediaRequest.Builder() 26 | .setMediaType(PickVisualMedia.ImageAndVideo.INSTANCE) 27 | .build()); 28 | } 29 | 30 | @Override 31 | protected ResultCallback makeCallback() { 32 | return new MediaCallback(); 33 | } 34 | 35 | private /*inner*/ final class MediaCallback extends ResultCallback { 36 | 37 | @Override 38 | protected void onActivityResult(List media, FilesReceiver receiver) { 39 | if (media.isEmpty()) activity.toast(R.string.toast_media_not_selected); 40 | else receiver.provide(media); 41 | activity.chime(RESUME); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/event/EventScheduler.java: -------------------------------------------------------------------------------- 1 | package net.emilla.event; 2 | 3 | import android.app.AlarmManager; 4 | import android.app.PendingIntent; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.os.Build; 8 | 9 | import net.emilla.util.Services; 10 | 11 | public abstract class EventScheduler

{ 12 | 13 | protected final Context context; 14 | private final AlarmManager mAlarmManager; 15 | 16 | public EventScheduler(Context ctx) { 17 | context = ctx; 18 | mAlarmManager = Services.alarm(ctx); 19 | } 20 | 21 | public final void plan(P plan) { 22 | var pendingIntent = pendingIntentFor(plan); 23 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S || mAlarmManager.canScheduleExactAlarms()) { 24 | mAlarmManager.setExact(AlarmManager.RTC_WAKEUP, plan.time, pendingIntent); 25 | } else mAlarmManager.set(AlarmManager.RTC_WAKEUP, plan.time, pendingIntent); 26 | // Todo: communicate which will happen in the settings. 27 | } 28 | 29 | public final void cancel(P plan) { 30 | mAlarmManager.cancel(pendingIntentFor(plan)); 31 | } 32 | 33 | private PendingIntent pendingIntentFor(P plan) { 34 | int flags = PendingIntent.FLAG_UPDATE_CURRENT 35 | | PendingIntent.FLAG_IMMUTABLE; 36 | return PendingIntent.getBroadcast(context, plan.slot, intentFor(plan), flags); 37 | } 38 | 39 | protected abstract Intent intentFor(P plan); 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/event/PingPlan.java: -------------------------------------------------------------------------------- 1 | package net.emilla.event; 2 | 3 | import android.app.Notification; 4 | 5 | public final class PingPlan extends Plan { 6 | 7 | public final Notification ping; 8 | public final String channel; 9 | 10 | public PingPlan(int slot, long time, Notification ping, String channel) { 11 | super(slot, time); 12 | 13 | this.ping = ping; 14 | this.channel = channel; 15 | } 16 | 17 | public static PingPlan afterSeconds(int slot, int seconds, Notification ping, String channel) { 18 | return new PingPlan(slot, System.currentTimeMillis() + seconds * 1000L, ping, channel); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/event/PingReceiver.java: -------------------------------------------------------------------------------- 1 | package net.emilla.event; 2 | 3 | import static net.emilla.BuildConfig.DEBUG; 4 | 5 | import android.annotation.SuppressLint; 6 | import android.content.BroadcastReceiver; 7 | import android.content.Context; 8 | import android.content.Intent; 9 | import android.util.Log; 10 | 11 | import net.emilla.ping.PingIntent; 12 | import net.emilla.ping.Pinger; 13 | import net.emilla.util.Permissions; 14 | 15 | public final class PingReceiver extends BroadcastReceiver { 16 | 17 | private static final String TAG = PingReceiver.class.getSimpleName(); 18 | 19 | @Override @SuppressLint("MissingPermission") 20 | public void onReceive(Context ctx, Intent intent) { 21 | if (Permissions.pings(ctx)) Pinger.of(ctx, new PingIntent(intent)).ping(); 22 | else if (DEBUG) Log.e(TAG, "Unable to ping due to lack of permission."); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/event/PingScheduler.java: -------------------------------------------------------------------------------- 1 | package net.emilla.event; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | 6 | import net.emilla.ping.PingIntent; 7 | 8 | public final class PingScheduler extends EventScheduler { 9 | 10 | public PingScheduler(Context ctx) { 11 | super(ctx); 12 | } 13 | 14 | @Override 15 | protected Intent intentFor(PingPlan plan) { 16 | return new PingIntent(context, plan.ping, plan.channel); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/event/Plan.java: -------------------------------------------------------------------------------- 1 | package net.emilla.event; 2 | 3 | public /*open*/ class Plan { 4 | 5 | public static final int 6 | POMODORO_WARNING = 1, 7 | POMODORO_ENDED = 2; 8 | 9 | public final int slot; 10 | public final long time; 11 | 12 | public Plan(int slot, long time) { 13 | this.slot = slot; 14 | this.time = time; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/exception/EmillaException.java: -------------------------------------------------------------------------------- 1 | package net.emilla.exception; 2 | 3 | import androidx.annotation.StringRes; 4 | 5 | public final class EmillaException extends RuntimeException { 6 | 7 | @StringRes 8 | public final int title, message; 9 | 10 | public EmillaException(@StringRes int title, @StringRes int message) { 11 | super(); 12 | 13 | this.title = title; 14 | this.message = message; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/lang/date/Duration.java: -------------------------------------------------------------------------------- 1 | package net.emilla.lang.date; 2 | 3 | import androidx.annotation.StringRes; 4 | 5 | import net.emilla.R; 6 | import net.emilla.exception.EmillaException; 7 | 8 | public final class Duration { 9 | 10 | public final int seconds; 11 | 12 | public Duration(int seconds, @StringRes int errorTitle) { 13 | if (seconds <= 0) throw new EmillaException(errorTitle, R.string.error_bad_minutes); 14 | this.seconds = seconds; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/lang/date/HourMin.java: -------------------------------------------------------------------------------- 1 | package net.emilla.lang.date; 2 | 3 | public interface HourMin { 4 | 5 | int hour24(); 6 | int minute(); 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/lang/date/Weekdays.java: -------------------------------------------------------------------------------- 1 | package net.emilla.lang.date; 2 | 3 | import java.util.ArrayList; 4 | 5 | public interface Weekdays { 6 | 7 | boolean empty(); 8 | ArrayList days(); 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/lang/date/impl/DurationEN_US.java: -------------------------------------------------------------------------------- 1 | package net.emilla.lang.date.impl; 2 | 3 | import static java.lang.Double.parseDouble; 4 | 5 | import androidx.annotation.StringRes; 6 | 7 | import net.emilla.R; 8 | import net.emilla.exception.EmillaException; 9 | import net.emilla.lang.date.Duration; 10 | 11 | public final class DurationEN_US { 12 | 13 | public static Duration instance(String minutes, @StringRes int errorTitle) { 14 | try { 15 | int seconds = (int) (parseDouble(minutes) * 60.0); 16 | // Todo: other time units, clock notation. 17 | return new Duration(seconds, errorTitle); 18 | } catch (NumberFormatException e) { 19 | throw new EmillaException(errorTitle, R.string.error_bad_minutes); 20 | } 21 | } 22 | 23 | private DurationEN_US() {} 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/lang/grammar/ListPhrase.java: -------------------------------------------------------------------------------- 1 | package net.emilla.lang.grammar; 2 | 3 | @FunctionalInterface 4 | public interface ListPhrase { 5 | 6 | String[] items(); 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/lang/grammar/impl/ListPhraseEN_US.java: -------------------------------------------------------------------------------- 1 | package net.emilla.lang.grammar.impl; 2 | 3 | import net.emilla.lang.grammar.ListPhrase; 4 | 5 | import java.util.regex.Pattern; 6 | 7 | public final class ListPhraseEN_US implements ListPhrase { 8 | 9 | private static final String 10 | CONJUNCTION = "(?i)(?<=[^\\s,])\\s+(and|&)\\s+(?=[^\\s,])", 11 | COORDINATION = "(?<=[^\\s,]),[\\s,]*(?=[^\\s,])", 12 | SERIAL = "(?i)(?<=\\S),[\\s,]*(and|&)\\s+(?=[^\\s,])"; 13 | 14 | private final String[] mItems; 15 | 16 | public ListPhraseEN_US(String phrase) { 17 | // todo: this may incorrectly destroy tokens like "red, and blue" -> "red" "blue" instead of 18 | // "red" "and blue". the approach doesn't permit various list interpretations such as 19 | // "items with commas", but it will suffice for now. 20 | // don't put commas in your contact names u jackal :P 21 | 22 | var coordination = Pattern.compile(COORDINATION); 23 | if (coordination.matcher(phrase).find()) { 24 | mItems = phrase.split(SERIAL + "|" + COORDINATION); 25 | return; 26 | } 27 | 28 | var conjunction = Pattern.compile(CONJUNCTION); 29 | if (conjunction.matcher(phrase).find()) { 30 | mItems = conjunction.split(phrase); 31 | return; 32 | } 33 | 34 | mItems = new String[]{phrase}; 35 | } 36 | 37 | @Override 38 | public String[] items() { 39 | return mItems; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/lang/measure/CelsiusConversion.kt: -------------------------------------------------------------------------------- 1 | package net.emilla.lang.measure 2 | 3 | data class CelsiusConversion(@JvmField val degrees: Double, @JvmField val fromKelvin: Boolean) { 4 | 5 | fun convert() = if (fromKelvin) degrees - 273.15 else (degrees - 32.0) * 5.0 / 9.0 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/lang/measure/FahrenheitConversion.kt: -------------------------------------------------------------------------------- 1 | package net.emilla.lang.measure 2 | 3 | data class FahrenheitConversion(@JvmField val degrees: Double, @JvmField val fromKelvin: Boolean) { 4 | 5 | fun convert(): Double = if (fromKelvin) degrees - 459.67 else degrees * 9.0 / 5.0 + 32.0 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/lang/measure/impl/CelsiusConversionEN_US.kt: -------------------------------------------------------------------------------- 1 | package net.emilla.lang.measure.impl 2 | 3 | import androidx.annotation.StringRes 4 | import net.emilla.R 5 | import net.emilla.exception.EmillaException 6 | import net.emilla.lang.LatinToken.Letter 7 | import net.emilla.lang.LatinToken.Word 8 | import net.emilla.lang.LatinTokens 9 | import net.emilla.lang.measure.CelsiusConversion 10 | 11 | object CelsiusConversionEN_US { 12 | 13 | @JvmStatic 14 | fun instance(s: String, @StringRes errorTitle: Int): CelsiusConversion { try { 15 | val tokens = LatinTokens(s) 16 | 17 | val degrees = tokens.nextNumber() 18 | if (tokens.finished()) return CelsiusConversion(degrees, false) 19 | 20 | tokens.skipFirst(arrayOf( 21 | Letter(false, '°', false), 22 | // todo: degree sign technically isn't applicable to Kelvin 23 | Word(true, "degrees", true), 24 | )) 25 | if (tokens.finished()) return CelsiusConversion(degrees, false) 26 | 27 | val token: String = tokens.nextOf(arrayOf( 28 | Word(true, "fahrenheit", true), 29 | Letter(false, 'f', true), 30 | Word(true, "kelvin", true), 31 | Letter(false, 'k', true), 32 | )) 33 | 34 | tokens.requireFinished() 35 | 36 | val isKelvin = when (token.lowercase()) { 37 | "f", "fahrenheit" -> false 38 | "k", "kelvin" -> true 39 | else -> throw IllegalArgumentException() 40 | } 41 | 42 | return CelsiusConversion(degrees, isKelvin) 43 | } catch (_: IllegalStateException) { 44 | throw EmillaException(errorTitle, R.string.error_bad_temperature) 45 | }} 46 | } -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/lang/measure/impl/FahrenheitConversionEN_US.kt: -------------------------------------------------------------------------------- 1 | package net.emilla.lang.measure.impl 2 | 3 | import androidx.annotation.StringRes 4 | import net.emilla.R 5 | import net.emilla.exception.EmillaException 6 | import net.emilla.lang.LatinToken.Letter 7 | import net.emilla.lang.LatinToken.Word 8 | import net.emilla.lang.LatinTokens 9 | import net.emilla.lang.measure.FahrenheitConversion 10 | 11 | object FahrenheitConversionEN_US { 12 | 13 | @JvmStatic 14 | fun instance(s: String, @StringRes errorTitle: Int): FahrenheitConversion { try { 15 | val tokens = LatinTokens(s) 16 | 17 | val degrees = tokens.nextNumber() 18 | if (tokens.finished()) return FahrenheitConversion(degrees, false) 19 | 20 | tokens.skipFirst(arrayOf( 21 | Letter(false, '°', false), 22 | // todo: degree sign technically isn't applicable to Kelvin 23 | Word(true, "degrees", true), 24 | )) 25 | if (tokens.finished()) return FahrenheitConversion(degrees, false) 26 | 27 | val token: String = tokens.nextOf(arrayOf( 28 | Word(true, "celsius", true), 29 | Letter(false, 'c', true), 30 | Word(true, "kelvin", true), 31 | Letter(false, 'k', true), 32 | )) 33 | 34 | tokens.requireFinished() 35 | 36 | val isKelvin = when (token.lowercase()) { 37 | "c", "celsius" -> false 38 | "k", "kelvin" -> true 39 | else -> throw IllegalArgumentException() 40 | } 41 | 42 | return FahrenheitConversion(degrees, isKelvin) 43 | } catch (_: IllegalStateException) { 44 | throw EmillaException(errorTitle, R.string.error_bad_temperature) 45 | }} 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/lang/phrase/Dice.java: -------------------------------------------------------------------------------- 1 | package net.emilla.lang.phrase; 2 | 3 | import java.util.Objects; 4 | import java.util.Random; 5 | 6 | public final class Dice implements Comparable { 7 | 8 | private int mCount; 9 | public final int faces; 10 | 11 | public Dice(int count, int faces) { 12 | mCount = count; 13 | this.faces = faces; 14 | } 15 | 16 | public void add(int count) { 17 | mCount += count; 18 | } 19 | 20 | public int count() { 21 | return mCount; 22 | } 23 | 24 | public int roll(Random rand) { 25 | if (faces == 1) return mCount; 26 | 27 | int result = 0; 28 | if (mCount >= 0) { 29 | for (int i = 0; i < mCount; ++i) { 30 | result += rand.nextInt(faces) + 1; 31 | } 32 | } else { 33 | for (int i = 0; i > mCount; --i) { 34 | result -= rand.nextInt(faces) + 1; 35 | } 36 | } 37 | 38 | return result; 39 | } 40 | 41 | @Override 42 | public int compareTo(Dice that) { 43 | return faces - that.faces; 44 | } 45 | 46 | @Override 47 | public boolean equals(Object that) { 48 | return this == that 49 | || that instanceof Dice dice 50 | && faces == dice.faces; 51 | } 52 | 53 | @Override 54 | public int hashCode() { 55 | return Objects.hash(faces); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/lang/phrase/Dices.java: -------------------------------------------------------------------------------- 1 | package net.emilla.lang.phrase; 2 | 3 | import java.util.Random; 4 | 5 | public final class Dices { 6 | 7 | private final Iterable mDices; 8 | 9 | public Dices(Iterable dices) { 10 | mDices = dices; 11 | } 12 | 13 | public int roll(Random rand) { 14 | int result = 0; 15 | for (Dice dice : mDices) result += dice.roll(rand); 16 | return result; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/lang/phrase/RandRange.java: -------------------------------------------------------------------------------- 1 | package net.emilla.lang.phrase; 2 | 3 | import static java.lang.Math.min; 4 | 5 | import androidx.annotation.StringRes; 6 | 7 | import net.emilla.R; 8 | import net.emilla.exception.EmillaException; 9 | 10 | public final class RandRange { 11 | 12 | public final int inclusStart; 13 | public final int exclusEnd; 14 | 15 | public RandRange(int inclusEnd, @StringRes int errorTitle) { 16 | this(min(inclusEnd, 1), inclusEnd <= 0 ? 0 : inclusEnd + 1, errorTitle); 17 | } 18 | 19 | public RandRange(int inclusStart, int exclusEnd, @StringRes int errorTitle) { 20 | if (inclusStart >= exclusEnd) { 21 | throw new EmillaException(errorTitle, R.string.error_invalid_number_range); 22 | } 23 | 24 | this.inclusStart = inclusStart; 25 | this.exclusEnd = exclusEnd; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/math/BinaryOperator.kt: -------------------------------------------------------------------------------- 1 | package net.emilla.math 2 | 3 | import net.emilla.math.CalcToken.InfixToken 4 | import kotlin.math.pow 5 | 6 | internal enum class BinaryOperator( 7 | @JvmField val precedence: Int, 8 | @JvmField val rightAssociative: Boolean 9 | ) : InfixToken { 10 | PLUS(1, false) { 11 | override fun Double.apply(n: Double) = this + n 12 | }, 13 | MINUS(1, false) { 14 | override fun Double.apply(n: Double) = this - n 15 | }, 16 | TIMES(2, false) { 17 | override fun Double.apply(n: Double) = this * n 18 | }, 19 | DIV(2, false) { 20 | override fun Double.apply(n: Double) = this / n 21 | }, 22 | POW(3, true) { 23 | override fun Double.apply(n: Double) = pow(n) 24 | }; 25 | 26 | abstract fun Double.apply(n: Double): Double 27 | 28 | companion object { 29 | @JvmField 30 | val LPAREN: BinaryOperator? = null 31 | 32 | @JvmStatic 33 | fun of(token: Char) = when (token) { 34 | // todo: natural language like "add", "to the power of", .. 35 | '+' -> PLUS 36 | '-' -> MINUS 37 | '*' -> TIMES 38 | '/' -> DIV 39 | '^' -> POW 40 | else -> throw IllegalArgumentException() 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/math/BitwiseSign.kt: -------------------------------------------------------------------------------- 1 | package net.emilla.math 2 | 3 | import net.emilla.math.CalcToken.BitwiseToken 4 | 5 | internal enum class BitwiseSign(@JvmField val postfix: Boolean) : BitwiseToken { 6 | POSITIVE(false) { 7 | override fun Long.apply() = this 8 | }, 9 | NEGATIVE(false) { 10 | override fun Long.apply() = -this 11 | }, 12 | NOT(false) { 13 | override fun Long.apply() = inv() 14 | }, 15 | FACTORIAL(true) { 16 | override fun Long.apply() = factorial() 17 | }; 18 | 19 | abstract fun Long.apply(): Long 20 | 21 | companion object { 22 | @JvmField 23 | val LPAREN: BitwiseSign? = null 24 | 25 | @JvmStatic 26 | fun of(token: Char) = when (token) { 27 | // todo: natural language like "positive", "factorial", .. 28 | '+' -> POSITIVE 29 | '-' -> NEGATIVE 30 | '~' -> NOT 31 | '!' -> FACTORIAL 32 | else -> throw IllegalArgumentException() 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/math/CalcToken.kt: -------------------------------------------------------------------------------- 1 | package net.emilla.math 2 | 3 | internal sealed interface CalcToken { 4 | sealed interface InfixToken : CalcToken 5 | sealed interface BitwiseToken : CalcToken 6 | 7 | object LParen : InfixToken, BitwiseToken 8 | object RParen : InfixToken, BitwiseToken 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/math/FloatingPointNumber.kt: -------------------------------------------------------------------------------- 1 | package net.emilla.math 2 | 3 | import androidx.annotation.StringRes 4 | import net.emilla.math.CalcToken.InfixToken 5 | 6 | internal class FloatingPointNumber(num: String, @StringRes errorTitle: Int) : InfixToken { 7 | @JvmField 8 | val value: Double = tryParseDouble(num, errorTitle) 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/math/IntegerNumber.kt: -------------------------------------------------------------------------------- 1 | package net.emilla.math 2 | 3 | import androidx.annotation.StringRes 4 | import net.emilla.math.CalcToken.BitwiseToken 5 | 6 | internal class IntegerNumber(num: String, @StringRes errorTitle: Int) : BitwiseToken { 7 | @JvmField 8 | val value: Long = tryParseLong(num, errorTitle) 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/math/Maths.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("Maths") 2 | 3 | package net.emilla.math 4 | 5 | import androidx.annotation.StringRes 6 | import net.emilla.R 7 | import net.emilla.exception.EmillaException 8 | import java.text.DecimalFormat 9 | 10 | // todo: configurable sig digs. 11 | fun prettyNumber(n: Double): String = DecimalFormat("#.######").format(n) 12 | 13 | fun tryParseLong(num: String, @StringRes errorTitle: Int) = try { 14 | num.toLong() 15 | } catch (_: NumberFormatException) { 16 | throw malformedExpression(errorTitle) 17 | } 18 | 19 | fun tryParseDouble(num: String, @StringRes errorTitle: Int) = try { 20 | num.toDouble() 21 | } catch (_: NumberFormatException) { 22 | throw malformedExpression(errorTitle) 23 | } 24 | 25 | fun malformedExpression(@StringRes errorTitle: Int): EmillaException { 26 | return EmillaException(errorTitle, R.string.error_calc_malformed_expression) 27 | } 28 | 29 | fun undefined(@StringRes errorTitle: Int): EmillaException { 30 | return EmillaException(errorTitle, R.string.error_calc_undefined) 31 | } 32 | 33 | fun Double.factorial(): Double { 34 | return toLong().factorial().toDouble() 35 | // TODO: this discards the fractional part. Find the proper way to compute factorial of 36 | // fractional numbers 37 | } 38 | 39 | fun Long.factorial(): Long { 40 | if (this < 0L) throw ArithmeticException() 41 | if (this < 2L) return 1L 42 | 43 | var fact = 2L 44 | for (i in 3L..this) fact *= i 45 | 46 | return fact 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/math/UnaryOperator.kt: -------------------------------------------------------------------------------- 1 | package net.emilla.math 2 | 3 | import net.emilla.math.CalcToken.InfixToken 4 | 5 | internal enum class UnaryOperator(@JvmField val postfix: Boolean) : InfixToken { 6 | POSITIVE(false) { 7 | override fun Double.apply() = this 8 | }, 9 | NEGATIVE(false) { 10 | override fun Double.apply() = -this 11 | }, 12 | PERCENT(true) { 13 | override fun Double.apply() = this / 100.0 14 | }, 15 | FACTORIAL(true) { 16 | override fun Double.apply() = factorial() 17 | }; 18 | 19 | abstract fun Double.apply(): Double 20 | 21 | companion object { 22 | @JvmField 23 | val LPAREN: UnaryOperator? = null 24 | 25 | @JvmStatic 26 | fun of(token: Char) = when (token) { 27 | // todo: natural language like "positive", "factorial", .. 28 | '+' -> POSITIVE 29 | '-' -> NEGATIVE 30 | '%' -> PERCENT 31 | '!' -> FACTORIAL 32 | else -> throw IllegalArgumentException() 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/ping/ClassicPinger.java: -------------------------------------------------------------------------------- 1 | package net.emilla.ping; 2 | 3 | import android.Manifest; 4 | import android.app.Notification; 5 | import android.app.NotificationManager; 6 | import android.content.Context; 7 | 8 | import androidx.annotation.RequiresPermission; 9 | 10 | import net.emilla.util.Services; 11 | 12 | /*internal open*/ class ClassicPinger implements Pinger { 13 | 14 | protected final NotificationManager pingManager; 15 | private final Notification mPing; 16 | private final int mSlot; 17 | 18 | /** 19 | * Singleton that decrements for each notification posted using 20 | * {@link PingChannel#SLOT_UNLIMITED} 21 | */ 22 | private static int sSlot = 0; 23 | 24 | public ClassicPinger(Context ctx, Notification ping, PingChannel channel) { 25 | pingManager = Services.notification(ctx); 26 | mPing = ping; 27 | mSlot = channel.slot; 28 | } 29 | 30 | @Override @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS) 31 | public /*open*/ void ping() { 32 | int id = mSlot == PingChannel.SLOT_UNLIMITED ? --sSlot : mSlot; 33 | // this can be used to edit or remove the notification later. 34 | pingManager.notify(id, mPing); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/ping/ModernPinger.java: -------------------------------------------------------------------------------- 1 | package net.emilla.ping; 2 | 3 | import android.Manifest; 4 | import android.app.Notification; 5 | import android.content.Context; 6 | import android.content.res.Resources; 7 | import android.os.Build; 8 | 9 | import androidx.annotation.RequiresApi; 10 | import androidx.annotation.RequiresPermission; 11 | 12 | @RequiresApi(Build.VERSION_CODES.O) 13 | /*internal*/ final class ModernPinger extends ClassicPinger { 14 | 15 | private final PingChannel mChannel; 16 | private final Resources mRes; 17 | 18 | public ModernPinger(Context ctx, Notification ping, PingChannel channel) { 19 | super(ctx, ping, channel); 20 | 21 | mChannel = channel; 22 | mRes = ctx.getResources(); 23 | } 24 | 25 | @Override @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS) 26 | public void ping() { 27 | if (pingManager.getNotificationChannel(mChannel.id) == null) { 28 | pingManager.createNotificationChannel(mChannel.make(mRes)); 29 | // TODO LANG: you need to update the channel name & description when language changes. 30 | // todo: more centralized channel ensurement so there's consistent order in the app 31 | // notifications settings. 32 | } 33 | super.ping(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/ping/PingIntent.java: -------------------------------------------------------------------------------- 1 | package net.emilla.ping; 2 | 3 | import android.app.Notification; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | 7 | import net.emilla.event.PingReceiver; 8 | 9 | public final class PingIntent extends Intent { 10 | 11 | private static final String 12 | EXTRA_PING = "ping", 13 | EXTRA_CHANNEL = "channel"; 14 | 15 | public PingIntent(Intent intent) { 16 | super(intent); 17 | } 18 | 19 | public PingIntent(Context ctx, Notification ping, String channel) { 20 | super(ctx, PingReceiver.class); 21 | 22 | putExtra(EXTRA_PING, ping); 23 | putExtra(EXTRA_CHANNEL, channel); 24 | } 25 | 26 | public Notification ping() { 27 | return getParcelableExtra(EXTRA_PING); 28 | } 29 | 30 | public PingChannel channel() { 31 | return PingChannel.of(getStringExtra(EXTRA_CHANNEL)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/ping/Pinger.java: -------------------------------------------------------------------------------- 1 | package net.emilla.ping; 2 | 3 | import android.Manifest; 4 | import android.app.Notification; 5 | import android.content.Context; 6 | import android.os.Build; 7 | 8 | import androidx.annotation.RequiresPermission; 9 | 10 | @FunctionalInterface 11 | public interface Pinger { 12 | 13 | @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS) 14 | void ping(); 15 | 16 | static Pinger of(Context ctx, PingIntent intent) { 17 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 18 | return new ModernPinger(ctx, intent.ping(), intent.channel()); 19 | } return new ClassicPinger(ctx, intent.ping(), intent.channel()); 20 | } 21 | 22 | static Pinger of(Context ctx, Notification ping, PingChannel channel) { 23 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 24 | return new ModernPinger(ctx, ping, channel); 25 | } return new ClassicPinger(ctx, ping, channel); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/run/AppGift.kt: -------------------------------------------------------------------------------- 1 | package net.emilla.run 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import net.emilla.R 6 | import net.emilla.activity.DummyActivity 7 | import net.emilla.app.Apps 8 | import net.emilla.exception.EmillaException 9 | 10 | class AppGift(private val activity: Activity, private val intent: Intent) : Gift { 11 | 12 | override fun run() { 13 | if (intent.resolveActivity(activity.packageManager) != null) { 14 | activity.finishAndRemoveTask() 15 | val dummy: Intent = Apps.meTask(activity, DummyActivity::class.java) 16 | .putExtra(Intent.EXTRA_INTENT, intent) 17 | .addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) 18 | activity.startActivity(dummy) 19 | } else throw EmillaException(R.string.error, R.string.error_no_app) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/run/AppSuccess.java: -------------------------------------------------------------------------------- 1 | package net.emilla.run; 2 | 3 | import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 4 | 5 | import android.app.Activity; 6 | import android.content.ActivityNotFoundException; 7 | import android.content.Intent; 8 | 9 | import net.emilla.R; 10 | import net.emilla.exception.EmillaException; 11 | 12 | public final class AppSuccess implements Success { 13 | 14 | private final Activity mActivity; 15 | private final Intent mIntent; 16 | 17 | public AppSuccess(Activity act, Intent intent) { 18 | mActivity = act; 19 | mIntent = intent.addFlags(FLAG_ACTIVITY_NEW_TASK); 20 | } 21 | 22 | @Override 23 | public void run() { 24 | var pm = mActivity.getPackageManager(); 25 | if (mIntent.resolveActivity(pm) != null) { 26 | mActivity.finishAndRemoveTask(); 27 | mActivity.startActivity(mIntent); 28 | } else try { 29 | mActivity.startActivity(mIntent); 30 | mActivity.finishAndRemoveTask(); 31 | } catch (ActivityNotFoundException e) { 32 | throw new EmillaException(R.string.error, R.string.error_no_app); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/run/BroadcastGift.java: -------------------------------------------------------------------------------- 1 | package net.emilla.run; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | 6 | public final class BroadcastGift implements Gift { 7 | 8 | private final Context mContext; 9 | private final Intent mIntent; 10 | 11 | public BroadcastGift(Context ctx, Intent intent) { 12 | mContext = ctx; 13 | mIntent = intent; 14 | } 15 | 16 | @Override 17 | public void run() { 18 | mContext.sendBroadcast(mIntent); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/run/CommandRun.java: -------------------------------------------------------------------------------- 1 | package net.emilla.run; 2 | 3 | @FunctionalInterface 4 | public interface CommandRun extends Runnable {} 5 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/run/CopyGift.java: -------------------------------------------------------------------------------- 1 | package net.emilla.run; 2 | 3 | import android.content.ClipData; 4 | import android.content.ClipboardManager; 5 | 6 | import net.emilla.activity.AssistActivity; 7 | import net.emilla.util.Services; 8 | 9 | public final class CopyGift implements Gift { 10 | 11 | private final AssistActivity mActivity; 12 | private final String mText; 13 | 14 | public CopyGift(AssistActivity act, String text) { 15 | mActivity = act; 16 | mText = text; 17 | } 18 | 19 | @Override 20 | public void run() { 21 | ClipboardManager clipMgr = Services.clipboard(mActivity); 22 | clipMgr.setPrimaryClip(ClipData.newPlainText(null, mText)); 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/run/DialogRun.java: -------------------------------------------------------------------------------- 1 | package net.emilla.run; 2 | 3 | import androidx.appcompat.app.AlertDialog; 4 | 5 | import net.emilla.activity.AssistActivity; 6 | 7 | public /*open*/ class DialogRun implements Gift, Offering, Failure { 8 | 9 | private final AssistActivity mActivity; 10 | private final AlertDialog mDialog; 11 | 12 | public DialogRun(AssistActivity act, AlertDialog.Builder builder) { 13 | this(act, builder.create()); 14 | } 15 | 16 | public DialogRun(AssistActivity act, AlertDialog dialog) { 17 | mActivity = act; 18 | dialog.setOnCancelListener(dlg -> { 19 | mActivity.onCloseDialog(); 20 | mActivity.resume(); 21 | }); 22 | // Todo: don't require this 23 | mDialog = dialog; 24 | } 25 | 26 | @Override 27 | public final void run() { 28 | mActivity.prepareForDialog(); 29 | mDialog.show(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/run/Failure.java: -------------------------------------------------------------------------------- 1 | package net.emilla.run; 2 | 3 | @FunctionalInterface 4 | public interface Failure extends CommandRun {} 5 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/run/Gift.java: -------------------------------------------------------------------------------- 1 | package net.emilla.run; 2 | 3 | @FunctionalInterface 4 | public interface Gift extends CommandRun {} 5 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/run/MessageFailure.java: -------------------------------------------------------------------------------- 1 | package net.emilla.run; 2 | 3 | import androidx.annotation.StringRes; 4 | 5 | import net.emilla.R; 6 | import net.emilla.activity.AssistActivity; 7 | import net.emilla.exception.EmillaException; 8 | import net.emilla.util.Dialogs; 9 | 10 | public final class MessageFailure extends DialogRun { 11 | 12 | public MessageFailure(AssistActivity act, EmillaException e) { 13 | this(act, e.title, e.message); 14 | } 15 | 16 | public MessageFailure(AssistActivity act, @StringRes int title, @StringRes int msg) { 17 | super(act, Dialogs.message(act, title, msg) 18 | .setNeutralButton(R.string.leave, (dlg, which) -> act.cancel())); 19 | } 20 | 21 | public MessageFailure(AssistActivity act, CharSequence title, @StringRes int msg) { 22 | super(act, Dialogs.message(act, title, msg) 23 | .setNeutralButton(R.string.leave, (dlg, which) -> act.cancel())); 24 | } 25 | 26 | public MessageFailure(AssistActivity act, CharSequence title, CharSequence msg) { 27 | super(act, Dialogs.message(act, title, msg) 28 | .setNeutralButton(R.string.leave, (dlg, which) -> act.cancel())); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/run/MessageGift.java: -------------------------------------------------------------------------------- 1 | package net.emilla.run; 2 | 3 | import androidx.annotation.StringRes; 4 | 5 | import net.emilla.activity.AssistActivity; 6 | import net.emilla.util.Dialogs; 7 | 8 | public final class MessageGift extends DialogRun { 9 | 10 | public MessageGift(AssistActivity act, @StringRes int title, @StringRes int msg) { 11 | super(act, Dialogs.message(act, title, msg)); 12 | } 13 | 14 | public MessageGift(AssistActivity act, @StringRes int title, CharSequence msg) { 15 | super(act, Dialogs.message(act, title, msg)); 16 | } 17 | 18 | public MessageGift(AssistActivity act, CharSequence title, @StringRes int msg) { 19 | super(act, Dialogs.message(act, title, msg)); 20 | } 21 | 22 | public MessageGift(AssistActivity act, CharSequence title, CharSequence msg) { 23 | super(act, Dialogs.message(act, title, msg)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/run/Offering.java: -------------------------------------------------------------------------------- 1 | package net.emilla.run; 2 | 3 | @FunctionalInterface 4 | public interface Offering extends CommandRun {} 5 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/run/PermissionFailure.java: -------------------------------------------------------------------------------- 1 | package net.emilla.run; 2 | 3 | import android.content.Intent; 4 | import android.content.pm.PackageManager; 5 | 6 | import androidx.annotation.StringRes; 7 | import androidx.appcompat.app.AlertDialog; 8 | 9 | import net.emilla.R; 10 | import net.emilla.activity.AssistActivity; 11 | import net.emilla.app.Apps; 12 | import net.emilla.util.Dialogs; 13 | 14 | public final class PermissionFailure extends DialogRun { 15 | 16 | private static AlertDialog.Builder dialog(AssistActivity act, @StringRes int permissionName) { 17 | // todo: this often results in an activity restart, which messes with the resume chime and 18 | // probably other elements of state. handle accordingly. 19 | Intent appInfo = Apps.infoTask(); 20 | PackageManager pm = act.getPackageManager(); 21 | if (appInfo.resolveActivity(pm) != null) { 22 | return Dialogs.dual(act, permissionName, R.string.dlg_msg_perm_denial, R.string.app_info, 23 | (dlg, which) -> act.startActivity(appInfo)); 24 | } 25 | 26 | return Dialogs.message(act, permissionName, R.string.dlg_msg_perm_denial); 27 | // this should pretty much never happen. 28 | } 29 | 30 | public PermissionFailure(AssistActivity act, @StringRes int permissionName) { 31 | super(act, dialog(act, permissionName)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/run/PermissionOffering.java: -------------------------------------------------------------------------------- 1 | package net.emilla.run; 2 | 3 | import android.os.Build; 4 | 5 | import androidx.annotation.Nullable; 6 | import androidx.annotation.RequiresApi; 7 | 8 | import net.emilla.activity.AssistActivity; 9 | 10 | /** 11 | *

12 | * Presents the user with a system permission request.

13 | *

14 | * This can only be used when 15 | * {@link android.app.Activity#shouldShowRequestPermissionRationale(String)} is true for the 16 | * permission(s) being requested.

17 | */ 18 | @RequiresApi(api = Build.VERSION_CODES.M) 19 | public final class PermissionOffering implements Offering { 20 | 21 | private final AssistActivity mActivity; 22 | private final String[] mPermissions; 23 | @Nullable 24 | private final Runnable mOnGrant; 25 | 26 | public PermissionOffering(AssistActivity act, String permission, @Nullable Runnable onGrant) { 27 | this(act, new String[]{permission}, onGrant); 28 | } 29 | 30 | public PermissionOffering(AssistActivity act, String[] permissions, @Nullable Runnable onGrant) { 31 | mActivity = act; 32 | mPermissions = permissions; 33 | mOnGrant = onGrant; 34 | } 35 | 36 | @Override 37 | public void run() { 38 | mActivity.offerPermissions(mPermissions, mOnGrant); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/run/PingGift.java: -------------------------------------------------------------------------------- 1 | package net.emilla.run; 2 | 3 | import android.Manifest; 4 | import android.app.Notification; 5 | import android.content.Context; 6 | 7 | import androidx.annotation.RequiresPermission; 8 | 9 | import net.emilla.ping.PingChannel; 10 | import net.emilla.ping.Pinger; 11 | 12 | public final class PingGift implements Gift { 13 | 14 | private final Context mContext; 15 | private final Notification mPing; 16 | private final PingChannel mChannel; 17 | 18 | @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS) 19 | public PingGift(Context ctx, Notification ping, PingChannel channel) { 20 | mContext = ctx; 21 | mPing = ping; 22 | mChannel = channel; 23 | } 24 | 25 | @Override @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS) 26 | public void run() { 27 | Pinger.of(mContext, mPing, mChannel).ping(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/run/Success.java: -------------------------------------------------------------------------------- 1 | package net.emilla.run; 2 | 3 | @FunctionalInterface 4 | public interface Success extends CommandRun {} 5 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/run/TimePickerOffering.java: -------------------------------------------------------------------------------- 1 | package net.emilla.run; 2 | 3 | import android.app.TimePickerDialog; 4 | import android.app.TimePickerDialog.OnTimeSetListener; 5 | import android.text.format.DateFormat; 6 | 7 | import net.emilla.activity.AssistActivity; 8 | 9 | public final class TimePickerOffering implements Offering { 10 | 11 | private final AssistActivity mActivity; 12 | private final TimePickerDialog mDialog; 13 | 14 | public TimePickerOffering(AssistActivity act, OnTimeSetListener timeSet) { 15 | mActivity = act; 16 | mDialog = new TimePickerDialog(act, 0, timeSet, 12, 0, DateFormat.is24HourFormat(act)); 17 | // TODO: this isn't respecting the LineageOS system 24-hour setting. 18 | // should there be an option for default time to be noon vs. the current time? noon seems 19 | // much more reasonable in all cases tbh. infinitely more predictable—who the heck wants to 20 | // set a timer for right now?! 21 | mDialog.setOnCancelListener(dlg -> { 22 | mActivity.onCloseDialog(); 23 | mActivity.resume(); 24 | }); 25 | // Todo: don't require this. 26 | } 27 | 28 | @Override 29 | public void run() { 30 | mActivity.prepareForDialog(); 31 | mDialog.show(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/run/ToastGift.java: -------------------------------------------------------------------------------- 1 | package net.emilla.run; 2 | 3 | import net.emilla.activity.AssistActivity; 4 | 5 | public final class ToastGift implements Gift { 6 | 7 | private final AssistActivity mActivity; 8 | private final CharSequence mMessage; 9 | private final boolean mLong; 10 | 11 | public ToastGift(AssistActivity activity, CharSequence msg, boolean longToast) { 12 | mActivity = activity; 13 | mMessage = msg; 14 | mLong = longToast; 15 | } 16 | 17 | @Override 18 | public void run() { 19 | mActivity.toast(mMessage, mLong); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/system/BackgroundServiceUtil.java: -------------------------------------------------------------------------------- 1 | package net.emilla.system; 2 | 3 | public final class BackgroundServiceUtil { 4 | 5 | // private enum State { 6 | // A11Y_WAKE_WORD, A11Y, WAKE_WORD, FAST_LOAD, OFF; 7 | // 8 | // public static State of() { 9 | // ; 10 | // } 11 | // } 12 | // 13 | // static { 14 | // var state = State.of(); 15 | // switch (state) { 16 | // case A11Y_WAKE_WORD -> { 17 | // ensureA11yService(); 18 | // ensureWakeWordService(); 19 | // } 20 | // case A11Y -> { 21 | // ensureA11yService(); 22 | // } 23 | // case WAKE_WORD -> { 24 | // ensureWakeWordService(); 25 | // } 26 | // case FAST_LOAD -> { 27 | // ensureFastLoadService(); 28 | // }} 29 | // } 30 | 31 | private BackgroundServiceUtil() {} 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/system/EmillaA11yService.java: -------------------------------------------------------------------------------- 1 | package net.emilla.system; 2 | 3 | import static android.content.Intent.ACTION_VOICE_COMMAND; 4 | import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 5 | 6 | import android.accessibilityservice.AccessibilityButtonController; 7 | import android.accessibilityservice.AccessibilityButtonController.AccessibilityButtonCallback; 8 | import android.accessibilityservice.AccessibilityService; 9 | import android.content.Intent; 10 | import android.os.Build; 11 | import android.view.accessibility.AccessibilityEvent; 12 | 13 | import androidx.annotation.RequiresApi; 14 | 15 | public final class EmillaA11yService extends AccessibilityService { 16 | 17 | // TODO: google assistant (maybe?) changes the accessibility menu icon for "assistant," so we 18 | // should also do this. I wonder if you can add items to that menu.. 19 | 20 | @Override 21 | public void onAccessibilityEvent(AccessibilityEvent event) {} 22 | 23 | @Override 24 | public void onInterrupt() {} 25 | 26 | @Override @RequiresApi(api = Build.VERSION_CODES.O) 27 | public void onCreate() { 28 | var controller = getAccessibilityButtonController(); 29 | var callback = new AssistButtonCallback(); 30 | controller.registerAccessibilityButtonCallback(callback); 31 | } 32 | 33 | @RequiresApi(api = Build.VERSION_CODES.O) 34 | private /*inner*/ class AssistButtonCallback extends AccessibilityButtonCallback { 35 | 36 | @Override 37 | public void onClicked(AccessibilityButtonController controller) { 38 | startActivity(new Intent(ACTION_VOICE_COMMAND).addFlags(FLAG_ACTIVITY_NEW_TASK)); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/system/EmillaFileProvider.java: -------------------------------------------------------------------------------- 1 | package net.emilla.system; 2 | 3 | import androidx.core.content.FileProvider; 4 | 5 | import net.emilla.R; 6 | 7 | public final class EmillaFileProvider extends FileProvider { 8 | 9 | public EmillaFileProvider() { 10 | super(R.xml.paths); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/util/CalendarDetails.java: -------------------------------------------------------------------------------- 1 | package net.emilla.util; 2 | 3 | import androidx.annotation.StringRes; 4 | 5 | import net.emilla.R; 6 | import net.emilla.exception.EmillaException; 7 | 8 | public final class CalendarDetails { 9 | 10 | public static int parseAvailability(String s, @StringRes int errorTitle) { 11 | throw new EmillaException(errorTitle, R.string.error_unfinished_feature); 12 | } 13 | 14 | public static int parseVisibility(String s, @StringRes int errorTitle) { 15 | throw new EmillaException(errorTitle, R.string.error_unfinished_feature); 16 | } 17 | 18 | private CalendarDetails() {} 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/util/Chars.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("Chars") 2 | 3 | package net.emilla.util 4 | 5 | fun Char.notSpace() = !isWhitespace() 6 | 7 | fun Char.isNonLineSpace() = when (this) { 8 | '\n', '\r' -> false 9 | else -> isWhitespace() 10 | } 11 | 12 | @JvmName("differentLetters") 13 | fun Char.differentLetter(c: Char): Boolean { 14 | return compareToIgnoreCase(c) != 0 15 | } 16 | 17 | @JvmName("compareIgnoreCase") 18 | fun Char.compareToIgnoreCase(c: Char): Int { 19 | if (this != c && uppercaseChar() != c.uppercaseChar()) { 20 | val a = lowercaseChar() 21 | val b = c.lowercaseChar() 22 | if (a != b) return a - b 23 | } 24 | 25 | return 0 26 | } 27 | 28 | fun Char.isSignOrDigit() = isDigit() || isSign() 29 | fun Char.isSignOrNumberChar() = isNumberChar() || isSign() 30 | 31 | fun Char.isDigit() = this in '0'..'9' 32 | 33 | fun Char.isNumberChar() = this == '.' || this in '0'..'9' 34 | 35 | fun Char.isSign() = when (this) { 36 | '+', '-' -> true 37 | else -> false 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/util/Features.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("Features") 2 | 3 | package net.emilla.util 4 | 5 | import android.content.pm.PackageManager 6 | import android.os.Build 7 | 8 | @JvmName("camera") 9 | fun PackageManager.hasCameraFeature(): Boolean { 10 | return hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) 11 | } 12 | 13 | @JvmName("phone") 14 | fun PackageManager.hasPhoneFeature(): Boolean { 15 | return hasSystemFeature(PackageManager.FEATURE_TELEPHONY) 16 | } 17 | 18 | @JvmName("sms") 19 | fun PackageManager.hasSmsFeature(): Boolean { 20 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 21 | hasSystemFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING) 22 | } else { 23 | hasSystemFeature(PackageManager.FEATURE_TELEPHONY) 24 | } 25 | } 26 | 27 | @JvmName("torch") 28 | fun hasTorchFeature(pm: PackageManager): Boolean { 29 | // todo: it'd be a good idea to test this on a minSdk device. 30 | return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH) 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/util/IndexWindow.java: -------------------------------------------------------------------------------- 1 | package net.emilla.util; 2 | 3 | public final class IndexWindow { 4 | 5 | public final int start; 6 | public final int last; 7 | public final int end; 8 | 9 | public IndexWindow(int start, int last) { 10 | this.start = start; 11 | this.last = last; 12 | this.end = last + 1; 13 | } 14 | 15 | public int size() { 16 | return end - start; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/util/MediaControl.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("MediaControl") 2 | 3 | package net.emilla.util 4 | 5 | import android.media.AudioManager 6 | import android.view.KeyEvent 7 | 8 | fun AudioManager.sendPlayEvent() = sendButtonEvent(KeyEvent.KEYCODE_MEDIA_PLAY) 9 | fun AudioManager.sendPauseEvent() = sendButtonEvent(KeyEvent.KEYCODE_MEDIA_PAUSE) 10 | fun AudioManager.sendPlayPauseEvent() = sendButtonEvent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) 11 | 12 | private fun AudioManager.sendButtonEvent(keyCode: Int) { 13 | dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, keyCode)) 14 | dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_UP, keyCode)) 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/util/Services.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("Services") 2 | 3 | package net.emilla.util 4 | 5 | import android.app.AlarmManager 6 | import android.app.NotificationManager 7 | import android.content.ClipboardManager 8 | import android.content.Context 9 | import android.hardware.camera2.CameraManager 10 | import android.media.AudioManager 11 | import android.view.accessibility.AccessibilityManager 12 | 13 | @JvmName("accessibility") 14 | fun Context.accessibilityService(): AccessibilityManager { 15 | return getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager 16 | } 17 | 18 | @JvmName("alarm") 19 | fun Context.alarmService(): AlarmManager { 20 | return getSystemService(Context.ALARM_SERVICE) as AlarmManager 21 | } 22 | 23 | @JvmName("audio") 24 | fun Context.audioService(): AudioManager { 25 | return getSystemService(Context.AUDIO_SERVICE) as AudioManager 26 | } 27 | 28 | @JvmName("camera") 29 | fun Context.cameraService(): CameraManager { 30 | return getSystemService(Context.CAMERA_SERVICE) as CameraManager 31 | } 32 | 33 | @JvmName("clipboard") 34 | fun Context.clipboardService(): ClipboardManager { 35 | return getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager 36 | } 37 | 38 | @JvmName("notification") 39 | fun Context.notificationService(): NotificationManager { 40 | return getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/util/Strings.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("Strings") 2 | 3 | package net.emilla.util 4 | 5 | fun String.trimLeading(): String { 6 | val index = indexOfNonSpace() 7 | return if (index > 0) substring(index) else this 8 | } 9 | 10 | fun String.indexOfNonSpace(): Int { 11 | if (isEmpty() || !this[0].isWhitespace()) return 0 12 | 13 | var index = 0 14 | do if (++index == length) return length 15 | while (this[index].isWhitespace()) 16 | 17 | return index 18 | } 19 | 20 | fun CharArray.indexOfNonSpace(): Int { 21 | if (isEmpty() || !this[0].isWhitespace()) return 0 22 | 23 | var index = 0 24 | do if (++index == size) return size 25 | while (this[index].isWhitespace()) 26 | 27 | return index 28 | } 29 | 30 | fun String.containsIgnoreCase(other: String): Boolean { 31 | if (other.isEmpty()) return true 32 | if (isEmpty()) return false 33 | 34 | val first = other[0] 35 | val max = length - other.length 36 | val trgLast = other.length - 1 37 | 38 | var i = 0 39 | while (i <= max) { 40 | // look for first char. 41 | if (this[i].differentLetter(first)) { 42 | do ++i 43 | while (i <= max && this[i].differentLetter(first)) 44 | 45 | if (i > max) return false 46 | } 47 | 48 | // found first character, now look at the rest of target. 49 | ++i 50 | if (regionMatches(i, other, 1, trgLast)) return true 51 | } 52 | 53 | return false 54 | } 55 | 56 | fun Char.repeat(count: Int): String = String(CharArray(count) { this }) 57 | fun CharArray.substring(start: Int = 0) = substring(start, size) 58 | fun CharArray.substring(start: Int = 0, end: Int) = String(copyOfRange(start, end)) 59 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/util/Timeit.java: -------------------------------------------------------------------------------- 1 | package net.emilla.util; 2 | 3 | import android.util.Log; 4 | 5 | public final class Timeit { 6 | 7 | private static final String TAG = Timeit.class.getSimpleName(); 8 | 9 | private static long sPrevTime = 0; 10 | 11 | public static long nanos(String label) { 12 | if (sPrevTime == 0) sPrevTime = System.nanoTime(); 13 | 14 | var s = String.valueOf(System.nanoTime() - sPrevTime); 15 | var sb = new StringBuilder(label).append(": "); 16 | int start = sb.length(); 17 | sb.append(s); 18 | 19 | for (int i = sb.length() - 3; i > start; i -= 3) { 20 | sb.insert(i, ','); 21 | } 22 | sb.append(" nanoseconds"); 23 | 24 | Log.d(TAG, sb.toString()); 25 | 26 | return sPrevTime = System.nanoTime(); 27 | } 28 | 29 | private Timeit() {} 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/util/trie/HashTrieMap.java: -------------------------------------------------------------------------------- 1 | package net.emilla.util.trie; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public final class HashTrieMap> extends TrieMap { 7 | 8 | private static final class HashTrieNode> extends TrieNode { 9 | 10 | @Override 11 | Map> newMap() { 12 | return new HashMap<>(); 13 | } 14 | } 15 | 16 | @Override 17 | TrieNode newNode() { 18 | return new HashTrieNode<>(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/util/trie/SortedTrieMap.java: -------------------------------------------------------------------------------- 1 | package net.emilla.util.trie; 2 | 3 | import androidx.annotation.Nullable; 4 | 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.TreeMap; 8 | 9 | public final class SortedTrieMap, V extends TrieMap.Value> 10 | extends TrieMap { 11 | 12 | private static final class SortedTrieNode, V extends Value> 13 | extends TrieNode { 14 | 15 | @Override 16 | Map> newMap() { 17 | return new TreeMap<>(); 18 | } 19 | } 20 | 21 | @Override 22 | TrieNode newNode() { 23 | return new SortedTrieNode<>(); 24 | } 25 | 26 | /** 27 | * Sorted list of elements that start with a given prefix. 28 | * 29 | * @param prefix phrase to get prefixed values of. 30 | * @return list of elements that start with {@code prefix} in sorted depth-first order, or 31 | * {@code null} if no such values exist. 32 | */ 33 | @Nullable 34 | public List elementsWithPrefix(Phrase prefix) { 35 | TrieNode current = root; 36 | for (K item : prefix) { 37 | TrieNode get = current.children().get(item); 38 | if (get == null) return null; 39 | current = get; 40 | } 41 | return current.values(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/res/color/bg_activatable_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/color/bg_assistant.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/color/bg_text_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_action_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_edit_box.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_edit_box_focused.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_edit_box_unfocused.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_selectable_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_star.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_star_checked.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_star_unchecked.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_alarm.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_app.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_assistant.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_attach.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_bookmark.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_calculate.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_calendar.xml: -------------------------------------------------------------------------------- 1 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_call.xml: -------------------------------------------------------------------------------- 1 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_clock.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_command.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_contact.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_copy.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_cursor_start.xml: -------------------------------------------------------------------------------- 1 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_dial.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_email.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_find.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_help.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_hide_data.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_info.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launch.xml: -------------------------------------------------------------------------------- 1 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_assistant_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_location.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_media.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_message.xml: -------------------------------------------------------------------------------- 1 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_navigate.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_note.xml: -------------------------------------------------------------------------------- 1 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notify.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_pause.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_person.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_play.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_pomodoro.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_random_number.xml: -------------------------------------------------------------------------------- 1 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_roll.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_select_all.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_share.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_show_data.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_sms.xml: -------------------------------------------------------------------------------- 1 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_snippets.xml: -------------------------------------------------------------------------------- 1 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_subject.xml: -------------------------------------------------------------------------------- 1 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_temperature.xml: -------------------------------------------------------------------------------- 1 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_timer.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_toast.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_todo.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_torch.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_uninstall.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_weather.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_web.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/list_divider_emilla.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 19 | 20 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/layout/btn_action.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/field_extra.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_assistant.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 18 | 19 | 27 | 28 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_commands.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/layout/prefrence_widget_switch.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/snippet_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/snippet_item_list.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/menu/bottom_nav_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_assistant.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_assistant.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/app/src/main/res/mipmap-hdpi/ic_launcher_assistant.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_assistant_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/app/src/main/res/mipmap-hdpi/ic_launcher_assistant_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_assistant.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/app/src/main/res/mipmap-mdpi/ic_launcher_assistant.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_assistant_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/app/src/main/res/mipmap-mdpi/ic_launcher_assistant_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_assistant.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/app/src/main/res/mipmap-xhdpi/ic_launcher_assistant.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_assistant_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/app/src/main/res/mipmap-xhdpi/ic_launcher_assistant_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_assistant.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/app/src/main/res/mipmap-xxhdpi/ic_launcher_assistant.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_assistant_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/app/src/main/res/mipmap-xxhdpi/ic_launcher_assistant_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_assistant.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/app/src/main/res/mipmap-xxxhdpi/ic_launcher_assistant.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_assistant_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/app/src/main/res/mipmap-xxxhdpi/ic_launcher_assistant_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/navigation/navigation_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 19 | 20 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/raw/nebula_act.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/app/src/main/res/raw/nebula_act.ogg -------------------------------------------------------------------------------- /app/src/main/res/raw/nebula_exit.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/app/src/main/res/raw/nebula_exit.ogg -------------------------------------------------------------------------------- /app/src/main/res/raw/nebula_fail.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/app/src/main/res/raw/nebula_fail.ogg -------------------------------------------------------------------------------- /app/src/main/res/raw/nebula_pend.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/app/src/main/res/raw/nebula_pend.ogg -------------------------------------------------------------------------------- /app/src/main/res/raw/nebula_resume.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/app/src/main/res/raw/nebula_resume.ogg -------------------------------------------------------------------------------- /app/src/main/res/raw/nebula_start.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/app/src/main/res/raw/nebula_start.ogg -------------------------------------------------------------------------------- /app/src/main/res/raw/nebula_succeed.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/app/src/main/res/raw/nebula_succeed.ogg -------------------------------------------------------------------------------- /app/src/main/res/values-notnight/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 17 | 18 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/values-sw600dp/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | always 4 | -------------------------------------------------------------------------------- /app/src/main/res/values-v31/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @android:color/system_accent1_200 4 | @android:color/system_accent1_400 5 | @android:color/system_accent1_500 6 | @android:color/system_accent1_700 7 | @android:color/system_accent2_200 8 | @android:color/system_accent2_500 9 | @android:color/system_accent2_700 10 | @android:color/system_neutral1_900 11 | @android:color/system_neutral1_800 12 | @android:color/system_neutral1_50 13 | @android:color/system_neutral1_100 14 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FC8BFF 4 | #BD50FF 5 | #9D30FF 6 | #5A00DF 7 | #03DAC5 8 | #008B79 9 | #018786 10 | #1A1B20 11 | #2F3036 12 | #F1F0F7 13 | #E2E2E9 14 | -------------------------------------------------------------------------------- /app/src/main/res/values/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | portrait 4 | 5 | true 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 16dp 3 | 8dp 4 | 52dp 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #BA575F 4 | #796AC0 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/xml/accessibility_service_config.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/xml/shortcuts.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 10 | 11 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/test/java/net/emilla/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package net.emilla; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import org.junit.Test; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see
Testing documentation 11 | */ 12 | public final class ExampleUnitTest { 13 | 14 | @Test 15 | public void addition_isCorrect() { 16 | assertEquals(4, 2 + 2); 17 | } 18 | } -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | plugins { 3 | id("com.android.application") version "8.9.1" apply false 4 | id("org.jetbrains.kotlin.android") version "2.1.10" apply false 5 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Enables namespacing of each library's R class so that its R class includes only the 19 | # resources declared in the library itself and none from the library's dependencies, 20 | # thereby reducing the size of the R class for that library 21 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/36f052a4b0b38e1d79504e7c938860423d09ff24/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Oct 08 12:08:35 MDT 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | } 15 | 16 | rootProject.name = "Emilla" 17 | include(":app") 18 | --------------------------------------------------------------------------------