In my app there is a WebView to which I load a website from server.
There are two cases:
On the loaded website there is a button which should lead to an another app. I know it can be handled like this:
Click me
but this doesn't work in the WebView (only in a standalone browser)! I tried to handle it in shouldOverrideUrlLoading and redirect to an external browser with Intent, but the URI with "intent://" URL is not recognised and cannot be opened.
The link I get from server is the 'intent' link.
The behaviour in both cases should be the same: if app is installed open the app, if not open Google Play do download the app.
Is there any way to do this?
I'm not sure if this is the best option, but I handeled it similarily to what #vineetv suggested. This method is called inside shouldOverrideUrlLoading():
private void handleNewUrl(String url) {
Uri uri = Uri.parse(url);
if (uri.getScheme().equals("http") || uri.getScheme().equals("https"))
openExternalWebsite(url);
else if (uri.getScheme().equals("intent")) {
String appPackage = getAppPackageFromUri(uri);
if (appPackage != null) {
PackageManager manager = getContext().getPackageManager();
Intent appIntent = manager.getLaunchIntentForPackage(appPackage);
if (appIntent != null) {
getActivity().startActivity(appIntent);
} else {
openExternalWebsite("https://play.google.com/store/apps/details?id=" + appPackage);
}
}
}
}
private String getAppPackageFromUri(Uri intentUri) {
Pattern pattern = Pattern.compile("package=(.*?);");
Matcher matcher = pattern.matcher(intentUri.getFragment());
if (matcher.find())
return matcher.group(1);
return null;
}
private void openExternalWebsite(String url) {
Intent webeIntent = new Intent(Intent.ACTION_VIEW);
webeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
webeIntent.setData(Uri.parse(url));
getActivity().startActivity(webeIntent);
}
It seems, to work. But if you have a better solution, let me know!
Note: This issue only arises when the Facebook app is installed.
I'm trying to open a user's Facebook profile page when an ImageButton is clicked. After looking at this suggested method for more recent Facebook app versions, it's mostly working, with the exception of users that are friends with the logged in user (in the app), or the logged in user themselves.
The process is:
Image button is clicked
Call to Facebook Graph API is made to get the link to the user's profile via their Facebook ID (stored in our database)
A new Intent is created by checking to see if the Facebook app is installed, and generating the correct Uri
A new Activity is started with the generated Intent
Because we're using Facebook Graph API v2.0, the username field is removed from the GraphUser object in the JSON. The problem is that the Facebook link (GraphUser.getLink()) is either:
An app-scoped (protected) link if the user (logged into the app) is not friends with the user whose profile we are trying to view; format is : https://www.facebook.com/app_scoped_user_id/{protected-id}
A 'normal' Facebook link with the user's real id (unprotected); format is: http://www.facebook.com/{real-user-id}
The real problem is that the Facebook Uri when the app is installed expects either:
An app-scoped link
A link with the username attached to it, not {user-id}
However, the web version (opened in a web view) doesn't care. The link can be app-scoped, username-based or id-based. So everything works for WebViews.
Since username is completely inaccessible in Graph API v2.0, I'm just getting null values, which means that for the Facebook app I can't generate the correct Uri that it expects (username-based), only id-based or app-scoped.
Here is the code that I'm using to create the Intent:
public static Intent getFacebookIntent(String facebookProfileUrl) {
PackageManager pm = App.getContext().getPackageManager();
Uri uri;
try {
pm.getPackageInfo("com.facebook.katana", 0);
uri = Uri.parse("fb://facewebmodal/f?href=" + facebookProfileUrl);
} catch (PackageManager.NameNotFoundException e) {
uri = Uri.parse(facebookProfileUrl);
}
return new Intent(Intent.ACTION_VIEW, uri);
}
And here is my onClick() method on the ImageButton:
new Request(
Session.getActiveSession(),
String.valueOf(mate.getFacebookId()),
null,
HttpMethod.GET,
new Request.Callback() {
public void onCompleted(Response response) {
GraphUser g = response.getGraphObjectAs(GraphUser.class);
String fbUrl = g.getLink();
// TODO: If link does not contain 'scoped', need USERNAME, not ID
String otherUrl = "https://www.facebook.com/" + myAppUser.getId();
String finalUrl = (fbUrl.contains("scoped")) ? fbUrl : otherUrl;
Intent intent = getFacebookIntent(finalUrl);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}
).executeAsync();
I need the username if the app is installed, otherwise the web view deals with it as it should.
So the question: How can I get a Facebook username by id for the Facebook app? Or, is there a different Uri that the Facebook app expects that I can account for, using ids and not usernames?
You should be able to use the fb://profile/{user-id} URL format to launch native apps. This will let you use the user ID instead of a username. You could rewrite your getFacebookIntent method as:
public Intent getFacebookIntent(GraphUser user) {
PackageManager pm = this.getPackageManager();
Uri uri;
try {
pm.getPackageInfo("com.facebook.katana", 0);
uri = Uri.parse("fb://profile/" + user.getId());
} catch (PackageManager.NameNotFoundException e) {
uri = Uri.parse(user.getLink());
}
return new Intent(Intent.ACTION_VIEW, uri);
}
You could then call this method and pass in the GraphUser object.
I'm developing a Xamarin Android app and I need the ability to be able to work with Passes (PassKit passes for example (JSON)). I need to be able to list all the passes in a ListVew and be able to open and display the pass. Also be able to save them to a wallet such as PassWallet or Pass2u. I don't need the ability to create them, just view them, and save them to a wallet or discard them.
There seems to be an example Xamarin iOS app which does exactly what i need here but of course I need to be able to do this in Xamarin Android.
I've been researching this for hours but don't know how to achieve what i need. JSON.net seems the way to go to read the passes, but that's as far as I've managed to get. Some examples would be great. Can anybody help?
To add the pass into PassWallet you can use the following:
private static boolean launchPassWallet(Context applicationContext, Uri uri, boolean launchGooglePlay) {
if (null != applicationContext) {
PackageManager packageManager = applicationContext.getPackageManager();
if (null != packageManager) {
final String strPackageName = "com.attidomobile.passwallet";
Intent startIntent = new Intent();
startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startIntent.setAction(Intent.ACTION_VIEW);
Intent passWalletLaunchIntent = packageManager
.getLaunchIntentForPackage(strPackageName);
if (null == passWalletLaunchIntent) {
// PassWallet isn't installed, open Google Play:
if (launchGooglePlay) {
String strReferrer = "";
try {
strReferrer = "&referrer=" + URLEncoder.encode(uri.toString(), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
strReferrer = "";
}
try {
startIntent.setData(Uri.parse("market://details?id=" + strPackageName + strReferrer));
applicationContext.startActivity(startIntent);
} catch (android.content.ActivityNotFoundException anfe) {
// Google Play not installed, open via website
startIntent.setData(Uri.parse("http://play.google.com/store/apps/details?id=" + strPackageName + strReferrer));
applicationContext.startActivity(startIntent);
}
}
} else {
final String strClassName = "com.attidomobile.passwallet.activity.TicketDetailActivity";
startIntent.setClassName(strPackageName, strClassName);
startIntent.addCategory(Intent.CATEGORY_BROWSABLE);
startIntent.setDataAndType(uri, "application/vnd.apple.pkpass");
applicationContext.startActivity(startIntent);
return true;
}
}
}
return false;
}
And an example call is:
launchPassWallet(getApplicationContext(),Uri.parse("http://test.attidomobile.com/PassWallet/Passes/AttidoMobile.pkpass"), true);
You can also use a file:// URL if you have the file locally.
To display them in the list, you'd need to unzip the .pkpass file and then parse the JSON for the relevant fields.
My application installs other applications, and it needs to keep track of what applications it has installed. Of course, this could be achieved by simply keeping a list of installed applications. But this should not be necessary! It should be the responsibility of the PackageManager to maintain the installedBy(a, b) relationship. In fact, according to the API it is:
public abstract String getInstallerPackageName(String packageName) -
Retrieve the package name of the application that installed a package. This identifies which market the package came from.
The current approach
Install APK using Intent
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
startActivity(intent);
Uninstall APK using Intent:
Intent intent = new Intent(Intent.ACTION_DELETE, Uri.fromParts("package",
getPackageManager().getPackageArchiveInfo(apkUri.getPath(), 0).packageName,null));
startActivity(intent);
This is obviously not the way e.g. Android Market installs / uninstalls packages. They use a richer version of the PackageManager. This can bee seen by downloading the Android source code from the Android Git repository. Below are the two hidden methods that corresponds to the Intent approach. Unfortunately they are not available to external developers. But perhaps they will be in the future?
The better approach
Installing APK using the PackageManager
/**
* #hide
*
* Install a package. Since this may take a little while, the result will
* be posted back to the given observer. An installation will fail if the calling context
* lacks the {#link android.Manifest.permission#INSTALL_PACKAGES} permission, if the
* package named in the package file's manifest is already installed, or if there's no space
* available on the device.
*
* #param packageURI The location of the package file to install. This can be a 'file:' or a
* 'content:' URI.
* #param observer An observer callback to get notified when the package installation is
* complete. {#link IPackageInstallObserver#packageInstalled(String, int)} will be
* called when that happens. observer may be null to indicate that no callback is desired.
* #param flags - possible values: {#link #INSTALL_FORWARD_LOCK},
* {#link #INSTALL_REPLACE_EXISTING}, {#link #INSTALL_ALLOW_TEST}.
* #param installerPackageName Optional package name of the application that is performing the
* installation. This identifies which market the package came from.
*/
public abstract void installPackage(
Uri packageURI, IPackageInstallObserver observer, int flags,
String installerPackageName);
Uninstalling APK using the PackageManager
/**
* Attempts to delete a package. Since this may take a little while, the result will
* be posted back to the given observer. A deletion will fail if the calling context
* lacks the {#link android.Manifest.permission#DELETE_PACKAGES} permission, if the
* named package cannot be found, or if the named package is a "system package".
* (TODO: include pointer to documentation on "system packages")
*
* #param packageName The name of the package to delete
* #param observer An observer callback to get notified when the package deletion is
* complete. {#link android.content.pm.IPackageDeleteObserver#packageDeleted(boolean)} will be
* called when that happens. observer may be null to indicate that no callback is desired.
* #param flags - possible values: {#link #DONT_DELETE_DATA}
*
* #hide
*/
public abstract void deletePackage(
String packageName, IPackageDeleteObserver observer, int flags);
Differences
When using intents the local package manager is not made aware of which application the installation originated from. Specifically, getInstallerPackageName(...) returns null.
The hidden method installPackage(...) takes the installer package name as a parameter, and is most likely capable of setting this value.
Question
Is it possible to specify package installer name using intents?
(Maybe the name of the installer package can be added as an extra to the installation intent?)
Tip: If you want to download the Android source code you can follow the steps described here: Downloading the Source Tree. To extract the *.java files and put them in folders according to the package hierarchy you can check out this neat script: View Android Source Code in Eclipse.
Android P+ requires this permission in AndroidManifest.xml
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
Then:
Intent intent = new Intent(Intent.ACTION_DELETE);
intent.setData(Uri.parse("package:com.example.mypackage"));
startActivity(intent);
to uninstall. Seems easier...
This is not currently available to third party applications. Note that even using reflection or other tricks to access installPackage() will not help, because only system applications can use it. (This is because it is the low-level install mechanism, after the permissions have been approved by the user, so it is not safe for regular applications to have access to.)
Also the installPackage() function arguments have often changed between platform releases, so anything you do trying access it will fail on various other versions of the platform.
EDIT:
Also it is worth pointing out that this installerPackage was only added fairly recently to the platform (2.2?) and was originally not actually used for tracking who installed the app -- it is used by the platform to determine who to launch when reporting bugs with the app, for implementing Android Feedback. (This was also one of the times the API method arguments changed.) For at least a long while after it was introduced, Market still didn't use it to track the apps it has installed (and it may very well still not use it), but instead just used this to set the Android Feedback app (which was separate from Market) as the "owner" to take care of feedback.
API level 14 introduced two new actions: ACTION_INSTALL_PACKAGE and ACTION_UNINSTALL_PACKAGE. Those actions allow you to pass EXTRA_RETURN_RESULT boolean extra to get an (un)installation result notification.
Example code for invoking the uninstall dialog:
String app_pkg_name = "com.example.app";
int UNINSTALL_REQUEST_CODE = 1;
Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
intent.setData(Uri.parse("package:" + app_pkg_name));
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
startActivityForResult(intent, UNINSTALL_REQUEST_CODE);
And receive the notification in your Activity#onActivityResult method:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == UNINSTALL_REQUEST_CODE) {
if (resultCode == RESULT_OK) {
Log.d("TAG", "onActivityResult: user accepted the (un)install");
} else if (resultCode == RESULT_CANCELED) {
Log.d("TAG", "onActivityResult: user canceled the (un)install");
} else if (resultCode == RESULT_FIRST_USER) {
Log.d("TAG", "onActivityResult: failed to (un)install");
}
}
}
If you have Device Owner (or profile owner, I haven't tried) permission you can silently install/uninstall packages using device owner API.
for uninstalling:
public boolean uninstallPackage(Context context, String packageName) {
ComponentName name = new ComponentName(MyAppName, MyDeviceAdminReceiver.class.getCanonicalName());
PackageManager packageManger = context.getPackageManager();
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
PackageInstaller packageInstaller = packageManger.getPackageInstaller();
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
params.setAppPackageName(packageName);
int sessionId = 0;
try {
sessionId = packageInstaller.createSession(params);
} catch (IOException e) {
e.printStackTrace();
return false;
}
packageInstaller.uninstall(packageName, PendingIntent.getBroadcast(context, sessionId,
new Intent("android.intent.action.MAIN"), 0).getIntentSender());
return true;
}
System.err.println("old sdk");
return false;
}
and to install package:
public boolean installPackage(Context context,
String packageName, String packagePath) {
ComponentName name = new ComponentName(MyAppName, MyDeviceAdminReceiver.class.getCanonicalName());
PackageManager packageManger = context.getPackageManager();
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
PackageInstaller packageInstaller = packageManger.getPackageInstaller();
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
params.setAppPackageName(packageName);
try {
int sessionId = packageInstaller.createSession(params);
PackageInstaller.Session session = packageInstaller.openSession(sessionId);
OutputStream out = session.openWrite(packageName + ".apk", 0, -1);
readTo(packagePath, out); //read the apk content and write it to out
session.fsync(out);
out.close();
System.out.println("installing...");
session.commit(PendingIntent.getBroadcast(context, sessionId,
new Intent("android.intent.action.MAIN"), 0).getIntentSender());
System.out.println("install request sent");
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
System.err.println("old sdk");
return false;
}
The only way to access those methods is through reflection. You can get a handle on a PackageManager object by calling getApplicationContext().getPackageManager() and using reflection access these methods. Checkout this tutorial.
According to Froyo source code, the Intent.EXTRA_INSTALLER_PACKAGE_NAME extra key is queried for the installer package name in the PackageInstallerActivity.
On a rooted device, you might use:
String pkg = context.getPackageName();
String shellCmd = "rm -r /data/app/" + pkg + "*.apk\n"
+ "rm -r /data/data/" + pkg + "\n"
// TODO remove data on the sd card
+ "sync\n"
+ "reboot\n";
Util.sudo(shellCmd);
Util.sudo() is defined here.
If you are passing package name as parameter to any of your user defined function then use the below code :
Intent intent=new Intent(Intent.ACTION_DELETE);
intent.setData(Uri.parse("package:"+packageName));
startActivity(intent);
If you're using Kotlin, API 14+, and just wish to show uninstall dialog for your app:
startActivity(Intent(Intent.ACTION_UNINSTALL_PACKAGE).apply {
data = Uri.parse("package:$packageName")
})
You can change packageName to any other package name if you want to prompt the user to uninstall another app on the device
Prerequisite:
Your APK needs to be signed by system as correctly pointed out earlier. One way to achieve that is building the AOSP image yourself and adding the source code into the build.
Code:
Once installed as a system app, you can use the package manager methods to install and uninstall an APK as following:
Install:
public boolean install(final String apkPath, final Context context) {
Log.d(TAG, "Installing apk at " + apkPath);
try {
final Uri apkUri = Uri.fromFile(new File(apkPath));
final String installerPackageName = "MyInstaller";
context.getPackageManager().installPackage(apkUri, installObserver, PackageManager.INSTALL_REPLACE_EXISTING, installerPackageName);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
Uninstall:
public boolean uninstall(final String packageName, final Context context) {
Log.d(TAG, "Uninstalling package " + packageName);
try {
context.getPackageManager().deletePackage(packageName, deleteObserver, PackageManager.DELETE_ALL_USERS);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
To have a callback once your APK is installed/uninstalled you can use this:
/**
* Callback after a package was installed be it success or failure.
*/
private class InstallObserver implements IPackageInstallObserver {
#Override
public void packageInstalled(String packageName, int returnCode) throws RemoteException {
if (packageName != null) {
Log.d(TAG, "Successfully installed package " + packageName);
callback.onAppInstalled(true, packageName);
} else {
Log.e(TAG, "Failed to install package.");
callback.onAppInstalled(false, null);
}
}
#Override
public IBinder asBinder() {
return null;
}
}
/**
* Callback after a package was deleted be it success or failure.
*/
private class DeleteObserver implements IPackageDeleteObserver {
#Override
public void packageDeleted(String packageName, int returnCode) throws RemoteException {
if (packageName != null) {
Log.d(TAG, "Successfully uninstalled package " + packageName);
callback.onAppUninstalled(true, packageName);
} else {
Log.e(TAG, "Failed to uninstall package.");
callback.onAppUninstalled(false, null);
}
}
#Override
public IBinder asBinder() {
return null;
}
}
/**
* Callback to give the flow back to the calling class.
*/
public interface InstallerCallback {
void onAppInstalled(final boolean success, final String packageName);
void onAppUninstalled(final boolean success, final String packageName);
}
I'm making an app that can launch other apps. I've got it launching apps fine using Spinners, however, I would also like to give the user the ability to launch direct dials from it.
As it is right now I've got "hot key" buttons that the user can configure. Currently, when the user wants to configure one of these "hot keys" I use a spinner to let them choose from all the installed applications on their phone. For starters, I would like it if they are able to view both installed applications and shortcuts in the spinner so that they can map a direct dial to one of these "hot keys."
So my main questions are, how can I go about looking up all the defined shortcuts available and execute them and how could I create my own direct dials in my app?
To dial a number directly
startActivity(new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + NUMBER)));
here is a simple function for this
public static void go2Call(Context context, String phoneNo) {
Intent intent = null;
Uri destUri = null;
/*
* http://developer.android.com/guide/appendix/g-app-intents.html
<uses-permission id="android.permission.CALL_PHONE" />
tel: phone_number
*/
if(DEBUG)Log.d(TAG, "go2Call ->" + "phoneNo:"+phoneNo);
phoneNo = PhoneNumberUtils.convertKeypadLettersToDigits(phoneNo);
if(DEBUG)Log.d(TAG, "go2Call ->" + "phoneNo(normalized):"+phoneNo);
if ( !TextUtils.isEmpty(phoneNo) ) {
destUri = Uri.parse("tel:" + phoneNo);
}
if (destUri!=null) {
intent = new Intent( Intent.ACTION_VIEW, destUri );
}
if ( intent!=null && isIntentAvailable(context, intent) ) {
context.startActivity(intent);
}
else {
// TODO: display error msg
Log.w(TAG, "error pr intent not available! ->" + "phoneNo:"+phoneNo);
}
}