I have a very simple activity, that redirects the user to the app's Play Store page, when the button is clicked:
public class MyActivity extends AppCompatActivity {
private static final String PLAY_STORE_URI =
"market://details?id=" + BuildConfig.APPLICATION_ID;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_activity);
findViewById(R.id.go_to_play_store).setOnClickListener(this::goToPlayStore);
}
public void goToPlayStore(View view) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(PLAY_STORE_URI));
startActivity(intent);
}
}
Is it possible to write a test to check that the PlayStore is launched when the button is clicked? Better, is it possible to verify it shows the expected page?
I know that by using ActivityMonitors transitions between Activities can be tested. I also know that I can verify the Intents being sent using Espresso Intents. But can I actually check that a user action launches another app?
I would click the button, then use:
intended(allOf(
hasAction(Intent.ACTION_VIEW),
hasData("https://play.google.com/store/apps/...your app...")
))
I would suggest a slightly different question - is it you app's job to verify that? You'd be testing not your own app but Android OS and Google's Play Store App.
The way I've been approaching it is by being explicit about the boundaries, and aware of the possible scenarios.
What I mean by that is, extract the Intent manipulation and invocation logic into a thin service (that's the being explicit about boundaries part) and test your Activity correctly interacts with it (by constructing the Intent appropriately).
The part about possible scenarios is being aware of what can happen. For example what does your app do (if anything) if the user doesn't have Play Store on their phone.
I'm a starting Android developer and would like to know how to achieve this:
1# User is using e.g. Chrome
2# User wants to share the page (meaning: the URL, which is text) into an app
3# User chooses the app from the share menu
4# The app shows only a small notification on top of Chrome (current foreground app, which is the sender of the intent)
So the app would receive the intent from Chrome and do something with it, but wouldn't change the foreground app. It would stay in the background and show only a very small notification to the user that the sharing of the URL into this app actually succeeded. Toast or something like that.
This is basically the way for instance Pocket handles sharing into it.
How can I achieve this? IntentService? I cannot seem to find the correct answer to this. Theme.NoDisplay, Intent, IntentService – I cannot figure out the correct way since I'm not too familiar with Android yet.
Thank you a lot in advance!
Use the NoDisplay theme (update activity declaration in manifest) in the activity that does your sharing work as follows:
android:theme="#android:style/Theme.NoDisplay"
This will prevent the activity from being displayed.
Then once the activity starts, do your sharing task, display the Toast and call finish()
finish your activity onResume(). It will not shows any of Your Activity.
You can do like this:
protected void onResume() {
super.onResume();
finish();
}
I have an app which I want to open, use, and then at the push of a button close. My question is do I need to keep singleton data of the application status in order to make this happen? I mean if I go with the solution of doing finish on resume() that means that I should need to keep a global data that each activity looks at to close out? This is a fine, albeit awkward way to close an app, but I'll go with it unless I hear another way soon.
Thanks.
PS please do not respond with android will decide when and what to do with your app. I'm sorry but I know my user does want to see this app again after they click Finish! And in the end that's what matters and they are not at all interested in what android needs to do with the app nor should they be.
Maybe you can use System.exit(0) if you want to kill your app.
As came up from comments, you need to do it from the main activity. My way (though there might be a cleaner way) is to add a static handler in the main activity:
static Handler handler = new Handler() {
#Override
public void handleMessage(Message msg)
{
System.exit(2);
}
}
and a static method:
static public Handler getExitHandler()
{
return handler;
}
and in each class I obtain this handler and send it a message when I want to exit:
MyMainActivity.getExitHandler().sendEmptyMessage(0);
again, not so clean, but the same about System.exit(2);
Well, there is a reason why everyone says that, and is the same as "you don't close a web page". I don't know what your users might expect, but this is the way Android/iOS works.
I've used, for something like what you want to do, the flag ACTIVITY_CLEAR_TOP (you set the flag on the intent to open the activity which contains the close button), so in this way the activity stack is deleted, only the current activity remaning: so when you do finish(); the app "closes".
Intent intent = new Intent(this, Some.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
You could pop up a dialogue that informs the user: "the app will close in x seconds" and start a timer that calls finish() on the activity when it ends.
For some reason whenever I (try to) start my app the phone decides to launch system settings instead of my "main activity". And yes, I am referring to the "Android system settings", and not something from my app.
This only happens on my phone, and I suppose it probably could be related to the fact that my app had just opened system settings when I decided to re-launch with a new version from Eclipse.
It is possible to start the app from within Eclipse, but when I navigate back from the app it returns to the system settings rather than the home screen, as if the settings activity was started first and then my activity. If I then start the app from the phone all I get is system settings yet again.
The app is listening to the VIEW-action for a specific URL substring, and when I start the app using a matching URL I get the same result as when I start it from Eclipse, app starts, but when I return I return to settings.
I have tried googling for this problem, and all I could find was something about Android saving state when an app gets killed, but without any information on how to reset this state. I have tried uninstalling the app, killing system settings, rebooting the phone, reinstalling, clearing application data.. no luck..
For what it's worth, here's the definition of my main activity from the manifest,
<activity android:name=".HomeActivity" android:label="#string/app_name" android:screenOrientation="portrait" android:clearTaskOnLaunch="true" android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"></action>
<category android:name="android.intent.category.DEFAULT"></category>
<category android:name="android.intent.category.BROWSABLE"></category>
<data android:pathPrefix="/isak-web-mobile/smart/" android:scheme="http" android:host="*"></data>
</intent-filter>
</activity>
And here is the logcat-line from when I try to start my app, nothing about any settings anywhere.
I/ActivityManager( 1301): Starting activity: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=se.opencare.isak/.HomeActivity }
When I launch from Eclipse I also get this line (as one would expect),
I/ActivityManager( 1301): Start proc se.opencare.isak for activity se.opencare.isak/.HomeActivity: pid=23068 uid=10163 gids={3003, 1007, 1015}
If it matters the phone is a HTC Desire Z running 2.2.1.
Currently, this is my HomeActivity,
public class HomeActivity extends Activity {
public static final String TAG = "HomeActivity";
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(TAG, "onActivityResult(" + requestCode + ", " + resultCode + ", " + data + ")");
super.onActivityResult(requestCode, resultCode, data);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate(" + savedInstanceState + ")");
super.onCreate(savedInstanceState);
}
#Override
protected void onDestroy() {
Log.d(TAG, "onDestroy()");
super.onDestroy();
}
#Override
protected void onPause() {
Log.d(TAG, "onPause()");
super.onPause();
}
#Override
protected void onPostCreate(Bundle savedInstanceState) {
Log.d(TAG, "onPostCreate(" + savedInstanceState + ")");
super.onPostCreate(savedInstanceState);
}
#Override
protected void onPostResume() {
Log.d(TAG, "onPostResume()");
super.onPostResume();
}
#Override
protected void onRestart() {
Log.d(TAG, "onRestart()");
super.onRestart();
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
Log.d(TAG, "onRestoreInstanceState(" + savedInstanceState + ")");
super.onRestoreInstanceState(savedInstanceState);
}
#Override
protected void onResume() {
Log.d(TAG, "onResume()");
super.onResume();
}
#Override
protected void onStart() {
Log.d(TAG, "onStart()");
super.onStart();
}
#Override
protected void onStop() {
Log.d(TAG, "onStop()");
super.onStop();
}
#Override
protected void onUserLeaveHint() {
Log.d(TAG, "onUserLeaveHint()");
super.onUserLeaveHint();
}
}
Nothing (of the above) is written to the log.
After reading Blundell's response I have tried changing the launchMode/clearTaskOnLaunch settings, with the following results,
Situation A - I remove launchMode and clearTaskOnLaunch: same problem as before
Situation B - I remove clearTaskOnLaunch and keeps launchMode="singleInstance": another problem
Open app - My HomeActivity is showing
Click on a button to open another activity within my app - it opens as it should
Click on the system back-button - it returns me to system settings
Click on the system back-button again - I am returned to my HomeActivity
Another click on the back-button brings me back to the Android home screen
Situation C - I remove only launchMode and keeps clearTaskOnLaunch: same problem as before
Sitatuion D - I remove clearTaskOnLaunch and set launchMode="singleInstance": same as situation B
You are not calling setContentView() from the HomeActivity (as per the code added by you). And when it is not present, different device behave differently, like on emulator you will see "Application not installed on your phone". Try setting content view.
I've tested this on my phone, but this seems to work correctly here...
What if you try to create a new project, and then just copy the code into it?
It worked for me, maybe it will work for you to...
I suspect it has something to do with your change of version of Eclipse, which might use some different setting from the version you used before, therefore rendering your initial set-up as useless...
My 2Cents
You could look into the launch activity of the Application.
Basically what I think might be happening is, your app is being launched in the same 'Stack' as the settings was, therefore the OS will push the settings to the screen and then load your activity on top of that stack.
You could try adding:
android:launchMode="singleInstance"
into your <activity /> tag of your manifest file. This will start a new stack for your application and only your application
This may fix the issue but there may be another cause and this may cause adverse effects depending on how your app works. Have a look at the documentation for further info:Android:LaunchMode
EDIT
Reading your manifest, why not just remove android:launchMode="singleTop" and let it launch with the default?
Also you are pairing this with:android:clearTaskOnLaunch , because the OS has the settings activity and your activity as the same task it may be clearing it down to the 'root' activity which is the settings and not your app. Is there a reason you have this, remove it and try again?
Please note that I am not an Android developer, but I own an Android phone and think this issue is quite interesting.
To sum it up what I have read about this issue so far here it looks for me that following may be the root cause of this issue:
You use clearTaskOnLaunch, which wipes out all intents/activities/tasks/whatever_buzzword_is_used_for_this when your application starts.
Android then seems to remember permanently the last state of the application in some (what I now will call) global internal state database (GISD for brevity) which cannot be wiped that easily (at least not with uninstalling the application, rebooting, clearing intents, etc.).
Android tries to reopen the application with it's last state. If there is no current activity then this info is taken from the GISD, hence in your case it is "system settings".
There is no way (or it is unknown how) to wipe the state held in the GISD. (Your question is how to do that.)
If this is true this is clearly a phone issue, a major bug in Android, as you then can probably brick applications! All you must do is following:
Somehow provoke a similar thing like clearTaskOnLaunch - which should be possible - for the application you want to brick.
Trick the application to launch to some other application like "System settings" - many applications do this to enable WiFi or similar, but I think some clever people (read: not me) will find a way to do this for virtually any application
Now kill the application in this state.
Android will remember that state then in the GISD and if you try to launch the application from Home screen this will fail in future. Nice.
However even in the absence of a fix hopefully it is possible to bring back the application the same way:
Start your application with an Activity such that it launches. As you write, this works for you.
Apply what clearTaskOnLaunch does. (I really have no idea how, but you as Android developer probably know.)
Let your application launch a third application such that this third application becomes the global state.
Kill your application.
Perhaps timing and sequence is important, so perhaps you have to leave the third application after your application is killed, perhaps you have to pull the battery of your phone at the right time, perhaps you must use some weird ADB stuff to prevent your phone to do the right thing, perhaps you need another sequence of actions or somethign still is missing, whatever, even finding out how to provoke your type of error can be difficult - but thanks to your finding we know there is such a thing.
If you can make Android launch the third application from Home screen instead of your app this would confirm, that you indeed can fix that issue. If this does not work, so it is impossible to prevent Android to invoke system settings instead of your app, then Android certainly has a big problem, as it is likely someone finds a way to permanently brick apps in all those precious phones out there (ab)using that bug.
If the previous step works, however, the last step now is to let this third application launch back into the main screen of your application:
Start your application.
This will launch the third application.
Apply what clearTaskOnLaunch does.
Let the third application launch your application's main screen (or whatever you want)
Kill the third application (and perhaps your application).
Now the global activity state shall be set back to your application again - if everything goes well third application should not be harmed either.
Note that I am not sure this really can be done, as I am not able to test it myself. And having said that all, I agree that we certainly need be a better way to repair this issue!
But interesting would be to see what happens, if your app has the state of the third app and the third app has the state of your app. Does this lead to an endless loop?
Interesting too is, what happens if you deinstall the third app. Does this clean the automatic launch into that application or does it leave you helpless?
As an Android Phone onwer (only), I must say, that's quite a nice mess you have found. And that's why I am highly interested if there is a fix, as doing a factory reset cannot be considered a solution. Read: This is not only a developer problem, it affects Android users as well.
PS: Please forgive me my bad English, the length of this post, not using the right Buzzwords and posting on an issue which I am not an expert of so that my answer is a little bit OT. However I do this with HTH.
i tried coding it, i tried solving it with google and stackoverflow, nothing found :=) so hopefully someone else has a better idea, im not sure if i get everything right:
i have 2 applications: ad (main app) / adfree-pro (just license starts ad app without ads ;P)
so the problem is, i want to have a pro version (with pro icon) in the launcher, which starts the normal-ad app, which is (the normal ad-app) not in the launcher.
i tried removing the ad-app from the launcher (which due to my research should JUST remove it from the launcher)
pkgMgr.setComponentEnabledSetting(new ComponentName(PACKAGE_NAME, PACKAGE_NAME + ".Main"),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
which results to: the icons in the launcher are correct ;) BUT the application can't be found on the phone, launched, started, even not with a launcher pro activity shortcut. it seems to be there (shortcuts can be created) but i crashes with an activity exception when i try to launch it.
02-18 14:38:59.237: ERROR/AndroidRuntime(9941): Caused by: android.content.ActivityNotFoundException: Unable to find explicit activity class {PACKAGE_NAME/PACKAGE_NAME.Main}; have you declared this activity in your AndroidManifest.xml?
which doesnt seem to belong (the error message)
it looks like there has happened more to the application than just simply removed the entry in the launcher.
thanks a lot guys,
every workaround for this situation appreciated :)
best regards :)
You can't have app installed and hide it's launcher icon. The way I'm addressing it with my application which works similar to yours that I don't try to fight icons but instead the app can be launched using ether icon. Obviously you don't have to do in the main (free) app and the code that launches app from your pro icon will look something like the following:
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
// check if main app is installed. If yes then start it
if (appExists()) {
Log.d(TAG, "Started main app from Pro");
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("myapp://com.myapp.main"));
startActivity(intent);
finish();
} else {
// display view with link back to Market
onAppNotExists();
}
}
It's up to you to implement appExists() which is probably some sort of license check
Of course, alternatively you can develop your app's common code as library project and then distribute it in 2 flavors without duplicating the code
BUT the application can't be found on the phone, launched, started,
even not with a launcher pro activity shortcut.
Not application, but activity.
So, if your LAUNCHER activity is BaseActivity, you may create something like BaseFakeActivity (don't forget to set it as LAUNCHER in your manifest instead of your BaseActivity) and which only function is to start your BaseActivity and then finish() itself.
Now you may hide your BaseFakeActivity but you'll still be possible to interact with your BaseActivity.
P.S.: Don't forget to test your app's behaviour after doing things this way ;)