Last active
November 11, 2024 08:44
-
-
Save hanpingchinese/025442f1d2f84a768c1468ead09f8d77 to your computer and use it in GitHub Desktop.
Helper class (and demo Activity) for calling Hanping Dictionary apps from third party apps
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.hanpingchinese.common; | |
import android.app.Activity; | |
import android.app.AlertDialog; | |
import android.content.ActivityNotFoundException; | |
import android.content.DialogInterface; | |
import android.content.Intent; | |
import android.content.pm.PackageManager; | |
import android.os.Bundle; | |
import android.util.Log; | |
import android.view.View; | |
import android.widget.ImageView; | |
import android.widget.TextView; | |
import androidx.appcompat.app.AppCompatActivity; | |
public class HanpingLauncherDemoActivity extends AppCompatActivity { | |
@Override | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_main); // other layouts are also available :) | |
if (!initHanpingLauncher()) { | |
// ask the user if they want to install Hanping | |
// note: a Snackbar might be less intrusive | |
AlertDialog.Builder builder = new AlertDialog.Builder(this); | |
builder.setTitle("Hanping Dictionary"); | |
builder.setMessage("Would you like to install Hanping?"); | |
builder.setPositiveButton("Install", new DialogInterface.OnClickListener() { | |
@Override | |
public void onClick(DialogInterface dialogInterface, int i) { | |
Intent installIntent = HanpingLauncherFactory.getMandarinAppInstallIntent(); | |
//Intent installIntent = HanpingLauncherFactory.getCantoneseAppInstallIntent(); | |
startActivity(installIntent); | |
// TODO: set up a broadcast receiver to listen to app installations | |
// so that when the Hanping app is installed, you could refresh the views by calling | |
// initHanpingLauncher() | |
// alternatively, show a message asking the user to restart the app when installation complete | |
// note: startActivityForResult() doesn't seem to help here | |
} | |
}); | |
builder.setNegativeButton("Not Now", null); | |
builder.show(); | |
} | |
} | |
private boolean initHanpingLauncher() { | |
PackageManager pm = getPackageManager(); | |
// alternatively use HanpingLauncher.getMandarinAppLauncher() or HanpingLauncher.getCantoneseAppLauncher() | |
final HanpingLauncherFactory.HanpingLauncher launcher = HanpingLauncherFactory.getAppLauncher(pm); | |
//final HanpingLauncherFactory.HanpingLauncher launcher = HanpingLauncherFactory.getMandarinAppLauncher(pm); | |
//final HanpingLauncherFactory.HanpingLauncher launcher = HanpingLauncherFactory.getCantoneseAppLauncher(pm); | |
if (launcher == null) { | |
// TODO: explicitly hide views in case the Hanping app has just been uninstalled | |
return false; | |
} | |
final Activity activity = this; | |
TextView labelView = findViewById(android.R.id.text1); // other ids are also available :) | |
labelView.setText(launcher.loadLabel(pm)); | |
ImageView iconView = findViewById(android.R.id.icon); // other ids are also available :) | |
iconView.setImageDrawable(launcher.loadIcon(pm)); | |
iconView.setOnClickListener(new View.OnClickListener() { | |
@Override | |
public void onClick(View view) { | |
// hardcoded Chinese word for demonstration purposes | |
String trad = "中國"; | |
String simp = "中国"; | |
String phonetic = "zhong1 guo2"; | |
try { | |
launcher.launch(trad, simp, phonetic, activity); | |
} catch (ActivityNotFoundException e) { | |
// Hanping was probably just uninstalled | |
Log.i("hanping", "Looks like Hanping was just uninstalled", e); | |
// so refresh views in case we can use a different Hanping app, or none at all | |
initHanpingLauncher(); | |
} | |
} | |
}); | |
return true; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.hanpingchinese.common; | |
import android.content.ActivityNotFoundException; | |
import android.content.Context; | |
import android.content.Intent; | |
import android.content.pm.ApplicationInfo; | |
import android.content.pm.PackageInfo; | |
import android.content.pm.PackageManager; | |
import android.graphics.drawable.Drawable; | |
import android.net.Uri; | |
import android.os.Build; | |
import android.util.Log; | |
import androidx.annotation.NonNull; | |
import androidx.annotation.Nullable; | |
public class HanpingLauncherFactory { | |
public static final String HANPING_CHINESE_PACKAGE = "com.embermitre.hanping.app.pro"; | |
public static final String HANPING_CANTONESE_PACKAGE = "com.embermitre.hanping.cantodict.app.pro"; | |
private static final String CMN_LANG_CODE = "cmn"; | |
private static final String YUE_LANG_CODE = "yue"; | |
private HanpingLauncherFactory() { | |
throw new IllegalStateException("this class is not intended to be instantiated"); | |
} | |
/** | |
* You probably want to call this when initiating the UI (and use it to get the icon) | |
* and hold onto the reference so that it can be used when launching Hanping. However, it's a pretty quick call | |
* anyway, so calling this each time the user clicks on the Hanping icon would also work fine. | |
* | |
* It does not hold on to the {@link PackageManager}. | |
* | |
* @return null iff there is no Hanping dict app installed | |
*/ | |
@Nullable | |
public static HanpingLauncher getAppLauncher(@NonNull PackageManager pm) { | |
HanpingLauncher result = getMandarinAppLauncher(pm); | |
if (result == null) { | |
result = getCantoneseAppLauncher(pm); | |
} | |
return result; | |
} | |
/** | |
* Best to call this no more than once per JVM. It does not hold on to the package manager. | |
* | |
* @return null iff neither mandarin dict app installed | |
*/ | |
@Nullable | |
public static HanpingLauncher getMandarinAppLauncher(@NonNull PackageManager pm) { | |
return createLauncherInternal(CMN_LANG_CODE, HANPING_CHINESE_PACKAGE, pm); | |
} | |
/** | |
* Best to call this no more than once per JVM. It does not hold on to the package manager. | |
* | |
* @return null iff cantonese dict app not installed | |
*/ | |
@Nullable | |
public static HanpingLauncher getCantoneseAppLauncher(@NonNull PackageManager pm) { | |
return createLauncherInternal(YUE_LANG_CODE, HANPING_CANTONESE_PACKAGE, pm); | |
} | |
@Nullable | |
private static HanpingLauncher createLauncherInternal(String langCode, String packageName, PackageManager pm) { | |
PackageInfo pi; | |
try { | |
pi = pm.getPackageInfo(packageName, 0); | |
} catch (PackageManager.NameNotFoundException e) { // this is thrown when the app is not installed | |
return null; | |
} | |
if (pi == null) { // probably never happens, but best check anyway | |
return null; | |
} | |
int versionCode = pi.versionCode; | |
if (versionCode < 906030000) { | |
Log.w("HanpingLauncherFactory", "Unsupported dictionary version: " + versionCode); | |
return null; | |
} | |
return new HanpingLauncherV2(langCode, pi.applicationInfo); | |
} | |
private static class HanpingLauncherV2 extends HanpingLauncher { | |
private final Uri mUriPrefix; | |
private HanpingLauncherV2(@NonNull String langCode, @NonNull ApplicationInfo ai) { | |
super(ai); | |
mUriPrefix = new Uri.Builder().scheme("hanping").appendEncodedPath(langCode).appendEncodedPath("word").build(); | |
} | |
@NonNull | |
@Override | |
protected Uri createIntentUri(@NonNull String wordKey) { | |
return mUriPrefix.buildUpon().appendPath(wordKey).build(); | |
} | |
} | |
public static abstract class HanpingLauncher { | |
private final ApplicationInfo mAppInfo; | |
protected HanpingLauncher(@NonNull ApplicationInfo ai) { | |
mAppInfo = ai; | |
} | |
@NonNull | |
protected abstract Uri createIntentUri(@NonNull String wordKey); | |
/** | |
* At least one of trad and simp must be non-null | |
* | |
* @param trad for example: 中國 | |
* @param simp for example: 中国 | |
* @param phonetic for example: zhong1 guo2 (note: ü should be written as v, for example: nv3 shi4) | |
* @return true iff the activity was started | |
*/ | |
public boolean launch(@Nullable String trad, @Nullable String simp, @Nullable String phonetic, @NonNull Context activityContext) { | |
if (trad == null && simp == null) { | |
throw new IllegalArgumentException("both trad and simp null"); | |
} | |
StringBuilder wordKeySb = new StringBuilder(); | |
if (trad != null) { | |
wordKeySb.append(trad); | |
} | |
wordKeySb.append('\u001f'); | |
if (simp != null) { | |
wordKeySb.append(simp); | |
} | |
if (phonetic != null) { | |
wordKeySb.append('\u001f'); // it's okay if this separator is not included | |
wordKeySb.append(phonetic); | |
} | |
Intent intent = new Intent(); | |
intent.setAction(Intent.ACTION_VIEW); | |
intent.setData(createIntentUri(wordKeySb.toString())); | |
intent.setPackage(mAppInfo.packageName); | |
int flags; | |
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { | |
flags = Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK; | |
} else { | |
flags = Intent.FLAG_ACTIVITY_NEW_DOCUMENT | Intent.FLAG_ACTIVITY_MULTIPLE_TASK; | |
} | |
intent.setFlags(flags); | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { | |
intent.putExtra(Intent.EXTRA_REFERRER, Uri.parse("android-app://" + activityContext.getPackageName())); | |
} | |
try { | |
activityContext.startActivity(intent); | |
return true; | |
} catch (ActivityNotFoundException e) { | |
// in the meantime user has uninstalled the app | |
return false; | |
} | |
} | |
@NonNull | |
public Drawable loadIcon(PackageManager pm) { | |
return mAppInfo.loadIcon(pm); | |
} | |
@NonNull | |
public CharSequence loadLabel(PackageManager pm) { | |
return mAppInfo.loadLabel(pm); | |
} | |
} | |
@NonNull | |
static Intent getAppInstallIntent(@NonNull String packageName) { | |
Uri uri = Uri.parse("market://details?id=" + packageName); | |
return new Intent(Intent.ACTION_VIEW, uri); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment