How can I go to recent apps menu and How to select a particular app from the recents by using Espresso Android Instrumentation Test
Since selecting app from recent apps menu, it need controller over device. I think it can't be done with Espresso alone.
But you can achieve this by using android uiautomator.
fun selectAppFromRecentApps(appTitle:String){
mDevice.pressHome()
mDevice.pressRecentApps()
var uiSelector = UiSelector().className("android.widget.ScrollView") //scroll view listing all recent apps
var count = mDevice.findObject(uiSelector).childCount
for (i in 0 until count step 1) {
val child = UiScrollable(uiSelector.childSelector(UiSelector().resourceId("com.android.systemui:id/task_view_bar").instance(i))).getChild(UiSelector().resourceId("com.android.systemui:id/title")) // app framelayout
val text = child.text
if (text == appTitle) {
child.click()
break
}
if(i==count-1){
throw RuntimeException("App : "+ appTitle +" not found in
recent apps")
}
}
}
Unfortunately VIGNESHs answer did not work for me. I expect different devices to have different implementations of the overview view, or probably it's the Android version.
Anyway the following worked for me on different devices:
getInstrumentation().waitForIdleSync();
mDevice.pressHome();
mDevice.pressRecentApps();
mDevice.waitForIdle();
// If the application is listed, there needs to be an element with a content description
// containing the package name
if (!mDevice.wait(Until.hasObject(By.descStartsWith(getTargetContext().getPackageName())), 1000))
{
fail("Overview did not open");
}
// As the app to be tested was the last one opened, we can simply press the button again.
mDevice.pressRecentApps();
// Wait until the activity under test is back.
// If you skip that, you might be to early, doing further tests.
mDevice.wait(Until.hasObject(By.pkg(getTargetContext().getPackageName())), 1000);
getInstrumentation().waitForIdleSync();
Related
When using the OnReceiveContentListener I followed along with the guide here. I also looked at the example project they reference. From my understanding the way it works is that if I enter the code
val editTextOnReceiveContentListener =
androidx.core.view.OnReceiveContentListener { editText, contentInfoCompat ->
val split = contentInfoCompat.partition { it.uri != null }
val uriContent = split.first
val remaining = split.second
/** Process uriContent here... **/
//return remaining
remaining
}
ViewCompat.setOnReceiveContentListener(
myEditText,
arrayOf("image/gif", "image/png"),
editTextOnReceiveContentListener
)
Then when 'remaining' is null the app will NOT show the user "AppName doesn't support image insertion here". And most of the time it seems to be work just as I expect.
However if I use the 'Cold Boot Now' option for starting the emulator (and other times occasionally) then the first gif that is entered will still display that image insertion is not supported here. Everything else seems to work fine except it shows the error. This happens even though the log shows that 'remaining' is null. I tested it on emulator devices Nexus 5 and Pixel 2 for API 29 and 31.
My question is, is this something that I should be concerned about and/or can fix in some way?
I'm trying to develop an android app for the SDK version 30 that (when a button is clicked) starts listening to what apps are opened on the phone. If it detects the user opening Whatsapp, it is supposed to show a LockScreen activity over Whatsapp that makes you answer a math question before being able to use Whatsapp.
I know this can be done as their are apps like QualityTime or Forest that have similar features to restrict you from using certain apps, but I am a newbie when it comes to programming (probably obvious from my code) and feel totally stuck.
I have already figured out how to detect what app the user opened in the last second with code from stack overflow:
public String getCurrentApp() {
String topPackageName = "None";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
UsageStatsManager mUsageStatsManager = (UsageStatsManager) getSystemService("usagestats");
long time = System.currentTimeMillis();
List<UsageStats> stats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - 1000 * 1, time);
// Sort the stats by the last time used
if (stats != null) {
SortedMap<Long, UsageStats> mySortedMap = new TreeMap<Long, UsageStats>();
for (UsageStats usageStats : stats) {
mySortedMap.put(usageStats.getLastTimeUsed(), usageStats);
}
if (!mySortedMap.isEmpty())
{
topPackageName = mySortedMap.get(mySortedMap.lastKey()).getPackageName();
}
}
}
return topPackageName;
}
I have another function that is started when the user clicks the button in my app to "activate" the listening process. This function keeps checking if the user opens Whatsapp and is then supposed to display the Lockscreen activity on top:
public void startListening(View view)
{
System.out.println("Lock activated.");
while (activated) {
String currentlyRunningApp;
currentlyRunningApp = getCurrentApp();
if (currentlyRunningApp.equals("com.whatsapp"))
{
System.out.println("Whatsapp detected. Showing Lockscreen...");
Intent i = new Intent(this,LockScreen.class);
startActivity(i);
}
try {
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
(All of the code I have shown is in my MainActivity btw.)
I have the following permissions granted to my app:
android.permission.PACKAGE_USAGE_STATS (for the getCurrentApp() function)
android.permission.SYSTEM_ALERT_WINDOW (as suggested here)
android.permission.ACTION_MANAGE_OVERLAY_PERMISSION (although I am not sure I even need this one)
My problem is, that instead of showing the Lockscreen activity I created, it only shows a blackscreen for the user. The Lockscreen activity itself works fine if I let the user open it through a button on the mainActivity, so the issue seems to really be that I can not properly show an activity if my app is running in the background and I want to display it on top of Whatsapp.
I have tried to look through similar questions, but all of the posts on here with similar use cases seem to be very old and outdated (i.e. this or this), as the newer versions seem to have way tighter security restrictions.
I also tried to do it with a screen overlay instead of an activity (using this source), but this doesn't even give me a blackscreen - just does nothing...
I am also aware that there are probably better ways to program the whole "listening and checking for whatsapp" part - i.e. with a service instead of a while-loop or something, but I only found out about services while researching this problem and I'd like to fix the blackscreen issue first.
After lots of trial and error I figured out that the issues was indeed caused by a missing permission, but one that I could not find on any stack overflow answer related to black screen problems. On top of that, I believe it's an issue that only occurred because I used a Xiaomi device for testing.
There are currently two separate permissions for displaying screens over other apps that you will need to grant:
Display over other apps, also called Display pop-up window. This is the android.permission.ACTION_MANAGE_OVERLAY_PERMISSION that I wasn't sure was even needed. So to emphasize, I definetly do need this permission.
Display pop-up windows while running in the background. This is the permission I was missing.
After I allowed them both (which you can do under Settings > Apps > Manage Apps > Your App > Other Permissions) everything worked fine.
To direct the user directly to the settings menu where they can allow these permissions, I used the code from this stack overflow answer. This is also where I got the info that it's a xiaomi-specific "issue".
I'm trying to incorporate Pepper's built in Android tablet more in DialogFlow interactions. Particularly, my goal is to open applications installed on the tablet itself for people to use while they're talking with Pepper. I'm aware there is a 'j-tablet-browser' app installed on Pepper's end that can let a person browse the tablet like an ordinary Android device, but I would like to take it one step further and directly launch an Android app, like Amazon's Alexa.
The best solution I can up with is:
User says specific utterance (e.g. "Pepper, open Alexa please")
DialogFlow launches the j-tablet-browser behavior
{
"speak": "Sure, just a second",
"action": "startApp",
"action_parameters": {
"appId": "j-tablet-browser/."
}
}
User navigates the Android menu manually to tap the Alexa icon
My ideal goal is to make the process seamless:
User says specific utterance (e.g. "Pepper, open Alexa please")
DialogFlow launches the Alexa app installed on the Android tablet
Does anyone have an idea how this could be done?
This is quite a broad question so I'll try and focus on the specifics for launching an app with a Dialogflow chatbot. If you don't already have a QiSDK Dialogflow chatbot running on Pepper, there is a good tutorial here which details the full process. If you already have a chatbot implemented I hope the below steps are general enough for you to apply to your project.
This chatbot only returns text results for Pepper to say, so you'll need to make some modifications to allow particular actions to be launched.
Modifying DialogflowDataSource
Step 2 on this page of the tutorial details how to send a text query to Dialogflow and get a text response. You'll want to modify it to return the full reponse object (including actions), not just the text. Define a new function called detectIntentFullResponse for example.
// Change this
return response.queryResult.fulfillmentText
// to this
return response.queryResult
Modifying DialogflowChatbot
Step 2 on this page shows how to implement a QiSDK Chatbot. Add some logic to check for actions in the replyTo function.
var response: DetectIntentResponse? = null
// ...
response = dataSource.detectIntentFullResponse(input, dialogflowSessionId, language)
// ...
return if (reponse.action != null) {
StandardReplyReaction(
ActionReaction(qiContext, response), ReplyPriority.NORMAL
)
} else if (reponse.answer != null) {
StandardReplyReaction(
SimpleSayReaction(qiContext, reponse.answer), ReplyPriority.NORMAL
)
} else {
StandardReplyReaction(
EmptyChatbotReaction(qiContext), ReplyPriority.FALLBACK
)
}
Now make a new Class, ActionReaction. Note that the below is incomplete, but should serve as an example of how you can determine which action to run (if you want others). Look at SimpleSayReaction for more implementation details.
class ActionReaction internal constructor(context: QiContext, private val response: DetectIntentResponse) :
BaseChatbotReaction(context) {
override fun runWith(speechEngine: SpeechEngine) {
if (response.action == "launch-app") {
var appID = response.parameters.app.toString()
// launch app at appID
}
}
}
As for launching the app, various approaches are detailed in other questions, such as here. It is possible to extend this approach to do other actions, such as running or retrieving online data.
I'm having a problem starting an activity in a downloaded feature module when it's published to the play store. It always crashes on setContentView() in the downloaded modules activity.
java.lang.RuntimeException: Unable to start activity ComponentInfo{xxx/xxxActivity}: android.content.res.Resources$NotFoundException: Resource ID #0x7e080000
Caused by: android.content.res.Resources$NotFoundException: Resource ID #0x7e080000
at android.content.res.ResourcesImpl.getValue(ResourcesImpl.java:227)
at android.content.res.Resources.loadXmlResourceParser(Resources.java:2149)
at android.content.res.Resources.getLayout(Resources.java:1158)
at android.view.LayoutInflater.inflate(LayoutInflater.java:421)
at android.view.LayoutInflater.inflate(LayoutInflater.java:374)
at androidx.appcompat.app.AppCompatDelegateImpl.setContentView(AppCompatDelegateImpl.java:469)
at androidx.appcompat.app.AppCompatActivity.setContentView(AppCompatActivity.java:140)
The really strange part is that if I publish a new version of the app (only change is versionCode) to play store and update the app everything works perfectly.
When I uninstall the app and install it again the crash returns.
my Application is inheriting SplitCompatApplication() and just to be sure I've since tried to add:
override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(newBase)
SplitCompat.install(this)
}
to the activty in the feature module and disabled proguard to make sure nothing is removed during minify
My SplitInstallStateUpdatedListener
private val listener = SplitInstallStateUpdatedListener { state ->
val multiInstall = state.moduleNames().size > 1
state.moduleNames().forEach { name ->
// Handle changes in state.
when (state.status()) {
SplitInstallSessionStatus.DOWNLOADING -> {
// In order to see this, the application has to be uploaded to the Play Store.
displayLoadingState(state, "Laddar ner $name")
}
SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION -> {
/*
This may occur when attempting to download a sufficiently large module.
In order to see this, the application has to be uploaded to the Play Store.
Then features can be requested until the confirmation path is triggered.
*/
startIntentSender(state.resolutionIntent()?.intentSender, null, 0, 0, 0)
}
SplitInstallSessionStatus.INSTALLED -> {
if(toInstall.isNotEmpty() && toInstall.contains(name)) {
toInstall.remove(name)
}
if(toInstall.isEmpty()) {
// Updates the app’s context with the code and resources of the
// installed module. (should only be for instant apps but tried it anyway, no change)
SplitInstallHelper.updateAppInfo(applicationContext)
Handler().post {
viewModel.goToOverview()
}
}
}
SplitInstallSessionStatus.INSTALLING -> displayLoadingState(state, "Installerar $name")
SplitInstallSessionStatus.FAILED -> {
toastAndLog("Error: ${state.errorCode()} for module ${state.moduleNames()}")
}
}
}
}
This code downloads modules depending on user claims and starts an activity in the base app
The downloaded modules activity is then started from a BottomSheetDialogFragment like this:
xxx.setOnClickListener(view -> {
Intent intent = new Intent();
String packageName = Constants.MODULE_BASEPACKAGE + "." + Constants.MODULE_XXXXX;
intent.setClassName(getActivity().getPackageName(),packageName + ".XxxxxActivity" );
ParcelUuid parcelUuid = new ParcelUuid(UUID.randomUUID());
intent.putExtra("uuid", parcelUuid);
startActivity(intent);
dismiss();
});
I'm all out of ideas about what to try next. It seems like it's something that doesn't update the resource list until an update is installed and a restart of the app is not enough, or am I just missing something simple?
You can always access the resources from the main project inside the dynamic module, so you could just put your resources for the dynamic module in the main app, and then use the R.java from the main App.
However, the proper way to open these resources is to use SplitCompat.install(this) inside the dynamic delivered activity
This seems to have been a bug in com.android.tools.build:gradle:3.2.1
When I upgraded to 3.3.0 the problem resolved itself.
Hopefully it might help someone else who has this problem...
I had an exactly same problem; fresh install crashes with Resources$NotFoundException, but subsequent upgrade works OK (the dynamic module is not downloaded again). But my case was slightly different, because instead of starting an Activity in the dynamic module, I wanted to load a Fragment through Navigation. In that case, I should have just navigated and let Navigation do its thing without manually checking the module was loaded or not (refer to https://developer.android.com/guide/navigation/navigation-dynamic for more info).
// Just navigate without calling splitInstallManager.installedModules.contains()
findNavController().navigate(DynamicDeliveryDirections.actionDynamicFragment())
If you want to start an Activity, you do need to check whether the module is loaded or not, as you are already doing. I suggest you take a look at Google's example, which does exactly what you are trying to do.
https://codelabs.developers.google.com/codelabs/on-demand-dynamic-delivery/index.html?index=..%2F..index#1
As for my case, I had to make sure the package names were correct. For example, if the main module's package name is com.example.foo and dynamic module is com.example.foo.dynamic_activity, then starting the Activity in the dynamic module would look like the following.
Intent().setClassName(
"com.example.foo",
"com.example.foo.dynamic_activity.DynamicActivity"
).also {
startActivity(it)
}
I don't know why it works, but for me using AppCompatActivity solves this problem
My Goal is here is to set my app as default launcher on Huawei devices.
1 - Explanations:
1.1 - Current situation:
I am already able to:
Check if my app is the default launcher
Display the 'launcher picker' (with the 'use once' / 'always' choice)
This all works fine.. except on Huawei devices!
From my point of view, Huawei's Android flavor does not properly 'honor' the "ACTION_MANAGE_DEFAULT_APPS_SETTINGS" intent action contract.
// this displays the list of default apps on all tested devices, except on Huawei devices!
// instead, it does display apps permissions, app links and apps'advanced settings
intent.setAction(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS);
activity.startActivity(intent);
As a B Plan, I am able to display the 'Applications & Notifications' settings 'page' using this:
String packageName = "com.android.settings";
String className = "Settings$AppAndNotificationDashboardActivity";
intent.setClassName(packageName, packageName + "." + className);
activity.startActivity(intent);
So the user can navigate from there, pressing this sequence of menu items:
-> Advanced Parameters ( expandable menu item : not present on tablet, and not sure it's present on phone)
-> Default Apps
-> Default Launcher
This requires 2 or 3 steps that I would like to avoid.
1.2 - This can be improved!
I found out that when the "-> Default Apps" menu item is selected, a (com.android.settings, .SubSettings) Intent (with extra) is launched but I was not able to make this works (permission denial).
But I installed Nova Launcher and it turns out it's able to display the "-> Default Apps" settings page on Huawei devices!
So the user land on a page where she/he only has to tap on "-> Default Launcher" then choose a default launcher: much easier.
2 - Questions:
As I think it's just not possible to display the 'Lancher Picker' on Huawei devices, here is my question:
How can I display the "-> Default Apps" settings page (image down here) on Huawei devices (like Nova Launcher does)?
Are they using another intent action on Huawei devices?
Thanks beforehand your help.
Yes on Huawei devices, Nova uses a different intent to open to the correct screen. I likely found this by using apktool on the Settings.apk pulled from a Huawei device and looking at the AndroidManifest.
Note that "com.android" is always a code smell as it means it's not part of the public API. Also this isn't even really "com.android" as it doesn't exist on AOSP and com.android.settings.PREFERRED_SETTINGS is purely a Huawei invention. It's very likely that some Huawei devices won't have this at all. It's also possible that in the future this intent might continue to work but not do what it currently does. So handle it carefully.
/* Some Huawei devices don't let us reset normally, handle it by opening preferred apps */
Intent preferredApps = new Intent("com.android.settings.PREFERRED_SETTINGS");
preferredApps.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
if (pm.resolveActivity(preferredApps, 0) != null) {
context.startActivity(preferredApps);
} else {
...
}
In fact, the accepted answer is not 100% correct, because it opens a general default apps chooser activity.
It works, but it's better to bring user right to the launcher chooser activity — it's com.google.android.permissioncontroller/com.android.packageinstaller.role.ui.HomeSettingsActivity (at least for the Android 10 Huawei Honors).
So, the correct code snippet is:
Intent()
.apply {
component = ComponentName("com.google.android.permissioncontroller", "com.android.packageinstaller.role.ui.HomeSettingsActivity")
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
}
.takeIf {
packageManager.resolveActivity(it, 0) != null
}
?.let(context::startActivity)