├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── raw │ │ │ │ ├── nebula_act.ogg │ │ │ │ ├── nebula_exit.ogg │ │ │ │ ├── nebula_fail.ogg │ │ │ │ ├── nebula_pend.ogg │ │ │ │ ├── nebula_resume.ogg │ │ │ │ ├── nebula_start.ogg │ │ │ │ └── nebula_succeed.ogg │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ ├── ic_launcher_assistant.webp │ │ │ │ └── ic_launcher_assistant_round.webp │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ ├── ic_launcher_assistant.webp │ │ │ │ └── ic_launcher_assistant_round.webp │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ ├── ic_launcher_assistant.webp │ │ │ │ └── ic_launcher_assistant_round.webp │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ ├── ic_launcher_assistant.webp │ │ │ │ └── ic_launcher_assistant_round.webp │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ ├── ic_launcher_assistant.webp │ │ │ │ └── ic_launcher_assistant_round.webp │ │ │ ├── values-sw600dp │ │ │ │ └── config.xml │ │ │ ├── drawable │ │ │ │ ├── ic_show_data.xml │ │ │ │ ├── bg_action_button.xml │ │ │ │ ├── bg_selectable_item.xml │ │ │ │ ├── bg_edit_box.xml │ │ │ │ ├── list_divider_emilla.xml │ │ │ │ ├── btn_star.xml │ │ │ │ ├── ic_play.xml │ │ │ │ ├── ic_pause.xml │ │ │ │ ├── ic_hide_data.xml │ │ │ │ ├── ic_bookmark.xml │ │ │ │ ├── ic_uninstall.xml │ │ │ │ ├── ic_info.xml │ │ │ │ ├── bg_edit_box_focused.xml │ │ │ │ ├── bg_edit_box_unfocused.xml │ │ │ │ ├── ic_command.xml │ │ │ │ ├── ic_folder.xml │ │ │ │ ├── ic_subject.xml │ │ │ │ ├── ic_email.xml │ │ │ │ ├── ic_media.xml │ │ │ │ ├── ic_app.xml │ │ │ │ ├── ic_cursor_start.xml │ │ │ │ ├── ic_copy.xml │ │ │ │ ├── ic_toast.xml │ │ │ │ ├── btn_star_checked.xml │ │ │ │ ├── ic_launch.xml │ │ │ │ ├── ic_assistant.xml │ │ │ │ ├── ic_notify.xml │ │ │ │ ├── ic_sms.xml │ │ │ │ ├── ic_message.xml │ │ │ │ ├── ic_torch.xml │ │ │ │ ├── ic_random_number.xml │ │ │ │ ├── ic_todo.xml │ │ │ │ ├── ic_calculate.xml │ │ │ │ ├── ic_temperature.xml │ │ │ │ ├── ic_search.xml │ │ │ │ ├── ic_navigate.xml │ │ │ │ ├── ic_note.xml │ │ │ │ ├── ic_attach.xml │ │ │ │ ├── ic_clock.xml │ │ │ │ ├── ic_alarm.xml │ │ │ │ ├── ic_timer.xml │ │ │ │ ├── btn_star_unchecked.xml │ │ │ │ ├── ic_contact.xml │ │ │ │ ├── ic_find.xml │ │ │ │ ├── ic_help.xml │ │ │ │ ├── ic_calendar.xml │ │ │ │ ├── ic_person.xml │ │ │ │ ├── ic_launcher_assistant_foreground.xml │ │ │ │ ├── ic_location.xml │ │ │ │ ├── ic_select_all.xml │ │ │ │ ├── ic_weather.xml │ │ │ │ ├── ic_call.xml │ │ │ │ ├── ic_pomodoro.xml │ │ │ │ ├── ic_share.xml │ │ │ │ ├── ic_roll.xml │ │ │ │ ├── ic_snippets.xml │ │ │ │ ├── ic_dial.xml │ │ │ │ ├── ic_launcher_foreground.xml │ │ │ │ ├── ic_notifications.xml │ │ │ │ ├── ic_settings.xml │ │ │ │ └── ic_web.xml │ │ │ ├── values │ │ │ │ ├── dimens.xml │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ ├── config.xml │ │ │ │ ├── attrs.xml │ │ │ │ ├── colors.xml │ │ │ │ └── ids.xml │ │ │ ├── color │ │ │ │ ├── bg_assistant.xml │ │ │ │ ├── bg_activatable_item.xml │ │ │ │ └── bg_text_button.xml │ │ │ ├── xml │ │ │ │ ├── backup_rules.xml │ │ │ │ ├── data_extraction_rules.xml │ │ │ │ ├── accessibility_service_config.xml │ │ │ │ ├── shortcuts.xml │ │ │ │ └── command_prefs.xml │ │ │ ├── layout │ │ │ │ ├── fragment_item_list.xml │ │ │ │ ├── list_item.xml │ │ │ │ ├── fragment_commands.xml │ │ │ │ ├── fragment_settings.xml │ │ │ │ ├── prefrence_widget_switch.xml │ │ │ │ ├── btn_action.xml │ │ │ │ └── field_extra.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_assistant.xml │ │ │ ├── menu │ │ │ │ ├── list_file_toolbar.xml │ │ │ │ ├── notes_toolbar.xml │ │ │ │ └── bottom_nav_menu.xml │ │ │ ├── values-v31 │ │ │ │ └── colors.xml │ │ │ ├── navigation │ │ │ │ └── navigation_config.xml │ │ │ └── values-notnight │ │ │ │ └── themes.xml │ │ ├── ic_launcher-playstore.png │ │ └── java │ │ │ └── net │ │ │ └── emilla │ │ │ ├── content │ │ │ ├── receive │ │ │ │ ├── ResultReceiver.java │ │ │ │ ├── ContactReceiver.java │ │ │ │ ├── ContactDataReceiver.java │ │ │ │ ├── AppChoiceReceiver.java │ │ │ │ ├── PhoneReceiver.java │ │ │ │ ├── EmailReceiver.java │ │ │ │ └── ContactCardReceiver.java │ │ │ ├── ResultLaunchers.java │ │ │ └── retrieve │ │ │ │ ├── ContactPhoneRetriever.java │ │ │ │ ├── ContactEmailRetriever.java │ │ │ │ └── FilesRetriever.java │ │ │ ├── lang │ │ │ ├── date │ │ │ │ ├── Meridiem.java │ │ │ │ ├── HourMin.java │ │ │ │ ├── DateTimeSpan.java │ │ │ │ ├── Duration.java │ │ │ │ ├── impl │ │ │ │ │ └── DurationEN_US.java │ │ │ │ └── Weekdays.java │ │ │ ├── grammar │ │ │ │ └── ListPhrase.java │ │ │ ├── measure │ │ │ │ ├── CelsiusConversion.kt │ │ │ │ └── FahrenheitConversion.kt │ │ │ └── phrase │ │ │ │ ├── Dices.java │ │ │ │ ├── RandRange.java │ │ │ │ └── Dice.kt │ │ │ ├── action │ │ │ ├── box │ │ │ │ ├── SnippetAdapter.java │ │ │ │ ├── TriResult.java │ │ │ │ ├── FileSearchAdapter.java │ │ │ │ └── ActionBox.java │ │ │ ├── Gadget.java │ │ │ ├── InstructyGadget.java │ │ │ ├── LabeledQuickAction.java │ │ │ ├── NoAction.java │ │ │ ├── MediaFetcher.java │ │ │ ├── QuickAction.java │ │ │ ├── PlayPause.java │ │ │ └── FileFetcher.java │ │ │ ├── run │ │ │ ├── CommandRun.kt │ │ │ ├── BroadcastGift.java │ │ │ ├── ToastGift.java │ │ │ ├── CopyGift.java │ │ │ ├── DialogRun.java │ │ │ ├── AppGift.kt │ │ │ ├── PingGift.java │ │ │ ├── PermissionOffering.java │ │ │ ├── AppSuccess.java │ │ │ ├── TimePickerOffering.java │ │ │ ├── MessageFailure.java │ │ │ ├── TextGift.java │ │ │ └── PermissionFailure.java │ │ │ ├── command │ │ │ ├── core │ │ │ │ ├── SnippetAction.java │ │ │ │ ├── CoreMaker.java │ │ │ │ ├── Find.java │ │ │ │ ├── Setting.java │ │ │ │ ├── CoreCommand.java │ │ │ │ ├── CoreYielder.java │ │ │ │ ├── CoreDataCommand.java │ │ │ │ ├── Launch.java │ │ │ │ ├── Bits.java │ │ │ │ ├── Pause.java │ │ │ │ ├── Play.java │ │ │ │ ├── Info.java │ │ │ │ ├── Calculate.java │ │ │ │ ├── Torch.java │ │ │ │ ├── Roll.java │ │ │ │ ├── Navigate.java │ │ │ │ ├── Dial.java │ │ │ │ ├── Time.java │ │ │ │ ├── Weather.java │ │ │ │ ├── Celsius.java │ │ │ │ ├── Notifications.java │ │ │ │ ├── Fahrenheit.java │ │ │ │ └── Uninstall.java │ │ │ ├── app │ │ │ │ ├── MultilineMessenger.java │ │ │ │ ├── Discord.java │ │ │ │ ├── VideoSearchBySend.java │ │ │ │ ├── Tor.java │ │ │ │ ├── Firefox.java │ │ │ │ ├── AospContacts.java │ │ │ │ ├── NewPipe.java │ │ │ │ ├── Signal.java │ │ │ │ ├── Tubular.java │ │ │ │ ├── GitHub.java │ │ │ │ ├── Outlook.java │ │ │ │ ├── Markor.java │ │ │ │ ├── YouTube.java │ │ │ │ ├── AppYielder.java │ │ │ │ ├── AppSend.java │ │ │ │ ├── AppCommand.java │ │ │ │ ├── AppSendData.java │ │ │ │ └── AppSearch.java │ │ │ ├── Subcommand.java │ │ │ ├── SubcommandEntry.java │ │ │ ├── DataCommand.java │ │ │ ├── CommandYielder.java │ │ │ ├── DuplicateParams.java │ │ │ ├── ActionMap.java │ │ │ ├── Params.java │ │ │ └── CommandMap.java │ │ │ ├── result │ │ │ └── ChimeSoundResult.kt │ │ │ ├── util │ │ │ ├── Uris.java │ │ │ ├── Exceptions.java │ │ │ ├── Patterns.java │ │ │ ├── Hashes.kt │ │ │ ├── CalendarDetails.java │ │ │ ├── MediaControl.kt │ │ │ ├── Toasts.java │ │ │ ├── Timeit.java │ │ │ ├── Features.kt │ │ │ ├── TokenIterator.java │ │ │ ├── MimeTypes.java │ │ │ ├── Chars.java │ │ │ ├── Apps.java │ │ │ └── Services.kt │ │ │ ├── chime │ │ │ ├── Silence.java │ │ │ ├── Redial.java │ │ │ ├── Nebula.java │ │ │ ├── Chimer.java │ │ │ ├── Custom.java │ │ │ └── Chime.java │ │ │ ├── cursor │ │ │ ├── CursorReader.java │ │ │ ├── CursorExtractor.java │ │ │ ├── CursorTester.java │ │ │ ├── CursorArrayExtractor.java │ │ │ ├── FileDisplayName.java │ │ │ └── IsWritableDirectory.java │ │ │ ├── math │ │ │ ├── CalcToken.kt │ │ │ ├── IntegerNumber.kt │ │ │ ├── FloatingPointNumber.kt │ │ │ ├── BitwiseSign.kt │ │ │ ├── UnaryOperator.kt │ │ │ └── BinaryOperator.kt │ │ │ ├── trie │ │ │ ├── PositionalIterator.java │ │ │ ├── PrefixValue.java │ │ │ ├── Glyphs.java │ │ │ ├── PhraseTree.java │ │ │ ├── Words.java │ │ │ ├── WordsTree.java │ │ │ └── GlyphsTree.java │ │ │ ├── event │ │ │ ├── Plan.java │ │ │ ├── PingScheduler.java │ │ │ ├── PingPlan.java │ │ │ └── PingReceiver.java │ │ │ ├── struct │ │ │ └── IndexedStruct.java │ │ │ ├── file │ │ │ ├── ListItemHolder.java │ │ │ └── TreeFile.java │ │ │ ├── exception │ │ │ └── EmillaException.java │ │ │ ├── config │ │ │ ├── EmillaSettingsFragment.java │ │ │ └── AssistantFragment.java │ │ │ ├── sort │ │ │ ├── PrefixSearcher.java │ │ │ ├── ArrayWrapper.java │ │ │ ├── SearchItem.java │ │ │ └── ArrayWindow.java │ │ │ ├── contact │ │ │ ├── MultiSearch.java │ │ │ ├── fragment │ │ │ │ ├── ContactPhonesFragment.java │ │ │ │ └── ContactEmailsFragment.java │ │ │ └── adapter │ │ │ │ └── ContactCardAdapter.java │ │ │ ├── system │ │ │ └── BackgroundServiceUtil.java │ │ │ ├── ping │ │ │ ├── PingIntent.java │ │ │ └── ChanneledPinger.java │ │ │ └── web │ │ │ ├── Website.java │ │ │ └── SearchEngine.java │ ├── test │ │ └── java │ │ │ └── net │ │ │ └── emilla │ │ │ └── util │ │ │ └── TestPermission.java │ └── androidTest │ │ └── java │ │ └── net │ │ └── emilla │ │ ├── TestEmilla.java │ │ └── lang │ │ └── TestLang.java └── proguard-rules.pro ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── settings.gradle.kts ├── README.md └── gradle.properties /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/raw/nebula_act.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/app/src/main/res/raw/nebula_act.ogg -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/res/raw/nebula_exit.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/app/src/main/res/raw/nebula_exit.ogg -------------------------------------------------------------------------------- /app/src/main/res/raw/nebula_fail.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/app/src/main/res/raw/nebula_fail.ogg -------------------------------------------------------------------------------- /app/src/main/res/raw/nebula_pend.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/app/src/main/res/raw/nebula_pend.ogg -------------------------------------------------------------------------------- /app/src/main/res/raw/nebula_resume.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/app/src/main/res/raw/nebula_resume.ogg -------------------------------------------------------------------------------- /app/src/main/res/raw/nebula_start.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/app/src/main/res/raw/nebula_start.ogg -------------------------------------------------------------------------------- /app/src/main/res/raw/nebula_succeed.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/app/src/main/res/raw/nebula_succeed.ogg -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /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/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/lang/date/Meridiem.java: -------------------------------------------------------------------------------- 1 | package net.emilla.lang.date; 2 | 3 | public enum Meridiem { 4 | AM, PM, UNSPECIFIED 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/action/box/SnippetAdapter.java: -------------------------------------------------------------------------------- 1 | package net.emilla.action.box; 2 | 3 | /*internal*/ final class SnippetAdapter { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/action/box/TriResult.java: -------------------------------------------------------------------------------- 1 | package net.emilla.action.box; 2 | 3 | public enum TriResult { 4 | SUCCESS, WAITING, FAILURE 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_assistant.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_assistant.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_assistant.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_assistant.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_assistant.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_assistant.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_assistant.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_assistant.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_assistant.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_assistant.webp -------------------------------------------------------------------------------- /app/src/main/res/values-sw600dp/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | always 4 | -------------------------------------------------------------------------------- /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/res/mipmap-hdpi/ic_launcher_assistant_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_assistant_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_assistant_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_assistant_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_assistant_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_assistant_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_assistant_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_assistant_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_assistant_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devycarol/Emilla/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_assistant_round.webp -------------------------------------------------------------------------------- /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/res/drawable/ic_show_data.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/run/CommandRun.kt: -------------------------------------------------------------------------------- 1 | package net.emilla.run 2 | 3 | import net.emilla.activity.AssistActivity 4 | 5 | fun interface CommandRun { 6 | fun run(act: AssistActivity) 7 | } 8 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 16dp 3 | 8dp 4 | 52dp 5 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/SnippetAction.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | public enum SnippetAction { 4 | PEEK, GET, POP, REMOVE, ADD 5 | // Todo: 'rename' action 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/result/ChimeSoundResult.kt: -------------------------------------------------------------------------------- 1 | package net.emilla.result 2 | 3 | import android.net.Uri 4 | import net.emilla.chime.Chime 5 | 6 | data class ChimeSoundResult(@JvmField val chime: Chime, @JvmField val soundUri: Uri?) 7 | -------------------------------------------------------------------------------- /app/src/main/res/color/bg_assistant.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #BA575F 4 | #796AC0 5 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/action/Gadget.java: -------------------------------------------------------------------------------- 1 | package net.emilla.action; 2 | 3 | import net.emilla.activity.AssistActivity; 4 | 5 | public interface Gadget { 6 | 7 | void load(AssistActivity act); 8 | void unload(AssistActivity act); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/action/InstructyGadget.java: -------------------------------------------------------------------------------- 1 | package net.emilla.action; 2 | 3 | import androidx.annotation.Nullable; 4 | 5 | public interface InstructyGadget extends Gadget { 6 | 7 | void instruct(@Nullable String instruction); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_action_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/CoreMaker.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | import net.emilla.activity.AssistActivity; 4 | 5 | @FunctionalInterface 6 | /*internal*/ interface CoreMaker { 7 | 8 | CoreCommand make(AssistActivity act); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_selectable_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /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.13-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | portrait 4 | 5 | true 6 | -------------------------------------------------------------------------------- /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/util/Uris.java: -------------------------------------------------------------------------------- 1 | package net.emilla.util; 2 | 3 | import android.net.Uri; 4 | 5 | public final class Uris { 6 | 7 | public static Uri sms(String numbersCsv) { 8 | return Uri.parse("smsto:" + numbersCsv); 9 | } 10 | 11 | private Uris() {} 12 | 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/chime/Silence.java: -------------------------------------------------------------------------------- 1 | package net.emilla.chime; 2 | 3 | import android.content.Context; 4 | 5 | /*internal*/ final class Silence implements Chimer { 6 | 7 | /*internal*/ Silence() {} 8 | 9 | @Override 10 | public void chime(Context ctx, Chime chime) {} 11 | 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/cursor/CursorReader.java: -------------------------------------------------------------------------------- 1 | package net.emilla.cursor; 2 | 3 | /*internal*/ abstract class CursorReader { 4 | 5 | /*internal*/ final String[] mProjection; 6 | 7 | /*internal*/ CursorReader(String[] projection) { 8 | mProjection = projection; 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_edit_box.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /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/trie/PositionalIterator.java: -------------------------------------------------------------------------------- 1 | package net.emilla.trie; 2 | 3 | import androidx.annotation.Nullable; 4 | 5 | import java.util.Iterator; 6 | 7 | public interface PositionalIterator extends Iterator { 8 | int position(); 9 | @Nullable 10 | L leftoversFrom(int position); 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/util/Exceptions.java: -------------------------------------------------------------------------------- 1 | package net.emilla.util; 2 | 3 | public final class Exceptions { 4 | 5 | public static IndexOutOfBoundsException iob(int index) { 6 | return new IndexOutOfBoundsException("Index out of range: " + index); 7 | } 8 | 9 | private Exceptions() {} 10 | 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/list_divider_emilla.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /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/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/res/color/bg_activatable_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/content/receive/ContactReceiver.java: -------------------------------------------------------------------------------- 1 | package net.emilla.content.receive; 2 | 3 | import android.database.Cursor; 4 | 5 | import net.emilla.activity.AssistActivity; 6 | 7 | @FunctionalInterface 8 | public interface ContactReceiver extends ResultReceiver { 9 | 10 | void useContact(AssistActivity act, Cursor cur); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/util/Patterns.java: -------------------------------------------------------------------------------- 1 | package net.emilla.util; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | public final class Patterns { 6 | 7 | public static final Pattern TRIMMING_CSV = Pattern.compile(" *, *"); 8 | public static final Pattern TRIMMING_LINES = Pattern.compile(" *\n *"); 9 | 10 | private Patterns() {} 11 | 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_star.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_play.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_item_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /app/src/test/java/net/emilla/util/TestPermission.java: -------------------------------------------------------------------------------- 1 | package net.emilla.util; 2 | 3 | public final class TestPermission { 4 | 5 | // @Test 6 | // public void testPermissionEntries() { 7 | // for (var permission : Permission.values()) { 8 | // assertTrue(permission.mRestrictSdk >= Build.VERSION_CODES.M); 9 | // } 10 | // } 11 | 12 | } -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/cursor/CursorExtractor.java: -------------------------------------------------------------------------------- 1 | package net.emilla.cursor; 2 | 3 | import android.database.Cursor; 4 | 5 | public abstract class CursorExtractor extends CursorReader { 6 | 7 | /*internal*/ CursorExtractor(String[] projection) { 8 | super(projection); 9 | } 10 | 11 | public abstract T extract(Cursor cursor); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/cursor/CursorTester.java: -------------------------------------------------------------------------------- 1 | package net.emilla.cursor; 2 | 3 | import android.database.Cursor; 4 | 5 | import java.util.function.Predicate; 6 | 7 | public abstract class CursorTester extends CursorReader implements Predicate { 8 | 9 | /*internal*/ CursorTester(String[] projection) { 10 | super(projection); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/trie/PrefixValue.java: -------------------------------------------------------------------------------- 1 | package net.emilla.trie; 2 | 3 | /*internal*/ final class PrefixValue { 4 | 5 | public final V value; 6 | public final boolean takesLeftovers; 7 | 8 | /*internal*/ PrefixValue(V value, boolean takesLeftovers) { 9 | this.value = value; 10 | this.takesLeftovers = takesLeftovers; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/util/Hashes.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("Hashes") 2 | 3 | package net.emilla.util 4 | 5 | @JvmName("one") 6 | fun hash1(o: Any) = hash1(o.hashCode()) 7 | @JvmName("one") 8 | fun hash1(n: Int) = 0x1F + n 9 | @JvmName("two") 10 | fun hash2(a: Any, b: Any) = hash2(a.hashCode(), b.hashCode()) 11 | @JvmName("two") 12 | fun hash2(a: Int, b: Int) = 0x1F * (0x1F + a) + b 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_pause.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/MultilineMessenger.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import android.content.Context; 4 | 5 | import net.emilla.R; 6 | 7 | /*internal*/ final class MultilineMessenger extends AppSendData { 8 | 9 | /*internal*/ MultilineMessenger(Context ctx, AppEntry appEntry) { 10 | super(ctx, appEntry, R.string.data_hint_message_cont); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /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 POMODORO_WARNING = 1; 6 | public static final int POMODORO_ENDED = 2; 7 | 8 | public final int slot; 9 | public final long time; 10 | 11 | public Plan(int slot, long time) { 12 | this.slot = slot; 13 | this.time = time; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_hide_data.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/Discord.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import android.content.Context; 4 | 5 | /*internal*/ final class Discord { 6 | 7 | public static final String PKG = "com.discord"; 8 | 9 | /*internal*/ static AppSend instance(Context ctx, AppEntry appEntry) { 10 | return new AppSend(ctx, appEntry); 11 | } 12 | 13 | private Discord() {} 14 | 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/VideoSearchBySend.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import android.content.Context; 4 | import android.view.inputmethod.EditorInfo; 5 | 6 | /*internal*/ final class VideoSearchBySend extends AppSend { 7 | 8 | /*internal*/ VideoSearchBySend(Context ctx, AppEntry appEntry) { 9 | super(ctx, appEntry, EditorInfo.IME_ACTION_SEARCH); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/content/receive/ContactDataReceiver.java: -------------------------------------------------------------------------------- 1 | package net.emilla.content.receive; 2 | 3 | import net.emilla.activity.AssistActivity; 4 | 5 | public interface ContactDataReceiver extends ContactReceiver { 6 | 7 | /// Provides the receiver with contact data. 8 | /// 9 | /// @param data is provided to the receiver. 10 | void provide(AssistActivity act, String data); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/res/menu/list_file_toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/menu/notes_toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_bookmark.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/Tor.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import android.content.Context; 4 | 5 | /*internal*/ final class Tor { 6 | 7 | public static final String PKG = "org.torproject.torbrowser"; 8 | 9 | /*internal*/ static AppCommand instance(Context ctx, AppEntry appEntry) { 10 | return new AppCommand(ctx, appEntry); 11 | } 12 | 13 | private Tor() {} 14 | 15 | } 16 | -------------------------------------------------------------------------------- /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 | /// Notifies the object of whether the chooser was accepted or dismissed. 7 | /// 8 | /// @param chosen true if an app was chosen, false if the chooser was dismissed. 9 | void provide(boolean chosen); 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_assistant.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/Firefox.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import android.content.Context; 4 | 5 | /*internal*/ final class Firefox { 6 | 7 | public static final String PKG = "org.mozilla.firefox"; 8 | 9 | /*internal*/ static AppSearch instance(Context ctx, AppEntry appEntry) { 10 | return new AppSearch(ctx, appEntry); 11 | } 12 | 13 | private Firefox() {} 14 | 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_uninstall.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /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/java/net/emilla/command/app/AospContacts.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import android.content.Context; 4 | 5 | /*internal*/ final class AospContacts { 6 | 7 | public static final String PKG = "com.android.contacts"; 8 | 9 | /*internal*/ static AppSearch instance(Context ctx, AppEntry appEntry) { 10 | return new AppSearch(ctx, appEntry); 11 | } 12 | 13 | private AospContacts() {} 14 | 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/NewPipe.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import android.content.Context; 4 | 5 | /*internal*/ final class NewPipe { 6 | 7 | public static final String PKG = "org.schabi.newpipe"; 8 | 9 | /*internal*/ static VideoSearchBySend instance(Context ctx, AppEntry appEntry) { 10 | return new VideoSearchBySend(ctx, appEntry); 11 | } 12 | 13 | private NewPipe() {} 14 | 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_info.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/Signal.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import android.content.Context; 4 | 5 | /*internal*/ final class Signal { 6 | 7 | public static final String PKG = "org.thoughtcrime.securesms"; 8 | 9 | /*internal*/ static MultilineMessenger instance(Context ctx, AppEntry appEntry) { 10 | return new MultilineMessenger(ctx, appEntry); 11 | } 12 | 13 | private Signal() {} 14 | 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/cursor/CursorArrayExtractor.java: -------------------------------------------------------------------------------- 1 | package net.emilla.cursor; 2 | 3 | import android.database.Cursor; 4 | 5 | public abstract class CursorArrayExtractor 6 | extends CursorExtractor { 7 | 8 | /*internal*/ CursorArrayExtractor(String[] projection) { 9 | super(projection); 10 | } 11 | 12 | public abstract boolean filter(Cursor cursor); 13 | public abstract T[] newArray(int length); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /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/ic_command.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/Subcommand.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command; 2 | 3 | import androidx.annotation.Nullable; 4 | 5 | public final class Subcommand> { 6 | 7 | public final A action; 8 | @Nullable 9 | public final String instruction; 10 | 11 | public Subcommand(A action, @Nullable String instruction) { 12 | this.action = action; 13 | this.instruction = instruction; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/Tubular.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import android.content.Context; 4 | 5 | /*internal*/ final class Tubular { 6 | 7 | public static final String PKG = "org.polymorphicshade.tubular"; 8 | 9 | /*internal*/ static VideoSearchBySend instance(Context ctx, AppEntry appEntry) { 10 | return new VideoSearchBySend(ctx, appEntry); 11 | } 12 | 13 | private Tubular() {} 14 | 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_folder.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/prefrence_widget_switch.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_subject.xml: -------------------------------------------------------------------------------- 1 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/SubcommandEntry.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command; 2 | 3 | import androidx.annotation.ArrayRes; 4 | import androidx.annotation.StringRes; 5 | 6 | public enum SubcommandEntry { 7 | ; 8 | 9 | @StringRes 10 | public final int name; 11 | @ArrayRes 12 | public final int aliases; 13 | 14 | SubcommandEntry(int name, int aliases) { 15 | this.name = name; 16 | this.aliases = aliases; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_email.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_media.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/GitHub.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import android.content.Context; 4 | 5 | import net.emilla.R; 6 | 7 | /*internal*/ final class GitHub { 8 | 9 | public static final String PKG = "com.github.android"; 10 | 11 | /*internal*/ static AppSendData instance(Context ctx, AppEntry appEntry) { 12 | return new AppSendData(ctx, appEntry, R.string.data_hint_issue); 13 | } 14 | 15 | private GitHub() {} 16 | 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/lang/date/DateTimeSpan.java: -------------------------------------------------------------------------------- 1 | package net.emilla.lang.date; 2 | 3 | import androidx.annotation.Nullable; 4 | 5 | import java.time.LocalDateTime; 6 | 7 | public final class DateTimeSpan { 8 | 9 | public final LocalDateTime start; 10 | @Nullable 11 | public final LocalDateTime end; 12 | 13 | public DateTimeSpan(LocalDateTime start, @Nullable LocalDateTime end) { 14 | this.start = start; 15 | this.end = end; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/run/BroadcastGift.java: -------------------------------------------------------------------------------- 1 | package net.emilla.run; 2 | 3 | import android.content.Intent; 4 | 5 | import net.emilla.activity.AssistActivity; 6 | 7 | public final class BroadcastGift implements CommandRun { 8 | 9 | private final Intent mIntent; 10 | 11 | public BroadcastGift(Intent intent) { 12 | mIntent = intent; 13 | } 14 | 15 | @Override 16 | public void run(AssistActivity act) { 17 | act.sendBroadcast(mIntent); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/Outlook.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import android.content.Context; 4 | 5 | import net.emilla.R; 6 | 7 | /*internal*/ final class Outlook { 8 | 9 | public static final String PKG = "com.microsoft.office.outlook"; 10 | 11 | /*internal*/ static AppSendData instance(Context ctx, AppEntry appEntry) { 12 | return new AppSendData(ctx, appEntry, R.string.data_hint_email); 13 | } 14 | 15 | private Outlook() {} 16 | 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/struct/IndexedStruct.java: -------------------------------------------------------------------------------- 1 | package net.emilla.struct; 2 | 3 | import java.util.Iterator; 4 | import java.util.RandomAccess; 5 | import java.util.stream.Stream; 6 | 7 | public interface IndexedStruct extends Iterable, RandomAccess { 8 | 9 | E get(int index); 10 | int size(); 11 | boolean isEmpty(); 12 | Stream stream(); 13 | 14 | @Override 15 | default Iterator iterator() { 16 | return stream().iterator(); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_app.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_cursor_start.xml: -------------------------------------------------------------------------------- 1 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_copy.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_toast.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/lang/phrase/Dices.java: -------------------------------------------------------------------------------- 1 | package net.emilla.lang.phrase; 2 | 3 | import java.util.Arrays; 4 | import java.util.Random; 5 | 6 | public final class Dices { 7 | 8 | private final Dice[] mDices; 9 | 10 | public Dices(Dice[] dices) { 11 | Arrays.sort(dices); 12 | mDices = dices; 13 | } 14 | 15 | public int roll(Random rand) { 16 | int result = 0; 17 | for (Dice dice : mDices) result += dice.roll(rand); 18 | return result; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /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/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(this.context, plan.ping, plan.channel); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/trie/Glyphs.java: -------------------------------------------------------------------------------- 1 | package net.emilla.trie; 2 | 3 | import net.emilla.lang.Lang; 4 | 5 | /*internal*/ final class Glyphs extends Phrase { 6 | 7 | /*internal*/ Glyphs(String phrase) { 8 | super(phrase); 9 | } 10 | 11 | @Override 12 | protected Integer normalizedNext() { 13 | int codePoint = Character.codePointAt(mPhrase, mPosition); 14 | mPosition += Character.charCount(codePoint); 15 | return Lang.normalize(codePoint); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_star_checked.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launch.xml: -------------------------------------------------------------------------------- 1 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/file/ListItemHolder.java: -------------------------------------------------------------------------------- 1 | package net.emilla.file; 2 | 3 | import android.widget.TextView; 4 | 5 | import androidx.recyclerview.widget.RecyclerView; 6 | 7 | import net.emilla.databinding.ListItemBinding; 8 | 9 | public final class ListItemHolder extends RecyclerView.ViewHolder { 10 | 11 | public final TextView labelView; 12 | 13 | public ListItemHolder(ListItemBinding binding) { 14 | super(binding.getRoot()); 15 | 16 | this.labelView = binding.getRoot(); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_assistant.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notify.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/xml/accessibility_service_config.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_sms.xml: -------------------------------------------------------------------------------- 1 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_message.xml: -------------------------------------------------------------------------------- 1 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/btn_action.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/Markor.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import android.content.Context; 4 | 5 | import net.emilla.R; 6 | 7 | /*internal*/ final class Markor { 8 | 9 | public static final String PKG = "net.gsantner.markor"; 10 | public static final String CLS_MAIN = PKG + ".activity.MainActivity"; 11 | 12 | /*internal*/ static AppSendData instance(Context ctx, AppEntry appEntry) { 13 | return new AppSendData(ctx, appEntry, R.string.data_hint_text); 14 | } 15 | 16 | private Markor() {} 17 | 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_torch.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/util/CalendarDetails.java: -------------------------------------------------------------------------------- 1 | package net.emilla.util; 2 | 3 | import androidx.annotation.StringRes; 4 | 5 | public final class CalendarDetails { 6 | 7 | public static int parseAvailability(String s, @StringRes int errorTitle) { 8 | throw new UnsupportedOperationException("Not implemented yet!"); 9 | } 10 | 11 | public static int parseVisibility(String s, @StringRes int errorTitle) { 12 | throw new UnsupportedOperationException("Not implemented yet!"); 13 | } 14 | 15 | private CalendarDetails() {} 16 | 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_random_number.xml: -------------------------------------------------------------------------------- 1 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_todo.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_calculate.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_temperature.xml: -------------------------------------------------------------------------------- 1 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /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/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/run/ToastGift.java: -------------------------------------------------------------------------------- 1 | package net.emilla.run; 2 | 3 | import net.emilla.activity.AssistActivity; 4 | import net.emilla.util.Toasts; 5 | 6 | public final class ToastGift implements CommandRun { 7 | 8 | private final CharSequence mMessage; 9 | private final boolean mIsLongToast; 10 | 11 | public ToastGift(CharSequence msg, boolean isLongToast) { 12 | mMessage = msg; 13 | mIsLongToast = isLongToast; 14 | } 15 | 16 | @Override 17 | public void run(AssistActivity act) { 18 | Toasts.show(act, mMessage, mIsLongToast); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/chime/Redial.java: -------------------------------------------------------------------------------- 1 | package net.emilla.chime; 2 | 3 | import android.content.Context; 4 | import android.media.AudioManager; 5 | import android.media.ToneGenerator; 6 | 7 | /*internal*/ final class Redial implements Chimer { 8 | 9 | private final ToneGenerator mToneGenerator = new ToneGenerator( 10 | AudioManager.STREAM_MUSIC, 11 | ToneGenerator.MAX_VOLUME 12 | ); 13 | 14 | /*internal*/ Redial() {} 15 | 16 | @Override 17 | public void chime(Context ctx, Chime chime) { 18 | mToneGenerator.startTone(chime.redialTone); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_attach.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_clock.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/trie/PhraseTree.java: -------------------------------------------------------------------------------- 1 | package net.emilla.trie; 2 | 3 | import java.util.function.IntFunction; 4 | 5 | public sealed interface PhraseTree permits WordsTree, GlyphsTree { 6 | 7 | void put(String phrase, V value, boolean takesLeftovers); 8 | PrefixResult get(String phrase); 9 | 10 | static PhraseTree of( 11 | boolean wordsAreSpaceSeparated, 12 | IntFunction arrayGenerator 13 | ) { 14 | return wordsAreSpaceSeparated 15 | ? new WordsTree(arrayGenerator) 16 | : new GlyphsTree(arrayGenerator); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_alarm.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_timer.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_star_unchecked.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_contact.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/menu/bottom_nav_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 18 | 19 | -------------------------------------------------------------------------------- /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 CommandRun { 10 | 11 | private final CharSequence mText; 12 | 13 | public CopyGift(CharSequence text) { 14 | mText = text; 15 | } 16 | 17 | @Override 18 | public void run(AssistActivity act) { 19 | ClipboardManager clipMgr = Services.clipboard(act); 20 | clipMgr.setPrimaryClip(ClipData.newPlainText(null, mText)); 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/trie/Words.java: -------------------------------------------------------------------------------- 1 | package net.emilla.trie; 2 | 3 | import net.emilla.lang.Lang; 4 | 5 | /*internal*/ final class Words extends Phrase { 6 | 7 | /*internal*/ Words(String phrase) { 8 | super(phrase); 9 | } 10 | 11 | @Override 12 | protected String normalizedNext() { 13 | int start = mPosition; 14 | 15 | do ++mPosition; 16 | while (mPosition < mLength && !Character.isWhitespace(mPhrase[mPosition])); 17 | 18 | int span = mPosition - start; 19 | var word = new String(mPhrase, start, span); 20 | 21 | return Lang.normalize(word); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /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 | /*internal*/ Duration(int hours, int minutes, int seconds) { 18 | this.seconds = hours * 60 * 60 + minutes * 60 + seconds; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/res/color/bg_text_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /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/java/net/emilla/exception/EmillaException.java: -------------------------------------------------------------------------------- 1 | package net.emilla.exception; 2 | 3 | import androidx.annotation.StringRes; 4 | 5 | import net.emilla.R; 6 | 7 | public final class EmillaException extends RuntimeException { 8 | 9 | @StringRes 10 | public final int title; 11 | @StringRes 12 | public final int message; 13 | 14 | public EmillaException(@StringRes int message) { 15 | this(R.string.error, message); 16 | } 17 | 18 | public EmillaException(@StringRes int title, @StringRes int message) { 19 | super(); 20 | 21 | this.title = title; 22 | this.message = message; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /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/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/res/drawable/ic_calendar.xml: -------------------------------------------------------------------------------- 1 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/Find.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | //import android.content.Context; 4 | //import android.view.inputmethod.EditorInfo; 5 | // 6 | //import net.emilla.activity.AssistActivity; 7 | // 8 | ///*internal*/ final class Find extends CoreCommand { 9 | // 10 | // /*internal*/ Find(Context ctx) { 11 | // super(ctx, CoreEntry.FIND, EditorInfo.IME_ACTION_SEARCH); 12 | // } 13 | // 14 | // @Override 15 | // protected void run(AssistActivity act) { 16 | // ; 17 | // } 18 | // 19 | // @Override 20 | // protected void run(AssistActivity act, String fileOrFolder) { 21 | // ; 22 | // } 23 | // 24 | //} 25 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/Setting.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | //import android.content.Context; 4 | //import android.view.inputmethod.EditorInfo; 5 | // 6 | //import net.emilla.activity.AssistActivity; 7 | // 8 | ///*internal*/ final class Setting extends CoreCommand { 9 | // 10 | // /*internal*/ Setting(Context ctx) { 11 | // super(ctx, CoreEntry.SETTING, EditorInfo.IME_ACTION_DONE); 12 | // } 13 | // 14 | // @Override 15 | // protected void run(AssistActivity act) { 16 | // ; 17 | // } 18 | // 19 | // @Override 20 | // protected void run(AssistActivity act, String query) { 21 | // ; 22 | // } 23 | // 24 | //} 25 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_person.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/chime/Nebula.java: -------------------------------------------------------------------------------- 1 | package net.emilla.chime; 2 | 3 | import android.content.Context; 4 | import android.media.MediaPlayer; 5 | 6 | /// The default chimer, Nebula. 7 | /*internal*/ final class Nebula implements Chimer { 8 | 9 | /*internal*/ Nebula() {} 10 | 11 | @Override 12 | public void chime(Context ctx, Chime chime) { 13 | MediaPlayer player = mediaPlayer(ctx, chime); 14 | player.setOnCompletionListener(MediaPlayer::release); 15 | player.start(); 16 | } 17 | 18 | public static MediaPlayer mediaPlayer(Context ctx, Chime chime) { 19 | return MediaPlayer.create(ctx, chime.nebulaSound); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/config/EmillaSettingsFragment.java: -------------------------------------------------------------------------------- 1 | package net.emilla.config; 2 | 3 | import android.content.SharedPreferences; 4 | 5 | import androidx.preference.Preference; 6 | import androidx.preference.PreferenceFragmentCompat; 7 | 8 | import java.util.Objects; 9 | 10 | public abstract class EmillaSettingsFragment extends PreferenceFragmentCompat { 11 | 12 | protected final T preferenceOf(String key) { 13 | return Objects.requireNonNull(findPreference(key)); 14 | } 15 | 16 | protected final SharedPreferences prefs() { 17 | return Objects.requireNonNull(getPreferenceManager().getSharedPreferences()); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /app/src/androidTest/java/net/emilla/TestEmilla.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 | @RunWith(AndroidJUnit4.class) 14 | public final class TestEmilla { 15 | 16 | @Test 17 | public void testApplicationId() { 18 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 19 | assertEquals(BuildConfig.APPLICATION_ID, appContext.getPackageName()); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_assistant_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_location.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_select_all.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_weather.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /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 CommandRun { 8 | 9 | protected final AlertDialog.Builder dialog; 10 | 11 | public DialogRun(AlertDialog.Builder dialog) { 12 | this.dialog = dialog; 13 | } 14 | 15 | @Override 16 | public /*open*/ void run(AssistActivity act) { 17 | this.dialog.setOnCancelListener(dlg -> { 18 | act.onCloseDialog(); // Todo: don't require this 19 | act.resume(); 20 | }); 21 | act.prepareForDialog(); 22 | this.dialog.show(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/YouTube.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import android.content.Context; 4 | 5 | /*internal*/ final class YouTube { 6 | 7 | public static final String PKG = "com.google.android.youtube"; 8 | 9 | /*internal*/ static AppSearch instance(Context ctx, AppEntry appEntry) { 10 | return new AppSearch(ctx, appEntry); 11 | // Todo: instantly pull up bookmarked videos, specialized search for channels, playlists, 12 | // etc. I assume the G assistant has similar functionality. If requires internet could use 13 | // bookmarks at the very least. Also, this command is broken when a video is playing. 14 | } 15 | 16 | private YouTube() {} 17 | 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_call.xml: -------------------------------------------------------------------------------- 1 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/CoreCommand.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.annotation.StringRes; 6 | 7 | import net.emilla.command.EmillaCommand; 8 | import net.emilla.exception.EmillaException; 9 | 10 | /*internal*/ abstract class CoreCommand extends EmillaCommand { 11 | 12 | @StringRes 13 | private final int mName; 14 | 15 | protected CoreCommand(Context ctx, CoreEntry coreEntry, int imeAction) { 16 | super(ctx, coreEntry, imeAction); 17 | 18 | mName = coreEntry.name; 19 | } 20 | 21 | protected final EmillaException badCommand(@StringRes int msg) { 22 | return new EmillaException(mName, msg); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/CoreYielder.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | import net.emilla.activity.AssistActivity; 4 | import net.emilla.command.CommandYielder; 5 | import net.emilla.command.EmillaCommand; 6 | 7 | /*internal*/ final class CoreYielder extends CommandYielder { 8 | 9 | private final CoreEntry mCoreEntry; 10 | 11 | /*internal*/ CoreYielder(CoreEntry coreEntry) { 12 | mCoreEntry = coreEntry; 13 | } 14 | 15 | @Override 16 | public boolean usesInstruction() { 17 | return mCoreEntry.usesInstruction; 18 | } 19 | 20 | @Override 21 | protected EmillaCommand makeCommand(AssistActivity act) { 22 | return mCoreEntry.mMaker.make(act); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/cursor/FileDisplayName.java: -------------------------------------------------------------------------------- 1 | package net.emilla.cursor; 2 | 3 | import android.database.Cursor; 4 | import android.provider.DocumentsContract.Document; 5 | 6 | public final class FileDisplayName extends CursorExtractor { 7 | 8 | private static final int INDEX_DISPLAY_NAME = 0; 9 | 10 | private static final String[] PROJECTION = { 11 | Document.COLUMN_DISPLAY_NAME 12 | }; 13 | 14 | public static final FileDisplayName INSTANCE = new FileDisplayName(); 15 | 16 | private FileDisplayName() { 17 | super(PROJECTION); 18 | } 19 | 20 | @Override 21 | public String extract(Cursor cursor) { 22 | return cursor.getString(INDEX_DISPLAY_NAME); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/trie/WordsTree.java: -------------------------------------------------------------------------------- 1 | package net.emilla.trie; 2 | 3 | import java.util.function.IntFunction; 4 | 5 | /*internal*/ final class WordsTree implements PhraseTree { 6 | 7 | private final PrefixTree mPrefixTree; 8 | 9 | /*internal*/ WordsTree(IntFunction arrayGenerator) { 10 | mPrefixTree = new PrefixTree(arrayGenerator); 11 | } 12 | 13 | @Override 14 | public void put(String phrase, V value, boolean takesLeftovers) { 15 | mPrefixTree.put(new Words(phrase), value, takesLeftovers); 16 | } 17 | 18 | @Override 19 | public PrefixResult get(String phrase) { 20 | return mPrefixTree.get(new Words(phrase)); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/util/Toasts.java: -------------------------------------------------------------------------------- 1 | package net.emilla.util; 2 | 3 | import android.content.Context; 4 | import android.widget.Toast; 5 | 6 | import androidx.annotation.StringRes; 7 | 8 | public final class Toasts { 9 | 10 | public static void show(Context ctx, @StringRes int text) { 11 | Toast.makeText(ctx, text, Toast.LENGTH_SHORT).show(); 12 | } 13 | 14 | public static void show(Context ctx, CharSequence text) { 15 | show(ctx, text, false); 16 | } 17 | 18 | public static void show(Context ctx, CharSequence text, boolean isLongToast) { 19 | Toast.makeText(ctx, text, isLongToast ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT).show(); 20 | } 21 | 22 | private Toasts() {} 23 | 24 | } 25 | -------------------------------------------------------------------------------- /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.INDEX_NUMBER; 4 | 5 | import android.database.Cursor; 6 | 7 | import net.emilla.activity.AssistActivity; 8 | 9 | @FunctionalInterface 10 | public interface PhoneReceiver extends ContactDataReceiver { 11 | 12 | @Override 13 | default void useContact(AssistActivity act, Cursor cur) { 14 | provide(act, cur.getString(INDEX_NUMBER)); 15 | } 16 | 17 | /// Provides the receiver with a phone number. 18 | /// 19 | /// @param phoneNumber is provided to the receiver. 20 | @Override 21 | void provide(AssistActivity act, String phoneNumber); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /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.INDEX_ADDRESS; 4 | 5 | import android.database.Cursor; 6 | 7 | import net.emilla.activity.AssistActivity; 8 | 9 | @FunctionalInterface 10 | public interface EmailReceiver extends ContactDataReceiver { 11 | 12 | @Override 13 | default void useContact(AssistActivity act, Cursor cur) { 14 | provide(act, cur.getString(INDEX_ADDRESS)); 15 | } 16 | 17 | /// Provides the receiver with an email address. 18 | /// 19 | /// @param emailAddress is provided to the receiver. 20 | @Override 21 | void provide(AssistActivity act, String emailAddress); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/trie/GlyphsTree.java: -------------------------------------------------------------------------------- 1 | package net.emilla.trie; 2 | 3 | import java.util.function.IntFunction; 4 | 5 | /*internal*/ final class GlyphsTree implements PhraseTree { 6 | 7 | private final PrefixTree mPrefixTree; 8 | 9 | /*internal*/ GlyphsTree(IntFunction arrayGenerator) { 10 | mPrefixTree = new PrefixTree(arrayGenerator); 11 | } 12 | 13 | @Override 14 | public void put(String phrase, V value, boolean takesLeftovers) { 15 | mPrefixTree.put(new Glyphs(phrase), value, takesLeftovers); 16 | } 17 | 18 | @Override 19 | public PrefixResult get(String phrase) { 20 | return mPrefixTree.get(new Glyphs(phrase)); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/res/xml/shortcuts.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 10 | 11 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/DataCommand.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command; 2 | 3 | import androidx.annotation.StringRes; 4 | 5 | import net.emilla.activity.AssistActivity; 6 | 7 | public interface DataCommand { 8 | 9 | @StringRes 10 | int dataHint(); 11 | void runWithData(AssistActivity act, String data); 12 | void runWithData(AssistActivity act, String instruction, String data); 13 | 14 | static void execute(DataCommand cmd, AssistActivity act, String data) { 15 | String instruction = ((EmillaCommand) cmd).instruction(); 16 | if (instruction != null) { 17 | cmd.runWithData(act, instruction, data); 18 | } else { 19 | cmd.runWithData(act, data); 20 | } 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/CoreDataCommand.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | import android.content.Context; 4 | import android.view.inputmethod.EditorInfo; 5 | 6 | import androidx.annotation.StringRes; 7 | 8 | import net.emilla.command.DataCommand; 9 | 10 | /*internal*/ abstract class CoreDataCommand extends CoreCommand implements DataCommand { 11 | 12 | @StringRes 13 | private final int mHint; 14 | 15 | protected CoreDataCommand(Context ctx, CoreEntry coreEntry, @StringRes int dataHint) { 16 | super(ctx, coreEntry, EditorInfo.IME_ACTION_NEXT); 17 | mHint = dataHint; 18 | } 19 | 20 | @Override @StringRes 21 | public final int dataHint() { 22 | return mHint; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_pomodoro.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/field_extra.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_share.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/sort/PrefixSearcher.java: -------------------------------------------------------------------------------- 1 | package net.emilla.sort; 2 | 3 | /*internal*/ final class PrefixSearcher implements Comparable { 4 | 5 | private final String mNormalizedSearch; 6 | private final int mPrefixLength; 7 | 8 | /*internal*/ PrefixSearcher(String normalizedSearch) { 9 | mNormalizedSearch = normalizedSearch; 10 | mPrefixLength = normalizedSearch.length(); 11 | } 12 | 13 | @Override 14 | public int compareTo(SearchItem item) { 15 | String searchKey = item.mSearchKey; 16 | 17 | if (searchKey.length() > mPrefixLength) { 18 | searchKey = searchKey.substring(0, mPrefixLength); 19 | } 20 | 21 | return mNormalizedSearch.compareTo(searchKey); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/res/xml/command_prefs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 14 | 15 | 16 | 19 | 20 | -------------------------------------------------------------------------------- /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 | int termCount = terms.length; 13 | for (int i = 1; i < termCount; ++i) { 14 | terms[i] = '%' + terms[i] + '%'; 15 | selection.append(" OR ").append(baseSelection); 16 | } 17 | 18 | return new MultiSearch(selection.toString(), terms, termCount > 1); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/lang/date/impl/DurationEN_US.java: -------------------------------------------------------------------------------- 1 | package net.emilla.lang.date.impl; 2 | 3 | import androidx.annotation.StringRes; 4 | 5 | import net.emilla.R; 6 | import net.emilla.exception.EmillaException; 7 | import net.emilla.lang.date.Duration; 8 | 9 | public final class DurationEN_US { 10 | 11 | public static Duration instance(String minutes, @StringRes int errorTitle) { 12 | try { 13 | var seconds = (int) (Double.parseDouble(minutes) * 60.0); 14 | // Todo: other time units, clock notation. 15 | return new Duration(seconds, errorTitle); 16 | } catch (NumberFormatException e) { 17 | throw new EmillaException(errorTitle, R.string.error_bad_minutes); 18 | } 19 | } 20 | 21 | private DurationEN_US() {} 22 | } 23 | -------------------------------------------------------------------------------- /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/main/java/net/emilla/sort/ArrayWrapper.java: -------------------------------------------------------------------------------- 1 | package net.emilla.sort; 2 | 3 | import net.emilla.struct.IndexedStruct; 4 | 5 | import java.util.Arrays; 6 | import java.util.stream.Stream; 7 | 8 | /*internal*/ final class ArrayWrapper implements IndexedStruct { 9 | 10 | /*internal*/ E[] mArray; 11 | 12 | /*internal*/ ArrayWrapper(E[] array) { 13 | mArray = array; 14 | } 15 | 16 | @Override 17 | public E get(int index) { 18 | return mArray[index]; 19 | } 20 | 21 | @Override 22 | public int size() { 23 | return mArray.length; 24 | } 25 | 26 | @Override 27 | public boolean isEmpty() { 28 | return mArray.length == 0; 29 | } 30 | 31 | @Override 32 | public Stream stream() { 33 | return Arrays.stream(mArray); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/AppYielder.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import net.emilla.activity.AssistActivity; 4 | import net.emilla.command.CommandYielder; 5 | 6 | public final class AppYielder extends CommandYielder { 7 | 8 | private final AppEntry mApp; 9 | 10 | /*internal*/ AppYielder(AppEntry app) { 11 | mApp = app; 12 | } 13 | 14 | @Override 15 | public boolean usesInstruction() { 16 | return mApp.actions.usesInstruction(); 17 | } 18 | 19 | @Override 20 | protected AppCommand makeCommand(AssistActivity act) { 21 | AppProperties properties = mApp.properties; 22 | if (properties != null) { 23 | return properties.maker.make(act, mApp); 24 | } 25 | 26 | return mApp.actions.defaultCommand(act, mApp); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/Launch.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.content.pm.PackageManager; 6 | import android.view.inputmethod.EditorInfo; 7 | 8 | import androidx.annotation.Nullable; 9 | 10 | import net.emilla.command.app.AppEntry; 11 | import net.emilla.util.Intents; 12 | 13 | /*internal*/ final class Launch extends OpenCommand { 14 | 15 | /*internal*/ Launch(Context ctx) { 16 | super(ctx, CoreEntry.LAUNCH, EditorInfo.IME_ACTION_GO); 17 | } 18 | 19 | @Override @Nullable 20 | protected Intent defaultIntent() { 21 | return null; 22 | } 23 | 24 | @Override 25 | public Intent makeIntent(AppEntry app, PackageManager pm) { 26 | return Intents.launchApp(app); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/lang/phrase/RandRange.java: -------------------------------------------------------------------------------- 1 | package net.emilla.lang.phrase; 2 | 3 | import androidx.annotation.StringRes; 4 | 5 | import net.emilla.R; 6 | import net.emilla.exception.EmillaException; 7 | 8 | public final class RandRange { 9 | 10 | public final int inclusStart; 11 | public final int exclusEnd; 12 | 13 | public RandRange(int inclusEnd, @StringRes int errorTitle) { 14 | this(Math.min(inclusEnd, 1), inclusEnd <= 0 ? 0 : inclusEnd + 1, errorTitle); 15 | } 16 | 17 | public RandRange(int inclusStart, int exclusEnd, @StringRes int errorTitle) { 18 | if (inclusStart >= exclusEnd) { 19 | throw new EmillaException(errorTitle, R.string.error_invalid_number_range); 20 | } 21 | 22 | this.inclusStart = inclusStart; 23 | this.exclusEnd = exclusEnd; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_roll.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/AppSend.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import static android.content.Intent.EXTRA_TEXT; 4 | 5 | import android.content.Context; 6 | import android.view.inputmethod.EditorInfo; 7 | 8 | import net.emilla.activity.AssistActivity; 9 | import net.emilla.util.Intents; 10 | 11 | /*internal open*/ class AppSend extends AppCommand { 12 | 13 | /*internal*/ AppSend(Context ctx, AppEntry appEntry) { 14 | this(ctx, appEntry, EditorInfo.IME_ACTION_SEND); 15 | } 16 | 17 | /*internal*/ AppSend(Context ctx, AppEntry appEntry, int imeAction) { 18 | super(ctx, appEntry, imeAction); 19 | } 20 | 21 | @Override 22 | protected final void run(AssistActivity act, String message) { 23 | appSucceed(act, Intents.sendToApp(this.appEntry.pkg).putExtra(EXTRA_TEXT, message)); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/file/TreeFile.java: -------------------------------------------------------------------------------- 1 | package net.emilla.file; 2 | 3 | import android.content.Intent; 4 | import android.net.Uri; 5 | import android.provider.DocumentsContract; 6 | 7 | import net.emilla.sort.SearchItem; 8 | 9 | public final class TreeFile extends SearchItem { 10 | 11 | private final String mDocumentId; 12 | private final String mMimeType; 13 | 14 | public TreeFile(String documentId, String mimeType, String displayName) { 15 | super(displayName); 16 | 17 | mDocumentId = documentId; 18 | mMimeType = mimeType; 19 | } 20 | 21 | public Uri uri(Folder parent) { 22 | return DocumentsContract.buildDocumentUriUsingTree(parent.treeUri, mDocumentId); 23 | } 24 | 25 | public Intent viewIntent(Folder parent) { 26 | return Files.viewIntent(uri(parent), mMimeType); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/run/AppGift.kt: -------------------------------------------------------------------------------- 1 | package net.emilla.run 2 | 3 | import android.content.Intent 4 | import net.emilla.R 5 | import net.emilla.activity.AssistActivity 6 | import net.emilla.activity.DummyActivity 7 | import net.emilla.exception.EmillaException 8 | import net.emilla.util.Intents 9 | 10 | class AppGift(private val intent: Intent) : CommandRun { 11 | 12 | override fun run(act: AssistActivity) { 13 | if (intent.resolveActivity(act.packageManager) != null) { 14 | act.finishAndRemoveTask() 15 | val dummy: Intent = Intents.me(act, DummyActivity::class.java) 16 | .putExtra(Intent.EXTRA_INTENT, intent) 17 | .addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) 18 | act.startActivity(dummy) 19 | } else throw EmillaException(R.string.error, R.string.error_no_app) 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /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 | 6 | import androidx.annotation.RequiresPermission; 7 | 8 | import net.emilla.activity.AssistActivity; 9 | import net.emilla.ping.PingChannel; 10 | import net.emilla.ping.Pinger; 11 | 12 | public final class PingGift implements CommandRun { 13 | 14 | private final Notification mPing; 15 | private final PingChannel mChannel; 16 | 17 | @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS) 18 | public PingGift(Notification ping, PingChannel channel) { 19 | mPing = ping; 20 | mChannel = channel; 21 | } 22 | 23 | @Override @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS) 24 | public void run(AssistActivity act) { 25 | Pinger.of(act, mPing, mChannel).ping(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/util/Timeit.java: -------------------------------------------------------------------------------- 1 | package net.emilla.util; 2 | 3 | public final class Timeit { 4 | 5 | private static final String TAG = Timeit.class.getSimpleName(); 6 | 7 | // private static long sPrevTime = 0; 8 | // 9 | // public static long nanos(String label) { 10 | // if (sPrevTime == 0) sPrevTime = System.nanoTime(); 11 | // 12 | // var s = String.valueOf(System.nanoTime() - sPrevTime); 13 | // var sb = new StringBuilder(label).append(": "); 14 | // int start = sb.length(); 15 | // sb.append(s); 16 | // 17 | // for (int i = sb.length() - 3; i > start; i -= 3) { 18 | // sb.insert(i, ','); 19 | // } 20 | // sb.append(" nanoseconds"); 21 | // 22 | // Log.d(TAG, sb.toString()); 23 | // 24 | // return sPrevTime = System.nanoTime(); 25 | // } 26 | 27 | private Timeit() {} 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_snippets.xml: -------------------------------------------------------------------------------- 1 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/CommandYielder.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command; 2 | 3 | import androidx.annotation.Nullable; 4 | 5 | import net.emilla.activity.AssistActivity; 6 | 7 | public abstract class CommandYielder { 8 | 9 | private EmillaCommand mCommand = null; 10 | 11 | protected CommandYielder() {} 12 | 13 | public abstract boolean usesInstruction(); 14 | protected abstract EmillaCommand makeCommand(AssistActivity act); 15 | 16 | public final EmillaCommand command(AssistActivity act) { 17 | if (mCommand == null) { 18 | mCommand = makeCommand(act); 19 | } 20 | return mCommand; 21 | } 22 | 23 | public final EmillaCommand command(AssistActivity act, @Nullable String instruction) { 24 | EmillaCommand command = command(act); 25 | command.instruct(instruction); 26 | return command; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /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/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 | @Override 19 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 20 | mBinding = FragmentAssistantBinding.inflate(inflater, container, false); 21 | return mBinding.getRoot(); 22 | } 23 | 24 | @Override 25 | public void onDestroyView() { 26 | super.onDestroyView(); 27 | mBinding = null; 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/content/ResultLaunchers.java: -------------------------------------------------------------------------------- 1 | package net.emilla.content; 2 | 3 | import android.content.ActivityNotFoundException; 4 | 5 | import androidx.activity.result.ActivityResultLauncher; 6 | import androidx.annotation.Nullable; 7 | 8 | import net.emilla.R; 9 | import net.emilla.activity.AssistActivity; 10 | import net.emilla.run.MessageFailure; 11 | 12 | public final class ResultLaunchers { 13 | 14 | public static boolean tryLaunch( 15 | AssistActivity act, 16 | ActivityResultLauncher launcher, 17 | @Nullable I input 18 | ) { 19 | try { 20 | launcher.launch(input); 21 | return true; 22 | } catch (ActivityNotFoundException e) { 23 | act.fail(new MessageFailure(act, R.string.error, R.string.error_no_app)); 24 | return false; 25 | } 26 | } 27 | 28 | private ResultLaunchers() {} 29 | 30 | } 31 | -------------------------------------------------------------------------------- /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 net.emilla.activity.AssistActivity; 9 | import net.emilla.math.BitwiseCalculator; 10 | import net.emilla.util.Intents; 11 | 12 | /*internal*/ final class Bits extends CategoryCommand { 13 | 14 | /*internal*/ Bits(AssistActivity act) { 15 | super(act, CoreEntry.BITS, EditorInfo.IME_ACTION_DONE); 16 | } 17 | 18 | @Override 19 | protected Intent makeFilter() { 20 | return Intents.categoryTask(CATEGORY_APP_CALCULATOR); 21 | } 22 | 23 | @Override 24 | protected void run(AssistActivity act, String expression) { 25 | giveText(act, String.valueOf(BitwiseCalculator.compute(expression, CoreEntry.BITS.name))); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/Pause.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | import android.content.Context; 4 | import android.media.AudioManager; 5 | import android.view.inputmethod.EditorInfo; 6 | 7 | import net.emilla.activity.AssistActivity; 8 | import net.emilla.util.MediaControl; 9 | import net.emilla.util.Services; 10 | 11 | /*internal*/ final class Pause extends CoreCommand { 12 | 13 | /*internal*/ Pause(Context ctx) { 14 | super(ctx, CoreEntry.PAUSE, EditorInfo.IME_ACTION_DONE); 15 | } 16 | 17 | @Override 18 | protected void run(AssistActivity act) { 19 | AudioManager am = Services.audio(act); 20 | MediaControl.sendPauseEvent(am); 21 | act.give(a -> {}); 22 | } 23 | 24 | @Override 25 | protected void run(AssistActivity act, String ignored) { 26 | run(act); // Todo: remove this from the interface for non-instructables. 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/Play.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | import android.content.Context; 4 | import android.media.AudioManager; 5 | import android.view.inputmethod.EditorInfo; 6 | 7 | import net.emilla.R; 8 | import net.emilla.activity.AssistActivity; 9 | import net.emilla.util.MediaControl; 10 | import net.emilla.util.Services; 11 | 12 | /*internal*/ final class Play extends CoreCommand { 13 | 14 | /*internal*/ Play(Context ctx) { 15 | super(ctx, CoreEntry.PLAY, EditorInfo.IME_ACTION_GO); 16 | } 17 | 18 | @Override 19 | protected void run(AssistActivity act) { 20 | AudioManager am = Services.audio(act); 21 | MediaControl.sendPlayEvent(am); 22 | act.give(a -> {}); 23 | } 24 | 25 | @Override 26 | protected void run(AssistActivity act, String media) { 27 | throw badCommand(R.string.error_unfinished_feature); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_dial.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/lang/phrase/Dice.kt: -------------------------------------------------------------------------------- 1 | package net.emilla.lang.phrase 2 | 3 | import net.emilla.util.hash1 4 | import java.util.Random 5 | 6 | class Dice(count: Int, val faces: Int) : Comparable { 7 | var count = count @JvmName("count") get 8 | private set 9 | 10 | fun add(count: Int) { 11 | this.count += count 12 | } 13 | 14 | fun roll(rand: Random): Int { 15 | if (faces == 1) return count 16 | 17 | var result = 0 18 | if (count >= 0) repeat(count) { 19 | result += rand.nextInt(faces) + 1 20 | } else repeat(count) { 21 | result -= rand.nextInt(faces) + 1 22 | } 23 | 24 | return result 25 | } 26 | 27 | override fun compareTo(other: Dice) = faces.compareTo(other.faces) 28 | override fun equals(other: Any?) = this === other || other is Dice && faces == other.faces 29 | override fun hashCode() = hash1(faces) 30 | } 31 | -------------------------------------------------------------------------------- /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/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 EXTRA_PING = "ping"; 12 | private static final String EXTRA_CHANNEL = "channel"; 13 | 14 | public PingIntent(Intent intent) { 15 | super(intent); 16 | } 17 | 18 | public PingIntent(Context ctx, Notification ping, String channel) { 19 | super(ctx, PingReceiver.class); 20 | 21 | putExtra(EXTRA_PING, ping); 22 | putExtra(EXTRA_CHANNEL, channel); 23 | } 24 | 25 | public Notification ping() { 26 | return getParcelableExtra(EXTRA_PING); 27 | } 28 | 29 | public PingChannel channel() { 30 | return PingChannel.of(getStringExtra(EXTRA_CHANNEL)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/Info.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.content.pm.PackageManager; 6 | import android.view.inputmethod.EditorInfo; 7 | 8 | import net.emilla.command.app.AppEntry; 9 | import net.emilla.util.Apps; 10 | import net.emilla.util.Intents; 11 | 12 | /*internal*/ final class Info extends OpenCommand { 13 | 14 | public static boolean possible(PackageManager pm) { 15 | return Apps.canDo(pm, Intents.appInfo("")); 16 | } 17 | 18 | /*internal*/ Info(Context ctx) { 19 | super(ctx, CoreEntry.INFO, EditorInfo.IME_ACTION_GO); 20 | } 21 | 22 | @Override 23 | protected Intent defaultIntent() { 24 | return Intents.appInfo(); 25 | } 26 | 27 | @Override 28 | public Intent makeIntent(AppEntry app, PackageManager pm) { 29 | return Intents.appInfo(app.pkg); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /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.Permission; 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 (Permission.PINGS.has(ctx)) { 22 | Pinger.of(ctx, new PingIntent(intent)).ping(); 23 | } else if (DEBUG) { 24 | Log.e(TAG, "Unable to ping due to lack of permission."); 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/sort/SearchItem.java: -------------------------------------------------------------------------------- 1 | package net.emilla.sort; 2 | 3 | import androidx.annotation.CallSuper; 4 | 5 | import net.emilla.lang.Lang; 6 | 7 | public abstract class SearchItem implements Comparable { 8 | 9 | public final String displayName; 10 | /*internal*/ final String mSearchKey; 11 | 12 | protected SearchItem(String displayName) { 13 | this.displayName = displayName; 14 | mSearchKey = Lang.normalize(displayName); 15 | } 16 | 17 | @CallSuper 18 | public /*open*/ boolean contains(CharSequence normalizedSearch) { 19 | return mSearchKey.contains(normalizedSearch); 20 | } 21 | 22 | @Override 23 | public final int compareTo(SearchItem other) { 24 | int cmp = mSearchKey.compareTo(other.mSearchKey); 25 | if (cmp != 0) { 26 | return cmp; 27 | } 28 | 29 | return this.displayName.compareTo(other.displayName); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /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 net.emilla.activity.AssistActivity; 9 | import net.emilla.math.Calculator; 10 | import net.emilla.math.Maths; 11 | import net.emilla.util.Intents; 12 | 13 | /*internal*/ final class Calculate extends CategoryCommand { 14 | 15 | /*internal*/ Calculate(AssistActivity act) { 16 | super(act, CoreEntry.CALCULATE, EditorInfo.IME_ACTION_DONE); 17 | } 18 | 19 | @Override 20 | protected Intent makeFilter() { 21 | return Intents.categoryTask(CATEGORY_APP_CALCULATOR); 22 | } 23 | 24 | @Override 25 | protected void run(AssistActivity act, String expression) { 26 | giveText(act, Maths.prettyNumber(Calculator.compute(expression, CoreEntry.CALCULATE.name))); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/Torch.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | import android.content.Context; 4 | import android.content.pm.PackageManager; 5 | import android.view.inputmethod.EditorInfo; 6 | 7 | import net.emilla.activity.AssistActivity; 8 | import net.emilla.util.Features; 9 | import net.emilla.util.TorchManager; 10 | 11 | /*internal*/ final class Torch extends CoreCommand { 12 | 13 | public static boolean possible(PackageManager pm) { 14 | return Features.torch(pm); 15 | } 16 | 17 | /*internal*/ Torch(Context ctx) { 18 | super(ctx, CoreEntry.TORCH, EditorInfo.IME_ACTION_DONE); 19 | } 20 | 21 | @Override 22 | protected void run(AssistActivity act) { 23 | TorchManager.toggle(act, CoreEntry.TORCH.name); 24 | } 25 | 26 | @Override 27 | protected void run(AssistActivity act, String ignored) { 28 | run(act); // Todo: remove this from the interface for non-instructables. 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /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.INDEX_ID; 4 | import static net.emilla.contact.adapter.ContactCursorAdapter.INDEX_KEY; 5 | 6 | import android.database.Cursor; 7 | import android.net.Uri; 8 | import android.provider.ContactsContract; 9 | 10 | import net.emilla.activity.AssistActivity; 11 | 12 | @FunctionalInterface 13 | public interface ContactCardReceiver extends ContactReceiver { 14 | 15 | @Override 16 | default void useContact(AssistActivity act, Cursor cur) { 17 | long id = cur.getLong(INDEX_ID); 18 | String key = cur.getString(INDEX_KEY); 19 | 20 | provide(act, ContactsContract.Contacts.getLookupUri(id, key)); 21 | } 22 | 23 | /// Provides the receiver with a contact. 24 | /// 25 | /// @param contact is provided to the receiver. 26 | void provide(AssistActivity act, Uri contact); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /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 | = hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) 11 | 12 | @JvmName("phone") 13 | fun PackageManager.hasPhoneFeature(): Boolean 14 | = hasSystemFeature(PackageManager.FEATURE_TELEPHONY) 15 | 16 | @JvmName("sms") 17 | fun PackageManager.hasSmsFeature(): Boolean { 18 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 19 | hasSystemFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING) 20 | } else { 21 | hasSystemFeature(PackageManager.FEATURE_TELEPHONY) 22 | } 23 | } 24 | 25 | @JvmName("torch") 26 | fun hasTorchFeature(pm: PackageManager): Boolean 27 | = pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH) 28 | // todo: it'd be a good idea to test this on a minSdk device. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Emilla 2 | A powerful Android assistant that respects you. 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 cuts you off or tries to know better than you. 9 | - It's based on explicit commands, not mysterious AI. 10 | - It finds what you need and gets out of the way. 11 | - It can't connect to the internet, it won't snoop on you. 12 | - Accessibility is a top priority. 13 | - You can turn off sounds if they're not your thing. 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/util/TokenIterator.java: -------------------------------------------------------------------------------- 1 | package net.emilla.util; 2 | 3 | import java.util.Iterator; 4 | 5 | public final class TokenIterator implements Iterator { 6 | 7 | private final byte[] mText; 8 | private final byte mDelimiter; 9 | private final int mLength; 10 | 11 | private int mPosition = 0; 12 | 13 | public TokenIterator(byte[] bytes, byte delimiter) { 14 | mText = bytes; 15 | mDelimiter = delimiter; 16 | mLength = bytes.length; 17 | } 18 | 19 | @Override 20 | public boolean hasNext() { 21 | return mPosition < mLength; 22 | } 23 | 24 | @Override 25 | public String next() { 26 | int start = mPosition; 27 | int end = start; 28 | 29 | while (end < mLength && mText[end] != mDelimiter) { 30 | ++end; 31 | } 32 | 33 | mPosition = end + 1; 34 | 35 | int length = end - start; 36 | return new String(mText, start, length); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/Roll.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | import android.content.Context; 4 | import android.view.inputmethod.EditorInfo; 5 | 6 | import net.emilla.R; 7 | import net.emilla.activity.AssistActivity; 8 | import net.emilla.lang.Lang; 9 | import net.emilla.lang.phrase.Dices; 10 | 11 | import java.util.Random; 12 | 13 | /*internal*/ final class Roll extends CoreCommand { 14 | 15 | /*internal*/ Roll(Context ctx) { 16 | super(ctx, CoreEntry.ROLL, EditorInfo.IME_ACTION_DONE); 17 | } 18 | 19 | @Override 20 | protected void run(AssistActivity act) { 21 | var rand = new Random(); 22 | giveText(act, rand.nextBoolean() ? R.string.heads : R.string.tails); 23 | } 24 | 25 | @Override 26 | protected void run(AssistActivity act, String roll) { 27 | Dices dices = Lang.dices(roll, CoreEntry.ROLL.name); 28 | var rand = new Random(); 29 | giveText(act, String.valueOf(dices.roll(rand))); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 13 | 14 | -------------------------------------------------------------------------------- /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.INDEX_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, INDEX_NUMBER); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/run/PermissionOffering.java: -------------------------------------------------------------------------------- 1 | package net.emilla.run; 2 | 3 | import android.app.Activity; 4 | import android.os.Build; 5 | 6 | import androidx.annotation.Nullable; 7 | import androidx.annotation.RequiresApi; 8 | 9 | import net.emilla.activity.AssistActivity; 10 | 11 | /// Presents the user with a system permission request. 12 | /// 13 | /// This can only be used when [Activity#shouldShowRequestPermissionRationale(String)] is true for 14 | /// the permission(s) being requested. 15 | @RequiresApi(Build.VERSION_CODES.M) 16 | public final class PermissionOffering implements CommandRun { 17 | 18 | private final String[] mPermissions; 19 | @Nullable 20 | private final Runnable mOnGrant; 21 | 22 | public PermissionOffering(String[] permissions, @Nullable Runnable onGrant) { 23 | mPermissions = permissions; 24 | mOnGrant = onGrant; 25 | } 26 | 27 | @Override 28 | public void run(AssistActivity act) { 29 | act.offerPermissions(mPermissions, mOnGrant); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /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.INDEX_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, INDEX_ADDRESS); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /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/androidTest/java/net/emilla/lang/TestLang.java: -------------------------------------------------------------------------------- 1 | package net.emilla.lang; 2 | 3 | import android.content.res.Configuration; 4 | import android.content.res.Resources; 5 | import android.os.LocaleList; 6 | 7 | import java.util.function.Consumer; 8 | 9 | public final class TestLang { 10 | 11 | public static void withEachLocale(Resources res, Consumer consumer) { 12 | var originalConf = res.getConfiguration(); 13 | LocaleList locales = originalConf.getLocales(); 14 | 15 | consumer.accept(res); 16 | 17 | var displayMetrics = res.getDisplayMetrics(); 18 | int localeCount = locales.size(); 19 | for (int i = 1; i < localeCount; ++i) { 20 | var conf = new Configuration(originalConf); 21 | conf.setLocale(locales.get(i)); 22 | res.updateConfiguration(conf, displayMetrics); 23 | 24 | consumer.accept(res); 25 | } 26 | 27 | res.updateConfiguration(originalConf, displayMetrics); 28 | } 29 | 30 | private TestLang() {} 31 | 32 | } 33 | -------------------------------------------------------------------------------- /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/java/net/emilla/chime/Chimer.java: -------------------------------------------------------------------------------- 1 | package net.emilla.chime; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | import net.emilla.config.SettingVals; 7 | 8 | @FunctionalInterface 9 | public interface Chimer { 10 | 11 | // Preference IDs 12 | String SILENCE = "none"; 13 | String NEBULA = "nebula"; 14 | String REDIAL = "voice_dialer"; 15 | String CUSTOM = "custom"; 16 | 17 | /// The user's preferred chimer for audio feedback. 18 | /// 19 | /// @param prefs used to build the chimer from user settings. 20 | /// @return the user's chosen chimer. 21 | static Chimer of(SharedPreferences prefs) { 22 | return switch (SettingVals.chimerId(prefs)) { 23 | case SILENCE -> new Silence(); 24 | case NEBULA -> new Nebula(); 25 | case REDIAL -> new Redial(); 26 | case CUSTOM -> new Custom(prefs); 27 | default -> throw new IllegalArgumentException(); 28 | }; 29 | } 30 | 31 | void chime(Context ctx, Chime chime); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/res/navigation/navigation_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 19 | 20 | 25 | -------------------------------------------------------------------------------- /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/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 net.emilla.activity.AssistActivity; 9 | import net.emilla.util.Apps; 10 | import net.emilla.util.Intents; 11 | 12 | /*internal*/ final class Navigate extends CategoryCommand { 13 | 14 | public static boolean possible(PackageManager pm) { 15 | return Apps.canDo(pm, Intents.view("geo:")); 16 | } 17 | 18 | /*internal*/ Navigate(AssistActivity act) { 19 | super(act, CoreEntry.NAVIGATE, EditorInfo.IME_ACTION_SEARCH); 20 | } 21 | 22 | @Override 23 | protected Intent makeFilter() { 24 | return Intents.view("geo:"); 25 | } 26 | 27 | @Override 28 | protected void run(AssistActivity act, String location) { 29 | // Todo: location bookmarks, navigate to contacts' addresses 30 | appSucceed(act, Intents.view(Uri.parse("geo:0,0?q=" + location))); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /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.Context; 6 | import android.content.Intent; 7 | import android.content.pm.PackageManager; 8 | import android.net.Uri; 9 | import android.view.inputmethod.EditorInfo; 10 | 11 | import net.emilla.activity.AssistActivity; 12 | import net.emilla.util.Apps; 13 | 14 | /*internal*/ final class Dial extends CoreCommand { 15 | 16 | public static boolean possible(PackageManager pm) { 17 | return Apps.canDo(pm, new Intent(ACTION_DIAL)); 18 | } 19 | 20 | /*internal*/ Dial(Context ctx) { 21 | super(ctx, CoreEntry.DIAL, EditorInfo.IME_ACTION_GO); 22 | } 23 | 24 | @Override 25 | protected void run(AssistActivity act) { 26 | appSucceed(act, new Intent(ACTION_DIAL)); 27 | } 28 | 29 | @Override 30 | protected void run(AssistActivity act, String numberOrPhoneword) { 31 | appSucceed(act, new Intent(ACTION_DIAL).setData(Uri.parse("tel:" + numberOrPhoneword))); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/action/NoAction.java: -------------------------------------------------------------------------------- 1 | package net.emilla.action; 2 | 3 | import static net.emilla.chime.Chime.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/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/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/command/DuplicateParams.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.graphics.drawable.Drawable; 6 | 7 | import androidx.annotation.StringRes; 8 | import androidx.appcompat.content.res.AppCompatResources; 9 | 10 | import net.emilla.R; 11 | import net.emilla.lang.Lang; 12 | 13 | /*internal*/ final class DuplicateParams implements Params { 14 | 15 | @StringRes 16 | private static final int NAME = R.string.command_duplicate; 17 | 18 | /*internal*/ DuplicateParams() {} 19 | 20 | @Override 21 | public String name(Resources res) { 22 | return res.getString(NAME); 23 | } 24 | 25 | @Override 26 | public CharSequence title(Resources res) { 27 | return Lang.colonConcat(res, NAME, R.string.instruction_duplicate); 28 | } 29 | 30 | @Override 31 | public Drawable icon(Context ctx) { 32 | return AppCompatResources.getDrawable(ctx, R.drawable.ic_command); 33 | } 34 | 35 | @Override 36 | public boolean isProperNoun() { 37 | return false; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/cursor/IsWritableDirectory.java: -------------------------------------------------------------------------------- 1 | package net.emilla.cursor; 2 | 3 | import android.database.Cursor; 4 | import android.provider.DocumentsContract.Document; 5 | 6 | public final class IsWritableDirectory extends CursorTester { 7 | 8 | private static final int INDEX_MIME_TYPE = 0; 9 | private static final int INDEX_FLAGS = 1; 10 | 11 | private static final String[] PROJECTION = { 12 | Document.COLUMN_MIME_TYPE, 13 | Document.COLUMN_FLAGS, 14 | }; 15 | 16 | private static final int REQUIRED_FLAGS 17 | = Document.FLAG_DIR_SUPPORTS_CREATE; 18 | private static final int MONITORED_FLAGS 19 | = REQUIRED_FLAGS 20 | | Document.FLAG_PARTIAL; 21 | 22 | public static final IsWritableDirectory INSTANCE = new IsWritableDirectory(); 23 | 24 | private IsWritableDirectory() { 25 | super(PROJECTION); 26 | } 27 | 28 | @Override 29 | public boolean test(Cursor cursor) { 30 | return Document.MIME_TYPE_DIR.equals(cursor.getString(INDEX_MIME_TYPE)) 31 | && (MONITORED_FLAGS & cursor.getInt(INDEX_FLAGS)) == REQUIRED_FLAGS; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /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.content.ActivityNotFoundException; 6 | import android.content.Intent; 7 | import android.content.pm.PackageManager; 8 | 9 | import net.emilla.R; 10 | import net.emilla.activity.AssistActivity; 11 | import net.emilla.exception.EmillaException; 12 | 13 | public final class AppSuccess implements CommandRun { 14 | 15 | private final Intent mIntent; 16 | 17 | public AppSuccess(Intent intent) { 18 | mIntent = intent.addFlags(FLAG_ACTIVITY_NEW_TASK); 19 | } 20 | 21 | @Override 22 | public void run(AssistActivity act) { 23 | PackageManager pm = act.getPackageManager(); 24 | if (mIntent.resolveActivity(pm) != null) { 25 | act.finishAndRemoveTask(); 26 | act.startActivity(mIntent); 27 | } else try { 28 | act.startActivity(mIntent); 29 | act.finishAndRemoveTask(); 30 | } catch (ActivityNotFoundException e) { 31 | throw new EmillaException(R.string.error, R.string.error_no_app); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/web/Website.java: -------------------------------------------------------------------------------- 1 | package net.emilla.web; 2 | 3 | import android.content.Intent; 4 | import android.net.Uri; 5 | 6 | import androidx.annotation.Nullable; 7 | 8 | import net.emilla.util.Intents; 9 | 10 | /*internal*/ final class Website { 11 | 12 | private final String mUrl; 13 | @Nullable 14 | private final SearchEngine mSearchEngine; 15 | 16 | private Website(String url, @Nullable SearchEngine searchEngine) { 17 | mUrl = url; 18 | mSearchEngine = searchEngine; 19 | } 20 | 21 | @Deprecated 22 | /*internal*/ static Website of(String url) { 23 | var engine = SearchEngine.of(url); 24 | return engine != null 25 | ? new Website(engine.rawUrl(), engine) 26 | : new Website(url, null); 27 | } 28 | 29 | /*internal*/ Intent viewIntent(@Nullable String searchQuery) { 30 | return Intents.view( 31 | searchQuery != null 32 | ? mSearchEngine.searchUrl(searchQuery) 33 | : Uri.parse(mUrl) 34 | ); 35 | } 36 | 37 | public boolean hasSearchEngine() { 38 | return mSearchEngine != null; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notifications.xml: -------------------------------------------------------------------------------- 1 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values-notnight/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 17 | 18 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/Time.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | import android.content.Context; 4 | import android.view.inputmethod.EditorInfo; 5 | 6 | import net.emilla.R; 7 | import net.emilla.activity.AssistActivity; 8 | import net.emilla.run.TextGift; 9 | 10 | import java.time.LocalTime; 11 | import java.time.format.DateTimeFormatter; 12 | import java.time.format.FormatStyle; 13 | 14 | /*internal*/ final class Time extends CoreCommand { 15 | 16 | /*internal*/ Time(Context ctx) { 17 | super(ctx, CoreEntry.TIME, EditorInfo.IME_ACTION_DONE); 18 | } 19 | 20 | @Override 21 | protected void run(AssistActivity act) { 22 | var timeNow = LocalTime.now(); 23 | var formatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT); 24 | // todo: also show date info 25 | // todo: configurable format 26 | act.give(new TextGift(act, R.string.local_time, timeNow.format(formatter))); 27 | } 28 | 29 | @Override 30 | protected void run(AssistActivity act, String location) { 31 | throw badCommand(R.string.error_unfinished_feature); 32 | // TODO: locations, time-elapse 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /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 net.emilla.R; 10 | import net.emilla.activity.AssistActivity; 11 | import net.emilla.util.Apps; 12 | import net.emilla.util.Intents; 13 | 14 | /*internal*/ final class Weather extends CategoryCommand { 15 | 16 | public static boolean possible(PackageManager pm) { 17 | return Apps.canDo(pm, Intents.categoryTask(CATEGORY_APP_WEATHER)); 18 | } 19 | 20 | /*internal*/ Weather(AssistActivity act) { 21 | super(act, CoreEntry.WEATHER, EditorInfo.IME_ACTION_GO); 22 | } 23 | 24 | @Override 25 | protected Intent makeFilter() { 26 | return Intents.categoryTask(CATEGORY_APP_WEATHER); 27 | // TODO: figure out what's up with API level stuff here. 28 | } 29 | 30 | @Override 31 | protected void run(AssistActivity act, String location) { 32 | throw badCommand(R.string.error_unfinished_categorical_app_search); // Todo 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/web/SearchEngine.java: -------------------------------------------------------------------------------- 1 | package net.emilla.web; 2 | 3 | import android.net.Uri; 4 | 5 | import androidx.annotation.Nullable; 6 | 7 | /*internal*/ final class SearchEngine { 8 | 9 | private static final String WILDCARD = "%s"; 10 | 11 | private final String mUrlPrefix; 12 | private final String mUrlSuffix; 13 | 14 | private SearchEngine(String urlPrefix, String urlSuffix) { 15 | mUrlPrefix = urlPrefix; 16 | mUrlSuffix = urlSuffix; 17 | } 18 | 19 | @Deprecated @Nullable 20 | /*internal*/ static SearchEngine of(String searchUrl) { 21 | int wildcardPosition = searchUrl.indexOf(WILDCARD); 22 | if (wildcardPosition < 0) { 23 | return null; 24 | } 25 | 26 | return new SearchEngine( 27 | searchUrl.substring(0, wildcardPosition), 28 | searchUrl.substring(wildcardPosition + 2) 29 | ); 30 | } 31 | 32 | public Uri searchUrl(String query) { 33 | return Uri.parse(mUrlPrefix + Uri.encode(query) + mUrlSuffix); 34 | } 35 | 36 | @Deprecated 37 | public String rawUrl() { 38 | return mUrlPrefix + mUrlSuffix; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/lang/date/Weekdays.java: -------------------------------------------------------------------------------- 1 | package net.emilla.lang.date; 2 | 3 | import java.time.DayOfWeek; 4 | import java.util.ArrayList; 5 | import java.util.Calendar; 6 | import java.util.Collection; 7 | import java.util.stream.Collectors; 8 | 9 | public final class Weekdays { 10 | 11 | public static ArrayList calendarArrayList(Collection weekdays) { 12 | return weekdays.stream() 13 | .map(Weekdays::toCalendarConstant) 14 | .collect(Collectors.toCollection(() -> new ArrayList(weekdays.size()))); 15 | } 16 | 17 | private static int toCalendarConstant(DayOfWeek weekday) { 18 | // DayOfWeek starts on Monday while the calendar constants start on Sunday 19 | return switch (weekday) { 20 | case MONDAY -> Calendar.MONDAY; 21 | case TUESDAY -> Calendar.TUESDAY; 22 | case WEDNESDAY -> Calendar.WEDNESDAY; 23 | case THURSDAY -> Calendar.THURSDAY; 24 | case FRIDAY -> Calendar.FRIDAY; 25 | case SATURDAY -> Calendar.SATURDAY; 26 | case SUNDAY -> Calendar.SUNDAY; 27 | }; 28 | } 29 | 30 | private Weekdays() {} 31 | 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/AppCommand.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import android.content.Context; 4 | import android.view.inputmethod.EditorInfo; 5 | 6 | import net.emilla.activity.AssistActivity; 7 | import net.emilla.command.EmillaCommand; 8 | 9 | public /*open*/ class AppCommand extends EmillaCommand { 10 | 11 | @FunctionalInterface 12 | public interface Maker { 13 | 14 | AppCommand make(Context ctx, AppEntry appEntry); 15 | 16 | } 17 | 18 | protected final AppEntry appEntry; 19 | 20 | /*internal*/ AppCommand(Context ctx, AppEntry appEntry) { 21 | this(ctx, appEntry, EditorInfo.IME_ACTION_GO); 22 | } 23 | 24 | /*internal*/ AppCommand(Context ctx, AppEntry appEntry, int imeAction) { 25 | super(ctx, appEntry, imeAction); 26 | 27 | this.appEntry = appEntry; 28 | } 29 | 30 | @Override 31 | protected final void run(AssistActivity act) { 32 | appSucceed(act, this.appEntry.launchIntent()); 33 | } 34 | 35 | @Override 36 | protected /*open*/ void run(AssistActivity act, String ignored) { 37 | run(act); // Todo: remove this from the interface for non-instructables. 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/util/MimeTypes.java: -------------------------------------------------------------------------------- 1 | package net.emilla.util; 2 | 3 | import android.webkit.MimeTypeMap; 4 | 5 | public final class MimeTypes { 6 | 7 | public static final String PREFIX_TEXT = "text/"; 8 | 9 | public static final String ANY_TEXT = PREFIX_TEXT + '*'; 10 | public static final String PLAIN_TEXT = PREFIX_TEXT + "plain"; 11 | public static final String CALENDAR_EVENT = "vnd.android.cursor.dir/event"; 12 | 13 | private static final String EXTENSION_TEXT = ".txt"; 14 | 15 | /// Converts the string to a ".txt" filename if its type isn't a known text format. 16 | public static String textFilename(String filename) { 17 | int dotIndex = filename.lastIndexOf('.'); 18 | 19 | if (dotIndex < 0) { 20 | return filename + EXTENSION_TEXT; 21 | } 22 | 23 | var typeMap = MimeTypeMap.getSingleton(); 24 | String extension = filename.substring(dotIndex + 1); 25 | String mimeType = typeMap.getMimeTypeFromExtension(extension); 26 | 27 | return mimeType != null && mimeType.startsWith(PREFIX_TEXT) 28 | ? filename 29 | : filename + EXTENSION_TEXT; 30 | } 31 | 32 | private MimeTypes() {} 33 | 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/AppSendData.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import android.content.Context; 4 | import android.view.inputmethod.EditorInfo; 5 | 6 | import androidx.annotation.StringRes; 7 | 8 | import net.emilla.R; 9 | import net.emilla.activity.AssistActivity; 10 | import net.emilla.command.DataCommand; 11 | 12 | /*internal open*/ class AppSendData extends AppSend implements DataCommand { 13 | 14 | @StringRes 15 | private final int mHint; 16 | 17 | /*internal*/ AppSendData(Context ctx, AppEntry appEntry) { 18 | this(ctx, appEntry, R.string.data_hint_text); 19 | } 20 | 21 | /*internal*/ AppSendData(Context ctx, AppEntry appEntry, @StringRes int hint) { 22 | super(ctx, appEntry, EditorInfo.IME_ACTION_NEXT); 23 | 24 | mHint = hint; 25 | } 26 | 27 | @Override @StringRes 28 | public final int dataHint() { 29 | return mHint; 30 | } 31 | 32 | @Override 33 | public final void runWithData(AssistActivity act, String message) { 34 | run(act, message); 35 | } 36 | 37 | @Override 38 | public final void runWithData(AssistActivity act, String message, String cont) { 39 | run(act, message + '\n' + cont); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /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 CommandRun { 10 | 11 | private final OnTimeSetListener mTimeSet; 12 | 13 | public TimePickerOffering(OnTimeSetListener timeSet) { 14 | mTimeSet = timeSet; 15 | } 16 | 17 | @Override 18 | public void run(AssistActivity act) { 19 | var dialog = new TimePickerDialog(act, 0, mTimeSet, 12, 0, DateFormat.is24HourFormat(act)); 20 | // TODO: this isn't respecting the LineageOS system 24-hour setting. 21 | // todo: should there be an option for default time to be noon vs. the current time? noon 22 | // seems much more reasonable in all cases tbh. infinitely more predictable—who the heck 23 | // wants to set a timer for right now?! 24 | dialog.setOnCancelListener(dlg -> { 25 | act.onCloseDialog(); // Todo: don't require this. 26 | act.resume(); 27 | }); 28 | act.prepareForDialog(); 29 | dialog.show(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_web.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/action/box/FileSearchAdapter.java: -------------------------------------------------------------------------------- 1 | package net.emilla.action.box; 2 | 3 | import android.content.ContentResolver; 4 | import android.view.LayoutInflater; 5 | 6 | import androidx.annotation.Nullable; 7 | 8 | import net.emilla.file.Folder; 9 | import net.emilla.file.TreeFile; 10 | import net.emilla.sort.ItemSearchAdapter; 11 | 12 | import java.util.function.Consumer; 13 | 14 | public final class FileSearchAdapter extends ItemSearchAdapter { 15 | 16 | private static final TreeFile[] EMPTY_ARRAY = new TreeFile[0]; 17 | 18 | @Nullable 19 | private Folder mFolder; 20 | 21 | public FileSearchAdapter(LayoutInflater inflater, Consumer itemClickAction) { 22 | super(inflater, EMPTY_ARRAY, itemClickAction, TreeFile[]::new); 23 | } 24 | 25 | public boolean loadFolder(ContentResolver cr, Folder folder) { 26 | TreeFile[] textFiles = folder.textFiles(cr); 27 | if (textFiles == null) { 28 | return false; 29 | } 30 | 31 | this.searcher.load(textFiles); 32 | mFolder = folder; 33 | 34 | refresh(); 35 | 36 | return true; 37 | } 38 | 39 | @Nullable 40 | public Folder folder() { 41 | return mFolder; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/Celsius.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | import android.content.Context; 4 | import android.view.inputmethod.EditorInfo; 5 | 6 | import net.emilla.R; 7 | import net.emilla.activity.AssistActivity; 8 | import net.emilla.lang.Lang; 9 | import net.emilla.lang.measure.CelsiusConversion; 10 | import net.emilla.math.Maths; 11 | 12 | /*internal*/ final class Celsius extends CoreCommand { 13 | 14 | /*internal*/ Celsius(Context ctx) { 15 | super(ctx, CoreEntry.CELSIUS, EditorInfo.IME_ACTION_DONE); 16 | } 17 | 18 | @Override 19 | protected void run(AssistActivity act) { 20 | act.offer(a -> {}); 21 | } 22 | 23 | @Override 24 | protected void run(AssistActivity act, String temperature) { 25 | CelsiusConversion celsius = Lang.celsius(temperature, CoreEntry.CELSIUS.name); 26 | 27 | String oldDegrees = Maths.prettyNumber(celsius.degrees); 28 | var res = act.getResources(); 29 | String unit = res.getString(celsius.fromKelvin ? R.string.kelvin : R.string.fahrenheit); 30 | String celsiusDegrees = Maths.prettyNumber(celsius.convert()); 31 | giveText(act, res.getString(R.string.celsius_conversion, oldDegrees, unit, celsiusDegrees)); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /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 | private final AssistActivity mActivity; 14 | private final FilesReceiver mReceiver; 15 | 16 | public MediaFetcher(AssistActivity act, String commandEntry) { 17 | mActivity = act; 18 | mReceiver = new FilesReceiver(act, commandEntry); 19 | } 20 | 21 | @Override @IdRes 22 | public int id() { 23 | return R.id.action_get_media; 24 | } 25 | 26 | @Override @DrawableRes 27 | public int icon() { 28 | return R.drawable.ic_media; 29 | } 30 | 31 | @Override @StringRes 32 | public int label() { 33 | return R.string.action_attach_media; 34 | } 35 | 36 | @Override @StringRes 37 | public int description() { 38 | return R.string.action_desc_attach_media; 39 | } 40 | 41 | @Override 42 | public void perform() { 43 | mActivity.offerMedia(mReceiver); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/Notifications.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.content.pm.PackageManager; 6 | import android.os.Build; 7 | import android.provider.Settings; 8 | import android.view.inputmethod.EditorInfo; 9 | 10 | import androidx.annotation.RequiresApi; 11 | 12 | import net.emilla.command.app.AppEntry; 13 | import net.emilla.util.Apps; 14 | import net.emilla.util.Intents; 15 | 16 | /*internal*/ final class Notifications extends OpenCommand { 17 | 18 | public static boolean possible(PackageManager pm) { 19 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O 20 | && Apps.canDo(pm, new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)); 21 | } 22 | 23 | /*internal*/ Notifications(Context ctx) { 24 | super(ctx, CoreEntry.NOTIFICATIONS, EditorInfo.IME_ACTION_GO); 25 | } 26 | 27 | @Override @RequiresApi(Build.VERSION_CODES.O) 28 | protected Intent defaultIntent() { 29 | return Intents.notificationSettings(); 30 | } 31 | 32 | @Override @RequiresApi(Build.VERSION_CODES.O) 33 | protected Intent makeIntent(AppEntry app, PackageManager pm) { 34 | return Intents.notificationSettings(app.pkg); 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /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.trie.PhraseTree; 9 | import net.emilla.trie.PrefixResult; 10 | 11 | import java.util.function.IntFunction; 12 | 13 | public final class ActionMap> { 14 | 15 | private final PhraseTree mPhraseTree; 16 | private final A mDefaultAction; 17 | 18 | public ActionMap(Resources res, A defaultAction, IntFunction arrayGenerator) { 19 | mPhraseTree = Lang.phraseTree(res, arrayGenerator); 20 | mDefaultAction = defaultAction; 21 | } 22 | 23 | public void put(Resources res, A action, @ArrayRes int names, boolean usesInstruction) { 24 | for (String name : res.getStringArray(names)) { 25 | mPhraseTree.put(name, action, usesInstruction); 26 | } 27 | } 28 | 29 | public Subcommand get(String instruction) { 30 | PrefixResult get = mPhraseTree.get(instruction); 31 | A action = get.value(); 32 | 33 | return action != null 34 | ? new Subcommand(action, get.leftovers) 35 | : new Subcommand(mDefaultAction, instruction); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/Params.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.graphics.drawable.Drawable; 6 | 7 | public interface Params { 8 | 9 | /// The command's name in Title Case. 10 | /// 11 | /// @param res can be used to retrieve the name from string resources. 12 | /// @return the name of the command. 13 | String name(Resources res); 14 | 15 | /// The command's title as it should appear in the assistant's action-bar. Usually, this 16 | /// should be the command name followed by a brief description of what it takes as input. 17 | /// 18 | /// @param res can be used to retrieve the title from string resources. 19 | /// @return the command's slightly detailed title. 20 | CharSequence title(Resources res); 21 | 22 | /// The command's icon for the submit button. 23 | /// 24 | /// @param ctx can be used to retrieve the icon from drawable resources. 25 | /// @return the command's icon drawable. 26 | Drawable icon(Context ctx); 27 | 28 | /// Whether the command should be lowercased mid-sentence. 29 | /// 30 | /// @return true if the command is a common noun, false if the command is a proper noun. 31 | boolean isProperNoun(); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/run/MessageFailure.java: -------------------------------------------------------------------------------- 1 | package net.emilla.run; 2 | 3 | import android.content.Context; 4 | 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.util.Dialogs; 11 | 12 | public final class MessageFailure extends DialogRun { 13 | 14 | public MessageFailure(Context ctx, EmillaException e) { 15 | this(ctx, e.title, e.message); 16 | } 17 | 18 | public MessageFailure(Context ctx, @StringRes int title, @StringRes int msg) { 19 | super(Dialogs.message(ctx, title, msg)); 20 | } 21 | 22 | public MessageFailure(Context ctx, @StringRes int title, CharSequence msg) { 23 | super(Dialogs.message(ctx, title, msg)); 24 | } 25 | 26 | public MessageFailure(Context ctx, CharSequence title, @StringRes int msg) { 27 | super(Dialogs.message(ctx, title, msg)); 28 | } 29 | 30 | public MessageFailure(Context ctx, CharSequence title, CharSequence msg) { 31 | super(Dialogs.message(ctx, title, msg)); 32 | } 33 | 34 | @Override 35 | public void run(AssistActivity act) { 36 | this.dialog.setNeutralButton(R.string.leave, (dlg, which) -> act.cancel()); 37 | super.run(act); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/Fahrenheit.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | import android.content.Context; 4 | import android.view.inputmethod.EditorInfo; 5 | 6 | import net.emilla.R; 7 | import net.emilla.activity.AssistActivity; 8 | import net.emilla.lang.Lang; 9 | import net.emilla.lang.measure.FahrenheitConversion; 10 | import net.emilla.math.Maths; 11 | 12 | /*internal*/ final class Fahrenheit extends CoreCommand { 13 | 14 | /*internal*/ Fahrenheit(Context ctx) { 15 | super(ctx, CoreEntry.FAHRENHEIT, EditorInfo.IME_ACTION_DONE); 16 | } 17 | 18 | @Override 19 | protected void run(AssistActivity act) { 20 | act.offer(a -> {}); 21 | } 22 | 23 | @Override 24 | protected void run(AssistActivity act, String temperature) { 25 | var res = act.getResources(); 26 | 27 | FahrenheitConversion fahrenheit = Lang.fahrenheit(temperature, CoreEntry.FAHRENHEIT.name); 28 | 29 | String oldDegrees = Maths.prettyNumber(fahrenheit.degrees); 30 | String unit = res.getString(fahrenheit.fromKelvin ? R.string.kelvin : R.string.celsius); 31 | String fahrenheitDegrees = Maths.prettyNumber(fahrenheit.convert()); 32 | giveText(act, res.getString(R.string.fahrenheit_conversion, oldDegrees, unit, fahrenheitDegrees)); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /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/action/box/ActionBox.java: -------------------------------------------------------------------------------- 1 | package net.emilla.action.box; 2 | 3 | import androidx.annotation.LayoutRes; 4 | import androidx.appcompat.app.AlertDialog; 5 | import androidx.fragment.app.Fragment; 6 | 7 | import net.emilla.action.InstructyGadget; 8 | import net.emilla.activity.AssistActivity; 9 | import net.emilla.run.CopyGift; 10 | import net.emilla.run.DialogRun; 11 | import net.emilla.run.TextGift; 12 | 13 | public abstract class ActionBox extends Fragment implements InstructyGadget { 14 | 15 | protected ActionBox(@LayoutRes int contentLayout) { 16 | super(contentLayout); 17 | } 18 | 19 | protected static void offerDialog(AssistActivity act, AlertDialog.Builder dlg) { 20 | act.offer(new DialogRun(dlg)); 21 | } 22 | 23 | protected static void giveText(AssistActivity act, CharSequence title, CharSequence text) { 24 | act.give(new TextGift(act, title, text)); 25 | } 26 | 27 | protected static void giveCopy(AssistActivity act, CharSequence text) { 28 | act.give(new CopyGift(text)); 29 | } 30 | 31 | @Override 32 | public final void load(AssistActivity act) { 33 | act.giveActionBox(this); 34 | } 35 | 36 | @Override 37 | public final void unload(AssistActivity act) { 38 | act.removeActionBox(this); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/ping/ChanneledPinger.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 ChanneledPinger extends Pinger { 14 | 15 | private final PingChannel mChannel; 16 | private final Resources mRes; 17 | 18 | /*internal*/ ChanneledPinger(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 (this.pingManager.getNotificationChannel(mChannel.id) == null) { 28 | this.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/action/QuickAction.java: -------------------------------------------------------------------------------- 1 | package net.emilla.action; 2 | 3 | import android.content.res.Resources; 4 | 5 | import androidx.annotation.CallSuper; 6 | import androidx.annotation.DrawableRes; 7 | import androidx.annotation.IdRes; 8 | 9 | import net.emilla.activity.AssistActivity; 10 | 11 | public interface QuickAction extends Gadget { 12 | 13 | // Preference keys 14 | String PREF_NO_COMMAND = "action_no_command"; 15 | String PREF_LONG_SUBMIT = "action_long_submit"; 16 | String PREF_DOUBLE_ASSIST = "action_double_assist"; 17 | String PREF_MENU_KEY = "action_menu"; 18 | 19 | // Action values 20 | String NONE = "none"; 21 | String FLASHLIGHT = "torch"; 22 | String ASSISTANT_SETTINGS = "config"; 23 | String CURSOR_START = "cursor_start"; 24 | String SELECT_ALL = "select_all"; 25 | String PLAY_PAUSE = "play_pause"; 26 | String HELP = "help"; 27 | 28 | @IdRes 29 | int id(); 30 | @DrawableRes 31 | int icon(); 32 | String label(Resources res); 33 | String description(Resources res); 34 | void perform(); 35 | 36 | @Override @CallSuper 37 | default void load(AssistActivity act) { 38 | act.addAction(this); 39 | } 40 | 41 | @Override @CallSuper 42 | default void unload(AssistActivity act) { 43 | act.removeAction(id()); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/chime/Custom.java: -------------------------------------------------------------------------------- 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 | import net.emilla.config.SettingVals; 9 | 10 | /*internal*/ final class Custom implements Chimer { 11 | 12 | private final Uri[] mUris; 13 | 14 | /*internal*/ Custom(SharedPreferences prefs) { 15 | Chime[] chimes = Chime.values(); 16 | int chimeCount = chimes.length; 17 | var uris = new Uri[chimeCount]; 18 | 19 | for (int i = 0; i < chimeCount; ++i) { 20 | uris[i] = SettingVals.customChimeSoundUri(prefs, chimes[i]); 21 | } 22 | 23 | mUris = uris; 24 | } 25 | 26 | @Override 27 | public void chime(Context ctx, Chime chime) { 28 | Uri uri = mUris[chime.ordinal()]; 29 | 30 | MediaPlayer player; 31 | if (uri != null) { 32 | player = MediaPlayer.create(ctx, uri); 33 | if (player == null) { 34 | // URI is broken 35 | player = Nebula.mediaPlayer(ctx, chime); 36 | } 37 | } else { 38 | player = Nebula.mediaPlayer(ctx, chime); 39 | } 40 | 41 | player.setOnCompletionListener(MediaPlayer::release); 42 | player.start(); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/core/Uninstall.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.core; 2 | 3 | import static android.content.Intent.ACTION_UNINSTALL_PACKAGE; 4 | 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.pm.PackageManager; 8 | import android.provider.Settings; 9 | import android.view.inputmethod.EditorInfo; 10 | 11 | import androidx.annotation.Nullable; 12 | 13 | import net.emilla.command.app.AppEntry; 14 | import net.emilla.util.Apps; 15 | import net.emilla.util.Intents; 16 | 17 | /*internal*/ final class Uninstall extends OpenCommand { 18 | 19 | public static boolean possible(PackageManager pm) { 20 | return Apps.canDo(pm, new Intent(ACTION_UNINSTALL_PACKAGE, Apps.packageUri(""))) 21 | // todo: ACTION_UNINSTALL_PACKAGE is deprecated? 22 | || Apps.canDo(pm, Intents.appInfo("")) 23 | || Apps.canDo(pm, new Intent(Settings.ACTION_SETTINGS)); 24 | } 25 | 26 | /*internal*/ Uninstall(Context ctx) { 27 | super(ctx, CoreEntry.UNINSTALL, EditorInfo.IME_ACTION_GO); 28 | } 29 | 30 | @Override @Nullable 31 | protected Intent defaultIntent() { 32 | return null; 33 | } 34 | 35 | @Override 36 | protected Intent makeIntent(AppEntry app, PackageManager pm) { 37 | return Intents.uninstallApp(app.pkg, pm); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /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( 38 | cur.getLong(INDEX_ID), 39 | cur.getString(INDEX_KEY), 40 | cur.getString(INDEX_NAME), 41 | cur.getString(INDEX_PHOTO), 42 | cur.getInt(INDEX_STARRED) != 0 43 | ); 44 | item.setSelected(true); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /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 | import net.emilla.util.Toasts; 11 | 12 | import java.util.List; 13 | 14 | public final class FilesRetriever extends ResultRetriever, FilesReceiver> { 15 | 16 | public FilesRetriever(AssistActivity act) { 17 | super(act, new GetMultipleContents()); 18 | } 19 | 20 | public void retrieve(FilesReceiver receiver, String mimeType) { 21 | if (alreadyHas(receiver)) return; 22 | launch(mimeType); 23 | } 24 | 25 | @Override 26 | protected ResultCallback makeCallback() { 27 | return new FileCallback(); 28 | } 29 | 30 | private /*inner*/ final class FileCallback extends ResultCallback { 31 | 32 | @Override 33 | protected void onActivityResult(List files, FilesReceiver receiver) { 34 | if (files.isEmpty()) { 35 | Toasts.show(FilesRetriever.this.activity, R.string.toast_files_not_selected); 36 | } else { 37 | receiver.provide(files); 38 | } 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/util/Chars.java: -------------------------------------------------------------------------------- 1 | package net.emilla.util; 2 | 3 | public final class Chars { 4 | 5 | public static boolean isNonLineSpace(char ch) { 6 | return switch (ch) { 7 | case '\n', '\r' -> false; 8 | default -> Character.isWhitespace(ch); 9 | }; 10 | } 11 | 12 | public static boolean differentLetters(char a, char b) { 13 | return compareIgnoreCase(a, b) != 0; 14 | } 15 | 16 | public static int compareIgnoreCase(char a, char b) { 17 | if (a != b && Character.toUpperCase(a) != Character.toUpperCase(b)) { 18 | a = Character.toLowerCase(a); 19 | b = Character.toLowerCase(b); 20 | if (a != b) return a - b; 21 | } 22 | 23 | return 0; 24 | } 25 | 26 | public static boolean isSignOrDigit(char ch) { 27 | return isSign(ch) || Character.isDigit(ch); 28 | } 29 | 30 | public static boolean isSignOrNumberChar(char ch) { 31 | return isSign(ch) || isNumberChar(ch); 32 | } 33 | 34 | public static boolean isNumberChar(char ch) { 35 | return ch == '.' || Character.isDigit(ch); 36 | } 37 | 38 | private static boolean isSign(char ch) { 39 | return switch (ch) { 40 | case '+', '-' -> true; 41 | default -> false; 42 | }; 43 | } 44 | 45 | private Chars() {} 46 | 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/sort/ArrayWindow.java: -------------------------------------------------------------------------------- 1 | package net.emilla.sort; 2 | 3 | import net.emilla.struct.IndexedStruct; 4 | import net.emilla.util.Exceptions; 5 | 6 | import java.util.Arrays; 7 | import java.util.stream.Stream; 8 | 9 | public final class ArrayWindow implements IndexedStruct { 10 | 11 | public static ArrayWindow closed(E[] array, int index) { 12 | return new ArrayWindow(array, index, index); 13 | } 14 | 15 | /*internal*/ final E[] mArray; 16 | /*internal*/ final int mStart; 17 | /*internal*/ final int mEnd; 18 | 19 | /*internal*/ ArrayWindow(E[] array, int start, int end) { 20 | mArray = array; 21 | mStart = start; 22 | mEnd = end; 23 | } 24 | 25 | @Override 26 | public E get(int index) { 27 | if (index < 0) { 28 | throw Exceptions.iob(index); 29 | } 30 | 31 | index += mStart; 32 | if (index >= mEnd) { 33 | throw Exceptions.iob(index); 34 | } 35 | 36 | return mArray[index]; 37 | } 38 | 39 | @Override 40 | public int size() { 41 | return mEnd - mStart; 42 | } 43 | 44 | @Override 45 | public boolean isEmpty() { 46 | return mStart == mEnd; 47 | } 48 | 49 | @Override 50 | public Stream stream() { 51 | return Arrays.stream(mArray, mStart, mEnd); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/run/TextGift.java: -------------------------------------------------------------------------------- 1 | package net.emilla.run; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.annotation.StringRes; 6 | 7 | import net.emilla.activity.AssistActivity; 8 | import net.emilla.util.Dialogs; 9 | 10 | public final class TextGift extends DialogRun { 11 | 12 | private final CharSequence mText; 13 | 14 | public TextGift(Context ctx, @StringRes int title, @StringRes int text) { 15 | super(Dialogs.message(ctx, title, text)); 16 | mText = ctx.getString(text); 17 | } 18 | 19 | public TextGift(Context ctx, @StringRes int title, CharSequence text) { 20 | super(Dialogs.message(ctx, title, text)); 21 | mText = text; 22 | } 23 | 24 | public TextGift(Context ctx, CharSequence title, @StringRes int text) { 25 | super(Dialogs.message(ctx, title, text)); 26 | mText = ctx.getString(text); 27 | } 28 | 29 | public TextGift(Context ctx, CharSequence title, CharSequence text) { 30 | super(Dialogs.message(ctx, title, text)); 31 | mText = text; 32 | } 33 | 34 | @Override 35 | public void run(AssistActivity act) { 36 | this.dialog.setNeutralButton(android.R.string.copy, (dlg, which) -> { 37 | act.onCloseDialog(); // Todo: don't require this. 38 | act.give(new CopyGift(mText)); 39 | }); 40 | super.run(act); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/util/Apps.java: -------------------------------------------------------------------------------- 1 | package net.emilla.util; 2 | 3 | import static android.content.Intent.ACTION_MAIN; 4 | import static android.content.Intent.CATEGORY_LAUNCHER; 5 | 6 | import android.content.Intent; 7 | import android.content.pm.PackageManager; 8 | import android.net.Uri; 9 | 10 | import net.emilla.BuildConfig; 11 | import net.emilla.command.app.AppEntry; 12 | 13 | public final class Apps { 14 | 15 | public static final String MY_PKG = BuildConfig.APPLICATION_ID; 16 | 17 | public static String entry(String pkg, String cls) { 18 | return pkg + '/' + cls; 19 | } 20 | 21 | public static boolean canDo(PackageManager pm, Intent intent) { 22 | return pm.resolveActivity(intent, 0) != null; 23 | } 24 | 25 | public static Uri packageUri(String pkg) { 26 | return Uri.parse("package:" + pkg); 27 | } 28 | 29 | public static AppEntry[] launchers(PackageManager pm) { 30 | var launcher = new Intent(ACTION_MAIN).addCategory(CATEGORY_LAUNCHER); 31 | return filter(pm, launcher); 32 | } 33 | 34 | public static AppEntry[] filter(PackageManager pm, Intent filter) { 35 | return pm.queryIntentActivities(filter, 0).stream() 36 | .map(resolveInfo -> AppEntry.from(pm, resolveInfo)) 37 | .sorted() 38 | .toArray(AppEntry[]::new); 39 | } 40 | 41 | private Apps() {} 42 | 43 | } 44 | -------------------------------------------------------------------------------- /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/command/CommandMap.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command; 2 | 3 | import android.content.res.Resources; 4 | 5 | import androidx.annotation.Nullable; 6 | 7 | import net.emilla.activity.AssistActivity; 8 | import net.emilla.lang.Lang; 9 | import net.emilla.trie.PhraseTree; 10 | import net.emilla.trie.PrefixResult; 11 | 12 | public final class CommandMap { 13 | 14 | private final PhraseTree mPhraseTree; 15 | private final CommandYielder mDefaultYielder; 16 | 17 | /*internal*/ CommandMap(Resources res, CommandYielder defaultYielder) { 18 | mPhraseTree = Lang.phraseTree(res, CommandYielder[]::new); 19 | mDefaultYielder = defaultYielder; 20 | } 21 | 22 | /*internal*/ void put(String command, CommandYielder yielder) { 23 | mPhraseTree.put(command, yielder, yielder.usesInstruction()); 24 | } 25 | 26 | @Nullable 27 | public EmillaCommand get(AssistActivity act, String fullCommand) { 28 | PrefixResult get = mPhraseTree.get(fullCommand); 29 | return PrefixResult.toEmillaCommand(act, get); 30 | } 31 | 32 | public EmillaCommand getDefault(AssistActivity act) { 33 | return mDefaultYielder.command(act); 34 | } 35 | 36 | public EmillaCommand getDefault(AssistActivity act, String fullCommand) { 37 | return mDefaultYielder.command(act, fullCommand); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/net/emilla/command/app/AppSearch.java: -------------------------------------------------------------------------------- 1 | package net.emilla.command.app; 2 | 3 | import android.app.SearchManager; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.view.inputmethod.EditorInfo; 7 | 8 | import net.emilla.R; 9 | import net.emilla.activity.AssistActivity; 10 | import net.emilla.util.Intents; 11 | 12 | /*internal*/ final class AppSearch extends AppCommand { 13 | 14 | /*internal*/ AppSearch(Context ctx, AppEntry appEntry) { 15 | super(ctx, appEntry, EditorInfo.IME_ACTION_SEARCH); 16 | } 17 | 18 | @Override 19 | protected void run(AssistActivity act, String query) { 20 | var res = act.getResources(); 21 | 22 | String[] searchAliases = res.getStringArray(R.array.subcmd_search); 23 | String lcQuery = query.toLowerCase(); 24 | Intent search = Intents.searchToApp(this.appEntry.pkg); 25 | for (String alias : searchAliases) { 26 | if (lcQuery.startsWith(alias)) { 27 | // Todo: visual indication that this will be used 28 | query = query.substring(alias.length()).trim(); 29 | if (!query.isEmpty()) search.putExtra(SearchManager.QUERY, query); 30 | appSucceed(act, search); 31 | return; 32 | } 33 | } 34 | appSucceed(act, search.putExtra(SearchManager.QUERY, query)); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /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.util.Dialogs; 12 | import net.emilla.util.Intents; 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 = Intents.appInfo(); 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(dialog(act, permissionName)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /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/chime/Chime.java: -------------------------------------------------------------------------------- 1 | package net.emilla.chime; 2 | 3 | import android.media.ToneGenerator; 4 | 5 | import androidx.annotation.RawRes; 6 | import androidx.annotation.StringRes; 7 | 8 | import net.emilla.R; 9 | 10 | public enum Chime { 11 | START("chime_start", R.string.chime_start, R.raw.nebula_start, ToneGenerator.TONE_PROP_BEEP), 12 | ACT("chime_act", R.string.chime_act, R.raw.nebula_act, ToneGenerator.TONE_PROP_PROMPT), 13 | PEND("chime_pend", R.string.chime_pend, R.raw.nebula_pend, ToneGenerator.TONE_PROP_BEEP), 14 | RESUME("chime_resume", R.string.chime_resume, R.raw.nebula_resume, ToneGenerator.TONE_PROP_BEEP), 15 | EXIT("chime_exit", R.string.chime_exit, R.raw.nebula_exit, ToneGenerator.TONE_PROP_BEEP2), 16 | SUCCEED("chime_succeed", R.string.chime_succeed, R.raw.nebula_succeed, ToneGenerator.TONE_PROP_ACK), 17 | FAIL("chime_fail", R.string.chime_fail, R.raw.nebula_fail, ToneGenerator.TONE_PROP_NACK); 18 | 19 | public final String preferenceKey; 20 | @StringRes 21 | public final int name; 22 | @RawRes 23 | public final int nebulaSound; 24 | public final int redialTone; 25 | 26 | Chime(String preferenceKey, @StringRes int name, @RawRes int nebulaSound, int redialTone) { 27 | this.name = name; 28 | this.nebulaSound = nebulaSound; 29 | this.redialTone = redialTone; 30 | this.preferenceKey = preferenceKey; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /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 | private final AssistActivity mActivity; 14 | private final FilesReceiver mReceiver; 15 | private final String mMimeType; 16 | 17 | public FileFetcher(AssistActivity act, String commandEntry, String mimeType) { 18 | mActivity = act; 19 | mReceiver = new FilesReceiver(act, commandEntry); 20 | mMimeType = mimeType; 21 | } 22 | 23 | @Override @IdRes 24 | public int id() { 25 | return R.id.action_get_files; 26 | } 27 | 28 | @Override @DrawableRes 29 | public int icon() { 30 | return R.drawable.ic_attach; 31 | } 32 | 33 | @Override @StringRes 34 | public int label() { 35 | return R.string.action_attach_files; 36 | } 37 | 38 | @Override @StringRes 39 | public int description() { 40 | return R.string.action_desc_attach_files; 41 | } 42 | 43 | @Override 44 | public void perform() { 45 | mActivity.offerFiles(mReceiver, mMimeType); 46 | // TODO: visual feedback via attachment manager widget 47 | } 48 | } 49 | --------------------------------------------------------------------------------