I am trying to write an utility method, that would be able to start activity (belonging to current application) marked as "android.intent.action.MAIN". The utility method should not accept any parameters.
Desired code:
public void startMainActivity(Context context) {
...
}
Manifest:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Any ideas?
This works since API Level 3 (Android 1.5):
private void startMainActivity(Context context) throws NameNotFoundException {
PackageManager pm = context.getPackageManager();
Intent intent = pm.getLaunchIntentForPackage(context.getPackageName());
context.startActivity(intent);
}
We used the nice solution of alex2k8 for a while until discovering it was not working on all devices on released version downloaded from Google Play.
Unfortunately the system didn't:
throw any exception
log the cause of the error
We used following workaround to solve it:
protected void startMainActivityWithWorkaround() throws NameNotFoundException, ActivityNotFoundException {
final String packageName = getPackageName();
final Intent launchIntent = getPackageManager().getLaunchIntentForPackage(packageName);
if (launchIntent == null) {
Log.e(LOG_TAG, "Launch intent is null");
} else {
final String mainActivity = launchIntent.getComponent().getClassName();
Log.d(LOG_TAG, String.format("Open activity with package name %s / class name %s", packageName, mainActivity));
final Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setComponent(new ComponentName(packageName, mainActivity));
// optional: intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}
I think you'd have to find a list of all the activities declared in your manifest, and then use the intentFilter's actionsIterator() to iterate through all the actions of each activity's intent-filter, match the one that has intent.action.MAIN and then start that Activity.
The problem is I'm not sure how to retrieve a list of all your declared Activites from the manifest.
Related
I have arranged for my app to start when a file with the correct extension is chosen in a file manager. I have this in my manifest:
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<action android:name="android.intent.action.VIEW" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:host="*" />
<data android:mimeType="application/vnd.google-earth.kmz" />
</intent-filter>
and in OnCreate() I call this method, to tell the app what to do with the chosen file:
private void associateFile()
{
Intent intent = getIntent();
if (intent.getAction().equals(Intent.ACTION_MAIN))
{
// this is a normal start, not a file manager start
return;
}
Uri uri = intent.getData();
if (uri == null)
{
return;
}
String filePath = FileLoader.LoadFile(this, uri);
processFile(filePath);
}
and it all works fine....until I use the file manager method to start when the app is already running. This causes another copy to start, giving disastrous results with background tasks.
So how to I catch the fact that the app is already running, and just gracefully exit the second attempt at opening, with perhaps an appropriate message to the user? I have tried
ActivityManager am = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
List<ActivityManager.AppTask> taskInfo = am.getAppTasks();
to see if the app is already running, but the count of taskInfo is always one, not two as I would expect for the second attempt at opening.
After a much experimentation I have arrived at the following, using a new 'ParentActivity' to control what happens:
public class ParentActivity extends AppCompatActivity
{
Intent parentIntent;
#Override
protected void onCreate(Bundle savedInstanceState)
{
String filePath = null;
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_parent);
parentIntent = getIntent();
if (parentIntent.getAction() == null)
{
finish();
return;
}
Uri uri = parentIntent.getData();
if (uri != null)
filePath = FileLoader.LoadFile(this, uri);
// find out if MainActivity is already running
ActivityManager am = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
List<ActivityManager.AppTask> tasks = am.getAppTasks();
for (ActivityManager.AppTask task : tasks) {
String strTask = String.valueOf(task.getTaskInfo().baseActivity);
if (strTask.contains("MainActivity") && filePath != null)
{
// MainActivity already running while file manager used to select another file
// tell MainActivity what file to deal with
Intent fileIntent = new Intent("openFile");
fileIntent.putExtra("openFile", filePath);
LocalBroadcastManager.getInstance(this).sendBroadcast(fileIntent);
finish();
return;
}
}
// we need to start a new MainActivity
Intent mainIntent = new Intent(this, MainActivity.class);
if (filePath != null)
{
//new start from file (chosen from file manager)
mainIntent.putExtra("startWithFile",filePath);
}
startActivity (mainIntent);
finish();
}
}
I still don't understand why it works. In the loop checking the tasks, there is only ever one task found. So in the case of the app being started 'normally' (not from a file manager) the task info relates to ParentActivity, as expected. When the app is already running and the file manager used used to start a new instance, I would expect to see two tasks in this list: ParentActivity (for the new one) and MainActivity for the existing one. But there's only one task, MainActivity. What's happened to ParentActivity in the task list?
#CommonsWare, apologies if I haven't used the correct terminology in this analysis. I hope you (and others) get the gist.
G'day,
Disclaimer: I'm not an Android dev, I'm QAing an Android app with the issue I'm describing. The technical terms I use to describe this issue might be wrong.
I'm testing an Android app that describes in its manifest that it can handle web intents with the address of type https://www.example.com/app/(.*). The way it should handle these URLs is that it gets the first match group $1 and sends a request to https://api.example.com/$1 and if the response is a HTTP200, it renders the response within the app. If not, it should open the URL in any browser app the user has installed on their device (by sending an intent to open the URL). If the user has no browser apps installed on their device, we show an error prompt saying they don't have a browser installed which can handle this URL.
Now, this works fine except when the user marks this app as the default to handle URLs like https://www.example.com/app/(.*) when it first tries to open a URL like https://www.example.com/app/(.*). Then, even if the user has browser apps installed on their system, when they open a link that needs to be opened in a browser, the only option seems to be the our original app and we have to show the error message (as it seems like there are no other browser apps installed on the system which can handle this URL).
One way to tackle this is to show a message asking the user to clear the defaults for this app when we encounter a URL that needs to be opened in a browser app but the only option is our own app — but this is terrible UX. Is there another work-around for this issue?
Sample code to understand the issue: https://gist.github.com/GVRV/5879fcf0b1838b495e3a2151449e0da3
Edit 1: Added sample code link
To solve this problem and keep the systems default handling of intents you need 2 additional activities and 1 <activity-alias>:
Create a new invisible empty Activity. I called it IntentFilterDelegationActivity. This activity is responsible to receive URL intents from the activity-alias (defined in the next step). Manifest:
<activity
android:name=".intent_filter.IntentFilterDelegationActivity"
android:excludeFromRecents="true"
android:exported="true"
android:launchMode="singleInstance"
android:noHistory="true"
android:taskAffinity=""
android:theme="#style/Theme.Transparent"/>
Code:
public class IntentFilterDelegationActivity extends Activity
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
findAndStartMatchingActivity(getIntent());
finish();
}
}
Create an <activity-alias>. The activity alias is responsible to delegate your URL intents to your IntentFilterDelegationActivity. Only a Manifest entry is needed:
<activity-alias
android:name="${packageName}.IntentFilterDelegation"
android:enabled="true"
android:exported="true"
android:targetActivity=".intent_filter.IntentFilterDelegationActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http" android:host="www.example.com"/>
</intent-filter>
</activity-alias>
Now you are able to do the trick: You can deactivate the activity-alias before you launch your own URL intent and activate the alias after the launch. This causes android that your app won't be listed as app which can handle the URL intent. To implement the activation and deactivation you need an additional Activity. I called it ForceOpenInBrowserActivity.
Manifest:
<activity
android:name=".activity.ForceOpenInBrowserActivity"
android:excludeFromRecents="true"
android:launchMode="singleInstance"
android:noHistory="true"
android:taskAffinity=""
android:theme="#style/Theme.Transparent"/>
Code:
public class ForceOpenInBrowserActivity extends Activity
{
public static final String URI = IntentUtils.getIntentExtraString(ForceOpenInBrowserActivity.class, "URI");
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Uri uri = fetchUriFromIntent();
if (uri != null)
{
startForcedBrowserActivity(uri);
}
else
{
finish();
}
}
#Nullable
private Uri fetchUriFromIntent()
{
Uri uri = null;
Intent intent = getIntent();
if (intent != null)
{
uri = intent.getParcelableExtra(URI);
}
return uri;
}
private void startForcedBrowserActivity(Uri uri)
{
disableActivityAlias(this);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// After starting another activity, this activity will be destroyed
// android:noHistory="true" android:excludeFromRecents="true"
startActivity(intent);
}
/**
* Re-enable intent filters when returning to app.
* Note: The intent filters will also be enabled when starting the app.
*/
#Override
protected void onStop()
{
super.onStop();
enableActivityAlias();
}
public void disableActivityAlias()
{
String packageName = getPackageName();
ComponentName componentName = new ComponentName(packageName, packageName + ".IntentFilterDelegation"); // Activity alias
getPackageManager().setComponentEnabledSetting(componentName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
}
public void enableActivityAlias()
{
String packageName = getPackageName();
ComponentName componentName = new ComponentName(packageName, packageName + ".IntentFilterDelegation");
getPackageManager().setComponentEnabledSetting(componentName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
}
}
Now you can send any URL intent which must be opened in an external browser to the ForceOpenInBrowserActivity:
#NonNull
public static Intent createForceBrowserIntent(Context context, #NonNull Uri uri)
{
Intent intent = new Intent(context, ForceOpenInBrowserActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(ForceOpenInBrowserActivity.URI, uri);
return intent;
}
if website https://www.example.com/ is under your supervision, you could change the logic and use an unique schema like example://app/(.) to handle your case. The website could then use redirection to for its navigation. In this way when you broadcast https://www.example.com/ for action view only browser apps could handle this and your app would be only listening to your custom schema example://app/(.) and wont launch.
Else you could check for default activity and clear it instead of showing an alert.
PackageManager pm = context.getPackageManager();
final ResolveInfo res = pm.resolveActivity(your_intent, 0);
if (res.activityInfo != null && getPackageName()
.equals(res.activityInfo.packageName)) {
pm.clearPackagePreferredActivities("you_package_name");
broadcast your intent
}
Sadly, there is no official solution for this problem (see this SO question).
A workaround is the following:
Use PackageManager.queryIntentActivities(), modify the result to not include your app and show it in a custom chooser dialog.
If you don't want your users to choose a browser every time, you can manage a custom default inside your app.
If you control the domain, there is a cleaner workaround:
Lets say your url is http://www.example.com. Your Android IntentFilter should listen for that schema. Now you create a second schema, e.g. http://web.example.com, which displays the same content as the normal url. If you want to redirect to the web from your app, use the second schema. Everywhere else, use the first one.
Note that you should not use a custom schema like example://, because this will cause problems if your app is not present.
I have an application named as "App" and another application named as "App1", I have a button in "App" when I click that button I want to open "App1", for this I am using Intent but it does not open "App1".Please help
here is my code for button in "App":-
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.app);
init();
}
public void init(){
mTranslucentBtn = (Button) findViewById(R.id.button);
mTranslucentBtn.setAlpha(0.7f);
mTranslucentBtn.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v){//calling an activity using <intent-filter> action name
startNewActivity(MainActivity.this,"com.example.devui1.rewardapp");
}
});
}
public void startNewActivity(Context context, String packageName) {
Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName);
if (intent == null) {
// Bring user to the market or let them choose an app?
intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("market://details?id=" + packageName));
}
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
In order for getLaunchIntentForPackage() to work, that package needs to have an Activity that is suitable for being launched as an entry point into that app. The documentation includes this:
The current implementation looks first for a main activity in the category CATEGORY_INFO, and next for a main activity in the category CATEGORY_LAUNCHER. Returns null if neither are found.
This suggests you need one of the following on an Activity in your other package:
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.INFO" />
</intent-filter>
Or
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
If that's not something you want, then you could try creating your own custom action string and using that to launch an activity exposing that specific action:
<intent-filter>
<action android:name="your.package.ACTION_NAME" />
</intent-filter>
public void startActivityWithPrivateAction(Context context, String packageName) {
Intent intent = new Intent("your.package.ACTION_NAME");
intent.setPackage(packageName);
List<ResolveInfo> activities = context.getPackageManager().queryIntentActivities(intent, 0);
if (activities.isEmpty() {
// no suitable activity was found; open the market instead.
intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("market://details?id=" + packageName));
}
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
I want to trigger an application, B from application, A.
To achieve this i wrote the following in A
PackageManager pm = getPackageManager();
Intent intent = pm.getLaunchIntentForPackage("com.somepackage.appb");
intent.putExtra("secret", "message");
startActivity(intent);
However this opens up the application B which is not desired.
Please suggest a work around to avoid B from opening up and receive the data in background from application A.
Write a BroadcastReceiver in application B and declare it in your manifest.
In application A, craft an intent with extras to target that receiver, and call sendBroadcast with that intent.
Application B:
Manifest
<application>
...
<receiver android:name=".IncomingReceiver" android:enabled="true">
<intent-filter>
<action android:name="jason.wei.custom.intent.action.TEST"></action>
</intent-filter>
</receiver>
</application>
IncomingReceiver.java
public class IncomingReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
String CUSTOM_INTENT = "jason.wei.custom.intent.action.TEST";
if (intent.getAction().equals(CUSTOM_INTENT)) {
System.out.println("GOT THE INTENT");
Toast.makeText(context, "GOT THE INTENT", Toast.LENGTH_LONG).show();
}
}
}
Application A:
String CUSTOM_INTENT = "jason.wei.custom.intent.action.TEST";
Intent i = new Intent();
i.setAction(CUSTOM_INTENT);
sendBroadcast(i);
You can use SharedPreferences.
As you can see there are many ways to do this...myself i prefer creating a service which can update data without interacting with app B
I am doing an Android application. I want to hide the application icon in the emulator and I want to start my application by pressing some numbers, for instance 456#. Is there a way to do this?
To Hide app icon from launcher programatically you can do this
PackageManager packageManager = context.getPackageManager();
ComponentName componentName = new ComponentName(context,
LauncherActivity.class);
packageManager.setComponentEnabledSetting(componentName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
To launch app by pressing number
first add folowing permission in mainfest file
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
Then register receiver
<receiver android:name=".LaunchAppViaDialReceiver">
<intent-filter>
<action android:name="android.intent.action.NEW_OUTGOING_CALL"/>
</intent-filter>
</receiver>
Then create a receiver class
public class LaunchAppViaDialReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
Bundle bundle = intent.getExtras();
if (null == bundle)
return;
String phoneNubmer = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
//here change the number to your desired number
if (phoneNubmer.equals("12345")) {
setResultData(null);
Gaurdian.changeStealthMode(context,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
Intent appIntent = new Intent(context, LauncherActivity.class);
appIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(appIntent);
}
}
If you want to hide the app icon it's a good idea to show the icon first and let the user know how to start the app once the icon is gone. First create an activity-alias in the manifest and move your intent filter there. This way you can disable the icon without disabling the activity.
<activity
android:name=".MainActivity"
android:label="#string/app_name" >
</activity>
<activity-alias
android:name=".Launcher"
android:targetActivity=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
Get the component name of the launcher alias using your package name:
private static final ComponentName LAUNCHER_COMPONENT_NAME = new ComponentName(
"your.package.name", "your.package.name.Launcher");
You can check if it's already disabled...
private boolean isLauncherIconVisible() {
int enabledSetting = getPackageManager()
.getComponentEnabledSetting(LAUNCHER_COMPONENT_NAME);
return enabledSetting != PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
}
...and disable it when appropriate after giving the user information:
private void hideLauncherIcon() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Important!");
builder.setMessage("To launch the app again, dial phone number 12345.");
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
getPackageManager().setComponentEnabledSetting(LAUNCHER_COMPONENT_NAME,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
}
});
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.show();
}
To launch from the dialer create a broadcast receiver:
public class LaunchViaDialReceiver extends BroadcastReceiver {
private static final String LAUNCHER_NUMBER = "12345";
#Override
public void onReceive(Context context, Intent intent) {
String phoneNubmer = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
if (LAUNCHER_NUMBER.equals(phoneNubmer)) {
setResultData(null);
Intent appIntent = new Intent(context, MainActivity.class);
appIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(appIntent);
}
}
}
Add it to the manifest:
<receiver android:name=".LaunchViaDialReceiver" >
<intent-filter>
<action android:name="android.intent.action.NEW_OUTGOING_CALL" />
</intent-filter>
</receiver>
And add the permission to the manifest:
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
The answer to the first part of your question, try this code:
PackageManager pm = getApplicationContext().getPackageManager();
pm.setComponentEnabledSetting(getComponentName(), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
Your application will not be visible, but the user can still find it in the Settings >> Applications >> Manage Application
This answer may also be helpful for you.
Please do not forget to post your answer here, if you have already achieved the functionality(pressing some number & opening our application).
Note that the solution:
PackageManager pm = getApplicationContext().getPackageManager();
pm.setComponentEnabledSetting(getComponentName(), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
will make the app NOT upgradeable from google play as the OS will not find the package after this component disabling and will not able to re-install it, unless the app is not manullay uninstalled (which is not a user friendly behaviour)
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
hideapplication();
}
private void hideapplication() {
// TODO Auto-generated method stub
PackageManager pm = getApplicationContext().getPackageManager();
pm.setComponentEnabledSetting(getComponentName(), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
}
}