├── 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 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/notes_toolbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 super Resources> 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 |
4 |
5 |
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 |
--------------------------------------------------------------------------------