Java tutorial
/* Offscreen Android Views library for Qt Author: Sergey A. Galin <sergey.galin@gmail.com> Distrbuted under The BSD License Copyright (c) 2015-2016, DoubleGIS, LLC. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the DoubleGIS, LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package ru.dublgis.androidhelpers; import java.util.Iterator; import java.util.List; import java.util.Locale; import android.os.LocaleList; import java.io.File; import java.util.ArrayList; import java.util.Set; import java.util.TreeSet; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; import android.os.Build; import android.provider.Settings; import android.support.annotation.NonNull; import android.support.v4.content.FileProvider; import android.telephony.TelephonyManager; import android.provider.Settings.Secure; public class DesktopUtils { private static final String TAG = "Grym/DesktopServices"; private static final boolean verbose = false; // Returns: // -1 - error // 1 - network connected // 0 - network not connected public static int isInternetActive(final Context ctx) { try { ConnectivityManager cmgr = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE); if (cmgr == null) return -1; NetworkInfo netinfo = cmgr.getActiveNetworkInfo(); if (netinfo != null && netinfo.isConnected()) return 1; return 0; } catch (final Throwable e) { Log.e(TAG, "IsInternetActive exception: ", e); } return -1; } // Returns: // a. -1 on error // b. See: http://developer.android.com/reference/android/net/ConnectivityManager.html#TYPE_BLUETOOTH // for possible returned values. public static int getNetworkType(final Context ctx) { try { ConnectivityManager cmgr = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE); if (cmgr == null) return -1; NetworkInfo netinfo = cmgr.getActiveNetworkInfo(); if (netinfo == null) return -1; return netinfo.getType(); } catch (final Throwable e) { Log.e(TAG, "GetNetworkType exception: ", e); } return -1; } // Open application settings public static void showApplicationSettings(final Context ctx) { try { final Intent intent = new Intent(); intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.addCategory(Intent.CATEGORY_DEFAULT); intent.setData(Uri.fromParts("package", ctx.getPackageName(), null)); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); ctx.startActivity(intent); } catch (final Throwable throwable) { Log.e(TAG, "showApplicationSettings throwable: " + throwable); } } // Show generic "send to" menu public static boolean sendTo(final Context ctx, final String chooser_caption, final String text, final String content_type) { try { Log.d(TAG, "Will send-to text \"" + text + "\" of type: \"" + content_type + "\""); Intent sendIntent = new Intent(Intent.ACTION_SEND); sendIntent.setType(content_type); sendIntent.putExtra(Intent.EXTRA_TEXT, text); Intent chooser = Intent.createChooser(sendIntent, chooser_caption); chooser.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); ctx.startActivity(chooser); return true; } catch (final Throwable e) { Log.e(TAG, "sendTo exception: ", e); return false; } } public static boolean sendSMS(final Context ctx, final String number, final String text) { try { //Log.d(TAG, "Will send sms \""+text+"\" to \""+number+"\""); String uris = "smsto:"; if (number != null && number.length() > 2) uris += number; if (verbose) Log.i(TAG, "URI: " + uris); Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse(uris)); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra("sms_body", text); ctx.startActivity(intent); return true; } catch (final Throwable e) { Log.e(TAG, "sendSMS exception: ", e); return false; } } /* For this to work with attachments on Android 6 or with force_content_provider == true, please add this section into you AndroidManifest.xml: <provider android:name="android.support.v4.content.FileProvider" android:authorities="ru.dublgis.sharefileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_provider_paths" /> </provider> Also, create res/xml/file_provider_paths.xml with the following content: <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-path name="share" path="/" /> </paths> */ public static boolean sendEmail(final Context ctx, final String to, final String subject, final String body, final String attach_file, final boolean force_content_provider, final String authorities) { //Log.d(TAG, "Will send email with subject \"" + // subject + "\" to \"" + to + "\" with attach_file = \"" + attach_file + "\"" + // ", force_content_provider = " + force_content_provider + // ", authorities = \"" + authorities + "\""); try { // TODO: support multiple recipients String[] recipients = new String[] { to }; final Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto", to, null)); List<ResolveInfo> resolveInfos = ctx.getPackageManager().queryIntentActivities(intent, 0); Intent chooserIntent = null; List<Intent> intentList = new ArrayList<Intent>(); Intent i = new Intent(Intent.ACTION_SEND); i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); i.setType("message/rfc822"); i.putExtra(Intent.EXTRA_EMAIL, recipients); i.putExtra(Intent.EXTRA_SUBJECT, subject); i.putExtra(Intent.EXTRA_TEXT, body); Uri workaround_grant_permission_for_uri = null; if (attach_file != null && !attach_file.isEmpty()) { if (!force_content_provider && android.os.Build.VERSION.SDK_INT < 23) { i.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(attach_file))); } else { // Android 6+: going the longer route. // For more information, please see: // http://stackoverflow.com/questions/32981194/android-6-cannot-share-files-anymore i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); workaround_grant_permission_for_uri = FileProvider.getUriForFile(ctx, authorities, new File(attach_file)); i.putExtra(Intent.EXTRA_STREAM, workaround_grant_permission_for_uri); } } for (ResolveInfo resolveInfo : resolveInfos) { String packageName = resolveInfo.activityInfo.packageName; String name = resolveInfo.activityInfo.name; // Some mail clients will not read the URI unless this is done. // See here: https://stackoverflow.com/questions/24467696/android-file-provider-permission-denial if (workaround_grant_permission_for_uri != null) { try { ctx.grantUriPermission(packageName, workaround_grant_permission_for_uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); } catch (final Throwable e) { Log.e(TAG, "grantUriPermission error: ", e); } } Intent fakeIntent = (Intent) i.clone(); fakeIntent.setComponent(new ComponentName(packageName, name)); if (chooserIntent == null) { chooserIntent = fakeIntent; } else { intentList.add(fakeIntent); } } if (chooserIntent == null) { chooserIntent = Intent.createChooser(i, null // "Select email application." ); chooserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } else if (!intentList.isEmpty()) { Intent fakeIntent = chooserIntent; chooserIntent = new Intent(Intent.ACTION_CHOOSER); chooserIntent.putExtra(Intent.EXTRA_INTENT, fakeIntent); Intent[] extraIntents = intentList.toArray(new Intent[intentList.size()]); chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents); } ctx.startActivity(chooserIntent); return true; } catch (final Throwable e) { Log.e(TAG, "sendEmail exception: ", e); return false; } } public static boolean sendEmail(final Context ctx, final String to, final String subject, final String body, final String[] attachment, final String authorities) { try { final String[] recipients = new String[] { to }; final Intent intent = new Intent( attachment.length > 1 ? Intent.ACTION_SEND_MULTIPLE : Intent.ACTION_SENDTO); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setType("message/rfc822"); intent.putExtra(Intent.EXTRA_EMAIL, recipients); intent.putExtra(Intent.EXTRA_SUBJECT, subject); intent.putExtra(Intent.EXTRA_TEXT, body); boolean grant_permissions_workaround = false; final ArrayList<Uri> uri = new ArrayList<>(); if (attachment.length > 0) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { for (final String fileName : attachment) { uri.add(Uri.fromFile(new File(fileName))); } } else { // Android 6+: going the longer route. // For more information, please see: // http://stackoverflow.com/questions/32981194/android-6-cannot-share-files-anymore intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); grant_permissions_workaround = true; for (final String fileName : attachment) { uri.add(FileProvider.getUriForFile(ctx, authorities, new File(fileName))); } } // Should not put array with only one element into intent because of a bug in GMail. if (uri.size() == 1) { intent.putExtra(Intent.EXTRA_STREAM, uri.get(0)); } else { intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uri); } } final IntentResolverInfo mailtoIntentResolvers = new IntentResolverInfo(ctx.getPackageManager()); mailtoIntentResolvers .appendResolvers(new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto", to, null))); final Intent chooserIntent; if (mailtoIntentResolvers.isEmpty()) { chooserIntent = Intent.createChooser(intent, null); } else { final IntentResolverInfo messageIntentResolvers = new IntentResolverInfo(ctx.getPackageManager()); messageIntentResolvers .appendResolvers(new Intent(Intent.ACTION_SENDTO, Uri.fromParts("sms", "", null))); messageIntentResolvers .appendResolvers(new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mms", "", null))); messageIntentResolvers .appendResolvers(new Intent(Intent.ACTION_SENDTO, Uri.fromParts("tel", "", null))); mailtoIntentResolvers.removeSamePackages(messageIntentResolvers.getResolveInfos()); final List<Intent> intentList = new ArrayList<>(); for (final ActivityInfo activityInfo : mailtoIntentResolvers.getResolveInfos()) { final String packageName = activityInfo.getPackageName(); final String name = activityInfo.getName(); // Some mail clients will not read the URI unless this is done. // See here: https://stackoverflow.com/questions/24467696/android-file-provider-permission-denial if (grant_permissions_workaround) { for (int i = 0; i < uri.size(); ++i) { try { ctx.grantUriPermission(packageName, uri.get(i), Intent.FLAG_GRANT_READ_URI_PERMISSION); } catch (final Throwable e) { Log.e(TAG, "grantUriPermission error: ", e); } } } final Intent cloneIntent = (Intent) intent.clone(); cloneIntent.setComponent(new ComponentName(packageName, name)); intentList.add(cloneIntent); } final Intent targetIntent = intentList.get(0); intentList.remove(0); chooserIntent = Intent.createChooser(targetIntent, null); if (!intentList.isEmpty()) { final Intent[] extraIntents = intentList.toArray(new Intent[intentList.size()]); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents); } else { chooserIntent.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, extraIntents); } } } chooserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); ctx.startActivity(chooserIntent); return true; } catch (final Throwable exception) { Log.e(TAG, "sendEmail exception: ", exception); return false; } } public static boolean openURL(final Context ctx, final String url) { try { Log.d(TAG, "Will open URL: " + url); String openurl = url; Uri uri = Uri.parse(openurl); String scheme = uri.getScheme(); if (scheme == null || (!scheme.equalsIgnoreCase("http") && !scheme.equalsIgnoreCase("https") && !scheme.equalsIgnoreCase("skype"))) { openurl = "http://" + openurl; } Intent i = new Intent(Intent.ACTION_VIEW); i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); i.setData(Uri.parse(openurl)); ctx.startActivity(i); return true; } catch (final Throwable e) { Log.e(TAG, "openURL exception: ", e); return false; } } // Open a file using some (or default) associated application public static boolean openFile(final Context ctx, final String fileName, final String mimeType) { Log.d(TAG, "Will open file: " + fileName); try { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); File file = new File(fileName); intent.setDataAndType(Uri.fromFile(file), mimeType); ctx.startActivity(intent); return true; } catch (final Throwable e) { Log.e(TAG, "Exception while opening a file: ", e); return false; } } // Request system to install a local apk file. System installer application will be open. public static boolean installApk(final Context ctx, final String apk) { Log.d(TAG, "Will install APK: " + apk); try { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setDataAndType(Uri.fromFile(new File(apk)), "application/vnd.android.package-archive"); ctx.startActivity(intent); Log.i(TAG, "Installation intent started successfully."); return true; } catch (final Throwable e) { Log.e(TAG, "Exception while installing apk: ", e); return false; } } // Request system to uninstall a package. User will be prompted for confirmation by the system package manager application. public static void uninstallApk(final Context ctx, final String packagename) { Log.i(TAG, "Will uninstall package: " + packagename); try { Intent intent = new Intent(Intent.ACTION_DELETE); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setData(Uri.parse("package:" + packagename)); ctx.startActivity(intent); Log.i(TAG, "Uninstallation intent started successfully."); } catch (final Throwable e) { Log.e(TAG, "Exception while uninstalling package: ", e); } } // See: https://stackoverflow.com/questions/5196833/android-device-phone-call-ability public static boolean isVoiceTelephonyAvailable(final Context ctx) { try { if (!ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { return false; } final TelephonyManager manager = (TelephonyManager) ctx.getSystemService(Context.TELEPHONY_SERVICE); if (manager == null) { return false; } if (manager.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE) { return false; } if (Build.VERSION.SDK_INT >= 22) // Android 5.1+ { if (!manager.isVoiceCapable()) { return false; } } return true; } catch (final Throwable e) { Log.e(TAG, "isVoiceTelephonyAvailable exception (will return 'false'): ", e); return false; } } // 'number' is an RFC-3966 URI without 'tel:'. // If 'action' is null or empty string it defaults to Intent.ACTION_VIEW. // See: http://tools.ietf.org/html/rfc3966 public static boolean callNumber(final Context ctx, final String number, final String action) { try { Log.i(TAG, "Will call number: " + number); final String doAction = (action == null || action.isEmpty()) ? Intent.ACTION_VIEW : action; final String doPhone = (number.startsWith("tel:")) ? number : "tel:" + number; final Intent i = new Intent(doAction); i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); i.setData(Uri.parse(doPhone)); ctx.startActivity(i); return true; } catch (final Throwable e) { Log.e(TAG, "callNumber exception: ", e); return false; } } public static String getTelephonyDeviceId(final Context ctx) { try { TelephonyManager tm = (TelephonyManager) ctx.getSystemService(Context.TELEPHONY_SERVICE); if (tm == null) { return ""; } return tm.getDeviceId(); } catch (final Throwable e) { Log.e(TAG, "getTelephonyDeviceId exception: ", e); return ""; } } public static String getDisplayCountry(final Context ctx) { try { return getDefaultLocale(ctx).getDisplayCountry(); } catch (final Throwable e) { Log.e(TAG, "getDisplayCountry exception: " + e); return ""; } } public static String getCountry(final Context ctx) { try { return getDefaultLocale(ctx).getCountry(); } catch (final Throwable e) { Log.e(TAG, "getCountry exception: " + e); return ""; } } // Settings.Secure.ANDROID_ID public static String getAndroidId(final Context ctx) { try { String androidId = Secure.getString(ctx.getContentResolver(), Secure.ANDROID_ID); // Ignore buggy device (also Android simulator's) device id if (!"9774d56d682e549c".equals(androidId)) { return androidId; } } catch (final Throwable e) { Log.e(TAG, "getAndroidId exception: ", e); } return ""; } // Warning: works on API >= 9 only! public static String getBuildSerial() { try { if (Build.VERSION.SDK_INT >= 9) { return Build.SERIAL; } } catch (final Throwable e) { Log.e(TAG, "getBuildSerial exception: ", e); } return ""; } public static String getInstalledAppsList(final Context ctx) { try { String result = new String(); List<PackageInfo> packs = ctx.getPackageManager().getInstalledPackages(0); for (int i = 0; i < packs.size(); ++i) { result += packs.get(i).packageName; result += "\n"; } return result; } catch (final Throwable e) { Log.e(TAG, "getInstalledAppsList exception: ", e); } return ""; } // Get user's default locale. private static Locale getDefaultLocale(final Context context) { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // API 24 / Android 7.0 return context.getResources().getConfiguration().getLocales().get(0); } else { return context.getResources().getConfiguration().locale; } } catch (final Throwable e) { Log.e(TAG, "getDefaultLocale exception: ", e); return Locale.getDefault(); } } public static String getDefaultLocaleName(final Context context) { try { return getDefaultLocale(context).toString(); } catch (final Throwable e) { Log.e(TAG, "getDefaultLocaleName exception: ", e); return ""; } } public static String getUserLocaleNames(final Context context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // API 24 / Android 7.0 try { final LocaleList list = context.getResources().getConfiguration().getLocales(); if (list.size() > 0) { String result = ""; for (int i = 0; i < list.size(); ++i) result += list.get(i).toString() + "\n"; return result; } } catch (final Throwable e) { Log.e(TAG, "getUserLocaleNames exception: ", e); } } return getDefaultLocaleName(context); } private static class ActivityInfo implements Comparable<ActivityInfo> { private @NonNull String mPackageName = ""; private @NonNull String mName = ""; ActivityInfo(final @NonNull String name, final @NonNull String packageName) { mName = name; mPackageName = packageName; } public String getPackageName() { return mPackageName; } public String getName() { return mName; } @Override public int compareTo(@NonNull ActivityInfo another) { final int packageNameCompare = mPackageName.compareTo(another.mPackageName); if (0 == packageNameCompare) { return mName.compareTo(another.mName); } return packageNameCompare; } } private static class IntentResolverInfo { private final PackageManager mPackageManager; final Set<ActivityInfo> mResolveInfoList = new TreeSet<>(); IntentResolverInfo(final PackageManager packageManager) { mPackageManager = packageManager; } public boolean isEmpty() { return getResolveInfos().isEmpty(); } Set<ActivityInfo> getResolveInfos() { return mResolveInfoList; } void appendResolvers(final Intent intent) { final List<ResolveInfo> resolveInfoList = mPackageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); for (final ResolveInfo resolveInfo : resolveInfoList) { mResolveInfoList .add(new ActivityInfo(resolveInfo.activityInfo.name, resolveInfo.activityInfo.packageName)); } } void removeSamePackages(final Set<ActivityInfo> resolveInfoSet) { for (final ActivityInfo resolveInfo : resolveInfoSet) { if (mResolveInfoList.isEmpty()) { break; } for (final Iterator<ActivityInfo> iterator = mResolveInfoList.iterator(); iterator.hasNext();) { final ActivityInfo storedResolveInfo = iterator.next(); if (storedResolveInfo.getPackageName().equalsIgnoreCase(resolveInfo.getPackageName())) { iterator.remove(); } } } } @Override public String toString() { return super.toString() + "; " + mResolveInfoList.toString(); } } }