Register hardware keyboard shortcuts in Android's help menu - android

Android supports external hardware keyboards as input, which may send keyboard shortcuts (e.g. Ctrl+A) in which there are some ways to program my app to accept shortcuts from external keyboards (e.g. like this: Custom keyboard shortcuts).
I came across this keyboard shortcut "help" screen in Android (activated by pressing OS + /) (see picture below)
and noticed some apps have some keyboard shortcuts directly registered within the system shortcuts help screen(these screens are not provided by the app, they can be found by pressing OS + /).
So far, the apps that I see that have this feature are:
Samsung Internet
Samsung One UI Home
Google Chrome
Chromium-based browsers (e.g. Brave)
Google Docs, Sheets, Slides
I cannot find any way to register my keyboard shortcuts in my app programmatically such that they will be recognised by the system. How can I possibly implement it in my app?
I do know iOS/iPadOS has such a feature.
Perhaps Android does have such a feature too? It seems like it would be quite useful to app developers and users, not sure why it's not documented at all in Android Developer documentation.
Thanks.

This feature is not documented in the Android Developer website, so I had to search around.
Since it is supported by AOSP, this is not a proprietary method/function by Google. No root is required on your users' devices.
Since this feature is available in Chromium-based browsers, I decided to check up the open-source Chromium Android source code (a mirror on GitHub is available here by someone: https://github.com/kuoruan/Chromium-Android) and I have discovered how this feature is implemented.
The relevant lines are here:
https://github.com/kuoruan/Chromium-Android/blob/29ba2966ff145c9cb2492a971f9c03f879c5b9c9/app/src/main/java/org/chromium/chrome/browser/KeyboardShortcuts.java#L117
https://github.com/kuoruan/Chromium-Android/blob/29ba2966ff145c9cb2492a971f9c03f879c5b9c9/app/src/main/java/org/chromium/chrome/browser/ChromeTabbedActivity.java#L2135
In any Activity, you can override the method onProvideKeyboardShortcuts.It provides 3 parameters: data, menu, and deviceId, in which we only need data here.
Here is the method signature:
fun onProvideKeyboardShortcuts(
data: MutableList<KeyboardShortcutGroup>?,
menu: Menu?,
deviceId: Int
)
This only works on API 24 (Android Nougat) and above.
data is a mutable list of KeyboardShortcutGroup, which is a group of keyboard shortcuts.
So, you can have multiple groups containing multiple shortcuts, useful for grouping the shortcuts for your users.
To register your shortcuts,
Create a KeyboardShortcutGroup. This is required to store and group your keyboard shortcuts together. The class accepts a CharSequence as a parameter for the name of the group, so you have to use getString(R.string.your_resource) for string resources.
val keyboardShortcutGroup = KeyboardShortcutGroup(/* the name of your group here: */ "Test Group")
Define your keyboard shortcuts. Keyboard shortcuts are defined using KeyboardShortcutInfo objects, which accepts 3 parameters in it's constructor - a label, the key(e.g. A/ Z/ 8/ 9), and the modifier key. A single keyboard shortcut is created this way:
KeyboardShortcutInfo("Shortcut One" /*label*/, KeyEvent.KEYCODE_Z/*key*/, KeyEvent.META_ALT_ON/*modifier*/)
You need to pass in key codes into the constructor for the key and the modifier key.
The key and modifier key must be passed in seperately.
Here are the list of available key codes:
https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_0 (See those prefixed with KEYCODE)
Based on testing, the modifier keys which work are:
Ctrl META_CTRL_ON
Alt META_ALT_ON
Shift META_SHIFT_ON
Super/OS/Meta (see https://android.stackexchange.com/a/243218/307843) META_META_ON
Function META_FUNCTION_ON
Left/Right keys for all of these are also available(i.e. META_ALT_LEFT_ON)
META_SYM_ON may work(have not tested yet), but Caps Lock doesn't work.
To combine modifier keys,
in Java, use the bitwise operator:
KeyEvent.META_ALT_LEFT_ON | KeyEvent.META_CTRL_ON
in Kotlin, use the or operator:
KeyEvent.META_ALT_LEFT_ON or KeyEvent.META_CTRL_ON
To this KeyboardShortcutGroup, add shortcuts using addItem.
keyboardShortcutGroup.addItem(shortcut /* <-- the shortcut created earlier */)
Add your KeyboardShortcutGroup to data
data.add(keyboardShortcutGroup)
Your code should look like this:
override fun onProvideKeyboardShortcuts(
data: MutableList<KeyboardShortcutGroup>?,
menu: Menu?,
deviceId: Int
) {
super.onProvideKeyboardShortcuts(data, menu, deviceId)
// Requires API 24
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val keyboardShortcutGroup = KeyboardShortcutGroup("Test Group")
keyboardShortcutGroup.addItem(KeyboardShortcutInfo("Shortcut One", KeyEvent.KEYCODE_Z, KeyEvent.META_ALT_ON))
data?.add(keyboardShortcutGroup)
}
}
You should see the end result:
You can use this to inform users of available shortcuts. Note that this does not implement them, it merely adds a listing to the Android shortcut menu.

Related

Detect if Material You is present in device

So, my boss asked me to analyze how to implement Material You colors starting from Android 12. I've been researching for it and every blog of every page about "how to do it" tells you that, you need to hold down at home screen, then go to "Wallpapers & Style" and change your wallpaper. There is an issue, though.
I remember that while Android 12 was in Beta, it was supporting Material You colors. However (I assume) after the official release, this support has been removed, because I am unable to find the option. Here is what it looks like when I hold down while at home screen:
It says "Wallpapers" and when I click on it, it does not open a menu called Wallpaper & style, it just redirects to Live Wallpapers. I've unable to find the wallpaper colors option on the official Android 12 release. However, it is present on the upcoming API 32 (Android 13 I believe) emulator.
Upon researching a little bit, I've found out that the system generates the wallpaper colors under the system resources such as system_accent1_600 etc. which are available starting from API 31 (more info here). This does work when I use an API 32 emulator which is in beta, but it defaults to something else (a shade of blue on accent colors, and shades of gray on neutral, a.k.a background colors) that I haven't figured out where from on an API 31 official emulator.
Here is the question:
Is Material You colors officially supported starting from Android 12 (API 31)? If yes, then why am I not able to find it on Android's official emulator?
Also, is it possible to detect if the device supports Material You colors with different options?
I think this is what you are looking for:
public static boolean isDynamicColorAvailable()
{
return VERSION.SDK_INT >= VERSION_CODES.S && DYNAMIC_COLOR_SUPPORTED_MANUFACTURERS.contains(Build.MANUFACTURER.toLowerCase());
}
Found this in DynamicColors class in com.google.android.material.color
You can simply call it like this:
boolean isdca = DynamicColors.isDynamicColorAvailable();
I hope this answers the last part.
1. Is Material You colors officially supported starting from Android 12 (API 31)?
Yes! But it based how the ROM implements. If you using Pixel, the you can change the color via the built wallpaper app. But if you're using AOSP, sine there is not an official way in UI to user to change it.
Check out this doc: https://gist.github.com/zhaofengli/232f5a3d33113871ad61491629886084
2. If yes, then why am I not able to find it on Android's official emulator?
It looks like Google removed it from the mirror. The previous mirror had this feature.
3. Also, is it possible to detect if the device supports Material You colors?
Since Android 12 supports Material You officially, so you can just check the api version simply.
But, according to the second point, some systems still don't support it, so you can use the method com.google.android.material.color.DynamicColors#isDynamicColorAvailable. This is the definitive method used by Material Design, depending on the SDK version and phone manufacturer.
https://cs.github.com/material-components/material-components-android/blob/2ae3ca42985722900f53de9d9a1ef61c143767eb/lib/java/com/google/android/material/color/DynamicColors.java#L279-L289
4. What is the correct way to implement Material You?
XML way: Follow the official doc: https://m3.material.io/libraries/mdc-android/color-theming
Programmatically way:
Check out my app's code:
val Context.colorOnPrimary: Int
get() {
return when {
DynamicColors.isDynamicColorAvailable() -> {
getColorFromAttr(R.attr.colorOnPrimary)
}
isNight() || !supportNight() -> {
ContextCompat.getColor(this, R.color.md_theme_dark_onPrimary)
}
else -> {
ContextCompat.getColor(this, R.color.md_theme_light_onPrimary)
}
}
}
#ColorInt
fun Context.getColorFromAttr(
#AttrRes attrColor: Int,
typedValue: TypedValue = TypedValue(),
resolveRefs: Boolean = true
): Int {
theme.resolveAttribute(attrColor, typedValue, resolveRefs)
return typedValue.data
}

Highlight the Menu Item found in Search Result of Android Settings App

Inside Android core Settings App, when we search anything in the search bar, then the result of that search is highlighted automatically.
For e.g. please see below image where I highlighted my application name in the Notification Access section.
I achieved this by following code:
val intent = Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS").apply {
val app = "${application.packageName}/${NotificationListener::class.java.name}" //Here NotificationListener is a service name
val fragmentKey = ":settings:fragment_args_key"
val showFragmentKey = ":settings:show_fragment_args"
putExtra(fragmentKey, app)
putExtra(showFragmentKey, Bundle().apply { putString(fragmentKey, app) })
}
I referred to the Solution on this LINK
However, I want to highlight my application name in the Usage Access Setting.
i.e. when I open the intent for Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS) then inside the list of apps, my app name must be highlighted.
However the above logic/approach is not working for Usage Access Screen.
My application opens the intent but does not highlight my app name there.
I did a detailed research but I am still facing challenge.
Hence, I request you all to please assist.
For ACTION_USAGE_ACCESS_SETTINGS , I can't find one. Have you seen it anywhere? If so, please show me on which app. I've requested about this here:
https://issuetracker.google.com/issues/165311852
https://issuetracker.google.com/issues/160303945
And one in general, for all settings:
https://issuetracker.google.com/issues/163176781
As for notification-access, check here: stackoverflow.com/a/63914445/878126

R shiny mobile app: prevent PullToRefresh

Context: I am developing a mobile Shiny app using the shinyMobile package, which is a wrapper for the famous framework7 HTML template.
In my app, the user has to make a selection of attributes on a first tab using multiple dropdown lists, and then, on the other tabs, some output is produced. Each tab requires the user to scroll up and down to access all the content and in this process, very often the 'pull to refresh' feature is triggered.
This is really annoying, because the entire attribute selection and output are lost, and the user has to start over from scratch.
What I tried: based on this SO thread which pointed me to this Google developer page, I tried setting the CSS overscroll-behavior property to contain with: body {overscroll-behavior-y: contain;}. PROBLEM: It does not work for me! (tested on Chrome Android)
Minimal reproducible example:
default app, deployed here
library(shiny);library(shinyMobile)
shiny::shinyApp(
ui = f7Page(
f7Card(
h5('try to pull to refresh. normally it should work.')
)
),
server = function(input, output) {}
)
Supposedly fixed app, deployed here
library(shiny);library(shinyMobile)
shiny::shinyApp(
ui = f7Page(
tags$style(type='text/css', '.body{overscroll-behavior-y: contain;}'),
f7Card(
h5('try to pull to refresh. Normally it should not work.')
)
),
server = function(input, output) {}
)
Hope you guys can reproduce my issue and identify what is amiss!!!
You might want to change your css part to: html,body{overscroll-behavior-y: contain;}, see https://stackoverflow.com/a/42509310/3502164.
Then it works for me on my mobile (android chrome).
Reproducible example:
library(shiny)
library(shinyMobile)
app <- shiny::shinyApp(
ui = f7Page(
tags$style(type='text/css', 'html,body{overscroll-behavior-y: contain;}'),
f7Card(
h5('try to pull to refresh. Normally it should not work.')
)
),
server = function(input, output) {}
)
# use host config to access app from mobiles in the same network
shiny::runApp(appDir = app, host = '0.0.0.0')

QML mobile app with context menus

I want to create a mobile app (with possible desktop use) that uses a context menu and has as close to a native look and feel as possible for both Android and iOS. (This is my first foray into both Qt and QML.)
I figured out how to create a Menu and call myMenu.popup() to show the context menu. And in Android this context menu looks very similar to a native android context menu. This context menu also looks native on the desktop. The problem comes with iOS.
iOS has a similar concept to context menus called actionsheets. Examples. But the contextMenu looks like a windows context menu (right click menu) floating on the window.
tl;dr;
Is there a way to get the Menu in qml to look similar to iOS actionsheets when run on a iOS device? I have searched for hours today and can't find anything.
code:
The Menu code is mostly copied from the Qt docs just to see how things look and work
Menu
{
id: myContextMenu
title: "Edit"
MenuItem {
text: "Cut"
onTriggered: {console.log("cut")}
}
MenuItem {
text: "Copy"
onTriggered: {console.log("copy")}
}
MenuItem {
text: "Paste"
onTriggered: {console.log("paste")}
}
MenuSeparator { }
Menu {
title: "More Stuff"
MenuItem {
text: "Do Nothing"
}
}
}
MouseArea {
id: longPressArea
anchors.fill: text
onClicked: {
myContextMenu.popup()
}
}
Summarizing the comments above: No, not in the current version of Qt, unless you roll your own in QML.
Quick Controls uses one of [native, QWidget, QML] implementations, whichever is found first. You can read the source to see that there is no native implementation: grep for createPlatformMenu() in Qt/../Src/qtbase/src/plugins/platforms/ios. Thats where the adaptors to native widgets are.
Another answer is: you could contribute by creating the adaptor to the native widget for iOS (if you are an iOS and C++ programmer.) Also assuming that a UIActionSheet is the proper widget to adapt (it seems so.)
I suppose your concern is that a centered menu (instead of a native one that animatedly slides onto the screen like a drawer, the feel) doesn't meet the HIG (or that the style/look is wrong.) Thats a moving target. The iOS8 documentation under showInView seems to say a centered popup menu is an option (at least on iPad, its unclear whether it would work on a phone.) And its fuzzy what the store would reject.
Isn't that an intended benefit of QML: you could provide different skins for tablet and phone?
With Qt Labs, since Qt5.8, you can get native looking menus on macOS, iOS, Android and Linux (gtk+). Haven't tried it myself yet, but you can take a look:
https://doc.qt.io/qt-5.12/qml-qt-labs-platform-menu.html
You need to import Qt.labs.platform 1.1, link against widgets with QT += widgets and use a QApplication instead of QGuiApplication.
For comparison, here is also the stable menu from qml which does not try to look native.
https://doc.qt.io/qt-5.12/qml-qtquick-controls2-menu.html

Detecting alt keypresses in Android app

I want to create keyboard shortcuts in my Android app such that, say, alt-t runs a certain command. I can't seem to figure out how to detect the presence of the alt modifier in the emulator, though.
I've overridden onKeyDown() in my app to look like the following (Scala):
override def onKeyDown(keycode:Int, event:KeyEvent) = keycode match {
case KeyEvent.KEYCODE_B =>
StatisticsService.map(_.sayBatteryLevel())
true
case KeyEvent.KEYCODE_D =>
StatisticsService.map(_.sayDate())
true
case KeyEvent.KEYCODE_S =>
StatisticsService.map(_.saySignalStrengths())
true
case KeyEvent.KEYCODE_T =>
StatisticsService.map(_.sayTime())
true
case _ => super.onKeyDown(keycode, event)
}
That of course matches the plain keys just fine, but not alt-b, alt-t, etc. How can I change the above to match the given alt-modified bindings?
I've searched Google and have tried using event.isAltPressed, but this doesn't work. I've also logged the results of the keypresses, and have noticed that the alt key isn't at all picked up. That is, simply pressing alt does nothing, and pressing alt-t produces identical logs to just t.
This is being tested in the emulator if that makes a difference.
Edit: Not sure how to respond to comments left on my question, but I'm aware that not all devices have alt keys, but that's not an issue here because I'm coding for a custom device that does. The question isn't "what's the most generic way to do this," but "how do I match alt keybindings when the documented methods don't work and no explanation seems to be given in Google's own docs?"
Thanks.

Categories

Resources