Skip to content

Instantly share code, notes, and snippets.

@hanpingchinese
Last active November 11, 2024 08:44
Show Gist options
  • Save hanpingchinese/025442f1d2f84a768c1468ead09f8d77 to your computer and use it in GitHub Desktop.
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
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;
}
}
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