|
|
@ -46,20 +46,12 @@ import javax.net.ssl.X509TrustManager;
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
public class MemorizingTrustManager implements X509TrustManager {
|
|
|
|
public class MemorizingTrustManager implements X509TrustManager {
|
|
|
|
|
|
|
|
|
|
|
|
private final static String DECISION_INTENT = "de.duenndns.ssl.DECISION";
|
|
|
|
|
|
|
|
final static String DECISION_INTENT_ID = DECISION_INTENT + ".decisionId";
|
|
|
|
|
|
|
|
final static String DECISION_INTENT_CERT = DECISION_INTENT + ".cert";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private final static Logger LOGGER = Logger.getLogger(MemorizingTrustManager.class.getName());
|
|
|
|
|
|
|
|
final static String DECISION_TITLE_ID = DECISION_INTENT + ".titleId";
|
|
|
|
|
|
|
|
private final static int NOTIFICATION_ID = 100509;
|
|
|
|
private final static int NOTIFICATION_ID = 100509;
|
|
|
|
|
|
|
|
|
|
|
|
private final static String KEYSTORE_NAME = "keystore";
|
|
|
|
private final static String KEYSTORE_NAME = "keystore";
|
|
|
|
private final static String KEYSTORE_KEY = "keystore";
|
|
|
|
private final static String KEYSTORE_KEY = "keystore";
|
|
|
|
|
|
|
|
|
|
|
|
private Context context;
|
|
|
|
private Context context;
|
|
|
|
private Activity foregroundAct;
|
|
|
|
|
|
|
|
private NotificationManager notificationManager;
|
|
|
|
private NotificationManager notificationManager;
|
|
|
|
private static int decisionId = 0;
|
|
|
|
private static int decisionId = 0;
|
|
|
|
private static final SparseArray<MTMDecision> openDecisions = new SparseArray<>();
|
|
|
|
private static final SparseArray<MTMDecision> openDecisions = new SparseArray<>();
|
|
|
@ -143,38 +135,6 @@ public class MemorizingTrustManager implements X509TrustManager {
|
|
|
|
return new X509TrustManager[]{new MemorizingTrustManager(c)};
|
|
|
|
return new X509TrustManager[]{new MemorizingTrustManager(c)};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Binds an Activity to the MTM for displaying the query dialog.
|
|
|
|
|
|
|
|
* <p>
|
|
|
|
|
|
|
|
* This is useful if your connection is run from a service that is
|
|
|
|
|
|
|
|
* triggered by user interaction -- in such cases the activity is
|
|
|
|
|
|
|
|
* visible and the user tends to ignore the service notification.
|
|
|
|
|
|
|
|
* <p>
|
|
|
|
|
|
|
|
* You should never have a hidden activity bound to MTM! Use this
|
|
|
|
|
|
|
|
* function in onResume() and @see unbindDisplayActivity in onPause().
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* @param act Activity to be bound
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private void bindDisplayActivity(Activity act) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
foregroundAct = act;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Removes an Activity from the MTM display stack.
|
|
|
|
|
|
|
|
* <p>
|
|
|
|
|
|
|
|
* Always call this function when the Activity added with
|
|
|
|
|
|
|
|
* {@link #bindDisplayActivity(Activity)} is hidden.
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* @param act Activity to be unbound
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public void unbindDisplayActivity(Activity act) {
|
|
|
|
|
|
|
|
// do not remove if it was overridden by a different activity
|
|
|
|
|
|
|
|
if(foregroundAct == act) {
|
|
|
|
|
|
|
|
foregroundAct = null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Get a list of all certificate aliases stored in MTM.
|
|
|
|
* Get a list of all certificate aliases stored in MTM.
|
|
|
|
*
|
|
|
|
*
|
|
|
@ -365,6 +325,7 @@ public class MemorizingTrustManager implements X509TrustManager {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
e = e.getCause();
|
|
|
|
e = e.getCause();
|
|
|
|
} while(e != null);
|
|
|
|
} while(e != null);
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -374,17 +335,16 @@ public class MemorizingTrustManager implements X509TrustManager {
|
|
|
|
if(e instanceof CertPathValidatorException) {
|
|
|
|
if(e instanceof CertPathValidatorException) {
|
|
|
|
return true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
e = e.getCause();
|
|
|
|
e = e.getCause();
|
|
|
|
} while(e != null);
|
|
|
|
} while(e != null);
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void checkCertTrusted(X509Certificate[] chain, String authType, boolean isServer) throws CertificateException {
|
|
|
|
private void checkCertTrusted(X509Certificate[] chain, String authType, boolean isServer) throws CertificateException {
|
|
|
|
|
|
|
|
|
|
|
|
LOGGER.log(Level.FINE, "checkCertTrusted(" + Arrays.toString(chain) + ", " + authType + ", " + isServer + ")");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
LOGGER.log(Level.FINE, "checkCertTrusted: trying appTrustManager");
|
|
|
|
|
|
|
|
if(isServer) {
|
|
|
|
if(isServer) {
|
|
|
|
appTrustManager.checkServerTrusted(chain, authType);
|
|
|
|
appTrustManager.checkServerTrusted(chain, authType);
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -393,22 +353,15 @@ public class MemorizingTrustManager implements X509TrustManager {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch(CertificateException ae) {
|
|
|
|
catch(CertificateException ae) {
|
|
|
|
LOGGER.log(Level.FINER, "checkCertTrusted: appTrustManager did not verify certificate. Will fall back to secondary verification mechanisms (if any).", ae);
|
|
|
|
|
|
|
|
// if the cert is stored in our appTrustManager, we ignore expiredness
|
|
|
|
// if the cert is stored in our appTrustManager, we ignore expiredness
|
|
|
|
if(isExpiredException(ae)) {
|
|
|
|
if(isExpiredException(ae) || isCertKnown(chain[0])) {
|
|
|
|
LOGGER.log(Level.INFO, "checkCertTrusted: accepting expired certificate from keystore");
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if(isCertKnown(chain[0])) {
|
|
|
|
|
|
|
|
LOGGER.log(Level.INFO, "checkCertTrusted: accepting cert already stored in keystore");
|
|
|
|
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
if(defaultTrustManager == null) {
|
|
|
|
if(defaultTrustManager == null) {
|
|
|
|
LOGGER.fine("No defaultTrustManager set. Verification failed, throwing " + ae);
|
|
|
|
|
|
|
|
throw ae;
|
|
|
|
throw ae;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
LOGGER.log(Level.FINE, "checkCertTrusted: trying defaultTrustManager");
|
|
|
|
|
|
|
|
if(isServer) {
|
|
|
|
if(isServer) {
|
|
|
|
defaultTrustManager.checkServerTrusted(chain, authType);
|
|
|
|
defaultTrustManager.checkServerTrusted(chain, authType);
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -417,7 +370,6 @@ public class MemorizingTrustManager implements X509TrustManager {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch(CertificateException e) {
|
|
|
|
catch(CertificateException e) {
|
|
|
|
LOGGER.log(Level.FINER, "checkCertTrusted: defaultTrustManager failed", e);
|
|
|
|
|
|
|
|
interactCert(chain, authType, e);
|
|
|
|
interactCert(chain, authType, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -452,12 +404,14 @@ public class MemorizingTrustManager implements X509TrustManager {
|
|
|
|
private static String hexString(byte[] data) {
|
|
|
|
private static String hexString(byte[] data) {
|
|
|
|
|
|
|
|
|
|
|
|
StringBuilder si = new StringBuilder();
|
|
|
|
StringBuilder si = new StringBuilder();
|
|
|
|
|
|
|
|
|
|
|
|
for(int i = 0; i < data.length; i++) {
|
|
|
|
for(int i = 0; i < data.length; i++) {
|
|
|
|
si.append(String.format("%02x", data[i]));
|
|
|
|
si.append(String.format("%02x", data[i]));
|
|
|
|
if(i < data.length - 1) {
|
|
|
|
if(i < data.length - 1) {
|
|
|
|
si.append(":");
|
|
|
|
si.append(":");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return si.toString();
|
|
|
|
return si.toString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -473,91 +427,97 @@ public class MemorizingTrustManager implements X509TrustManager {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void certDetails(StringBuilder si, X509Certificate c) {
|
|
|
|
private static void certDetails(StringBuilder stringBuilder, X509Certificate c) {
|
|
|
|
|
|
|
|
|
|
|
|
SimpleDateFormat validityDateFormater = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
|
|
|
SimpleDateFormat validityDateFormater = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
|
|
|
si.append("\n");
|
|
|
|
|
|
|
|
si.append(c.getSubjectDN().toString());
|
|
|
|
stringBuilder.append("\n")
|
|
|
|
si.append("\n");
|
|
|
|
.append(c.getSubjectDN().toString())
|
|
|
|
si.append(validityDateFormater.format(c.getNotBefore()));
|
|
|
|
.append("\n")
|
|
|
|
si.append(" - ");
|
|
|
|
.append(validityDateFormater.format(c.getNotBefore()))
|
|
|
|
si.append(validityDateFormater.format(c.getNotAfter()));
|
|
|
|
.append(" - ")
|
|
|
|
si.append("\nSHA-256: ");
|
|
|
|
.append(validityDateFormater.format(c.getNotAfter()))
|
|
|
|
si.append(certHash(c, "SHA-256"));
|
|
|
|
.append("\nSHA-256: ")
|
|
|
|
si.append("\nSHA-1: ");
|
|
|
|
.append(certHash(c, "SHA-256"))
|
|
|
|
si.append(certHash(c, "SHA-1"));
|
|
|
|
.append("\nSHA-1: ")
|
|
|
|
si.append("\nSigned by: ");
|
|
|
|
.append(certHash(c, "SHA-1"))
|
|
|
|
si.append(c.getIssuerDN().toString());
|
|
|
|
.append("\nSigned by: ")
|
|
|
|
si.append("\n");
|
|
|
|
.append(c.getIssuerDN().toString())
|
|
|
|
|
|
|
|
.append("\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private String certChainMessage(final X509Certificate[] chain, CertificateException cause) {
|
|
|
|
private String certChainMessage(final X509Certificate[] chain, CertificateException cause) {
|
|
|
|
|
|
|
|
|
|
|
|
Throwable e = cause;
|
|
|
|
Throwable e = cause;
|
|
|
|
StringBuilder si = new StringBuilder();
|
|
|
|
StringBuilder stringBuilder = new StringBuilder();
|
|
|
|
|
|
|
|
|
|
|
|
if(isPathException(e)) {
|
|
|
|
if(isPathException(e)) {
|
|
|
|
si.append(context.getString(R.string.mtm_trust_anchor));
|
|
|
|
stringBuilder.append(context.getString(R.string.mtm_trust_anchor));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(isExpiredException(e)) {
|
|
|
|
else if(isExpiredException(e)) {
|
|
|
|
si.append(context.getString(R.string.mtm_cert_expired));
|
|
|
|
stringBuilder.append(context.getString(R.string.mtm_cert_expired));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
else {
|
|
|
|
// get to the cause
|
|
|
|
// get to the cause
|
|
|
|
while(e.getCause() != null)
|
|
|
|
while(e.getCause() != null) {
|
|
|
|
e = e.getCause();
|
|
|
|
e = e.getCause();
|
|
|
|
si.append(e.getLocalizedMessage());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
stringBuilder.append(e.getLocalizedMessage());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
si.append("\n\n");
|
|
|
|
stringBuilder.append("\n\n");
|
|
|
|
si.append(context.getString(R.string.mtm_connect_anyway));
|
|
|
|
stringBuilder.append(context.getString(R.string.mtm_connect_anyway));
|
|
|
|
si.append("\n\n");
|
|
|
|
stringBuilder.append("\n\n");
|
|
|
|
si.append(context.getString(R.string.mtm_cert_details));
|
|
|
|
stringBuilder.append(context.getString(R.string.mtm_cert_details));
|
|
|
|
|
|
|
|
|
|
|
|
for(X509Certificate c : chain) {
|
|
|
|
for(X509Certificate c : chain) {
|
|
|
|
certDetails(si, c);
|
|
|
|
certDetails(stringBuilder, c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return si.toString();
|
|
|
|
|
|
|
|
|
|
|
|
return stringBuilder.toString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private String hostNameMessage(X509Certificate cert, String hostname) {
|
|
|
|
private String hostNameMessage(X509Certificate cert, String hostname) {
|
|
|
|
|
|
|
|
|
|
|
|
StringBuilder si = new StringBuilder();
|
|
|
|
StringBuilder stringBuilder = new StringBuilder();
|
|
|
|
|
|
|
|
|
|
|
|
si.append(context.getString(R.string.mtm_hostname_mismatch, hostname));
|
|
|
|
stringBuilder.append(context.getString(R.string.mtm_hostname_mismatch, hostname));
|
|
|
|
si.append("\n\n");
|
|
|
|
stringBuilder.append("\n\n");
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
Collection<List<?>> sans = cert.getSubjectAlternativeNames();
|
|
|
|
Collection<List<?>> sans = cert.getSubjectAlternativeNames();
|
|
|
|
|
|
|
|
|
|
|
|
if(sans == null) {
|
|
|
|
if(sans == null) {
|
|
|
|
si.append(cert.getSubjectDN());
|
|
|
|
stringBuilder.append(cert.getSubjectDN());
|
|
|
|
si.append("\n");
|
|
|
|
stringBuilder.append("\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
else {
|
|
|
|
for(List<?> altName : sans) {
|
|
|
|
for(List<?> altName : sans) {
|
|
|
|
Object name = altName.get(1);
|
|
|
|
Object name = altName.get(1);
|
|
|
|
if(name instanceof String) {
|
|
|
|
if(name instanceof String) {
|
|
|
|
si.append("[");
|
|
|
|
stringBuilder.append("[");
|
|
|
|
si.append(altName.get(0));
|
|
|
|
stringBuilder.append(altName.get(0));
|
|
|
|
si.append("] ");
|
|
|
|
stringBuilder.append("] ");
|
|
|
|
si.append(name);
|
|
|
|
stringBuilder.append(name);
|
|
|
|
si.append("\n");
|
|
|
|
stringBuilder.append("\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch(CertificateParsingException e) {
|
|
|
|
catch(CertificateParsingException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
e.printStackTrace();
|
|
|
|
si.append("<Parsing error: ");
|
|
|
|
stringBuilder.append("<Parsing error: ");
|
|
|
|
si.append(e.getLocalizedMessage());
|
|
|
|
stringBuilder.append(e.getLocalizedMessage());
|
|
|
|
si.append(">\n");
|
|
|
|
stringBuilder.append(">\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
si.append("\n");
|
|
|
|
stringBuilder.append("\n");
|
|
|
|
si.append(context.getString(R.string.mtm_connect_anyway));
|
|
|
|
stringBuilder.append(context.getString(R.string.mtm_connect_anyway));
|
|
|
|
si.append("\n\n");
|
|
|
|
stringBuilder.append("\n\n");
|
|
|
|
si.append(context.getString(R.string.mtm_cert_details));
|
|
|
|
stringBuilder.append(context.getString(R.string.mtm_cert_details));
|
|
|
|
certDetails(si, cert);
|
|
|
|
certDetails(stringBuilder, cert);
|
|
|
|
return si.toString();
|
|
|
|
return stringBuilder.toString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
@ -584,29 +544,24 @@ public class MemorizingTrustManager implements X509TrustManager {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
|
|
|
|
|
|
|
private void startActivityNotification(Intent intent, int decisionId, String certName) {
|
|
|
|
private void startActivityNotification(Intent intent, int decisionId, String certName) {
|
|
|
|
|
|
|
|
|
|
|
|
final PendingIntent call = PendingIntent.getActivity(context, 0, intent, 0);
|
|
|
|
final PendingIntent call = PendingIntent.getActivity(context, 0, intent, 0);
|
|
|
|
final String mtmNotification = context.getString(R.string.mtm_notification);
|
|
|
|
final String mtmNotification = context.getString(R.string.mtm_notification);
|
|
|
|
|
|
|
|
|
|
|
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "ssl").setSmallIcon(android.R.drawable.ic_lock_lock).setContentTitle(mtmNotification).setContentText(certName).setTicker(certName).setContentIntent(call).setAutoCancel(true).setPriority(NotificationCompat.PRIORITY_HIGH);
|
|
|
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "ssl")
|
|
|
|
|
|
|
|
.setSmallIcon(android.R.drawable.ic_lock_lock)
|
|
|
|
|
|
|
|
.setContentTitle(mtmNotification)
|
|
|
|
|
|
|
|
.setContentText(certName)
|
|
|
|
|
|
|
|
.setTicker(certName)
|
|
|
|
|
|
|
|
.setContentIntent(call)
|
|
|
|
|
|
|
|
.setAutoCancel(true)
|
|
|
|
|
|
|
|
.setPriority(NotificationCompat.PRIORITY_HIGH);
|
|
|
|
|
|
|
|
|
|
|
|
notificationManager.notify(NOTIFICATION_ID + decisionId, builder.build());
|
|
|
|
notificationManager.notify(NOTIFICATION_ID + decisionId, builder.build());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Returns the top-most entry of the activity stack.
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* @return the Context of the currently bound UI or the master context if none is bound
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
Context getUI() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return (foregroundAct != null) ? foregroundAct : context;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private int interact(final String message, final int titleId) {
|
|
|
|
private int interact(final String message, final int titleId) {
|
|
|
|
/* prepare the MTMDecision blocker object */
|
|
|
|
|
|
|
|
MTMDecision choice = new MTMDecision();
|
|
|
|
MTMDecision choice = new MTMDecision();
|
|
|
|
final int myId = createDecisionId(choice);
|
|
|
|
final int myId = createDecisionId(choice);
|
|
|
|
|
|
|
|
|
|
|
@ -615,19 +570,14 @@ public class MemorizingTrustManager implements X509TrustManager {
|
|
|
|
public void run() {
|
|
|
|
public void run() {
|
|
|
|
|
|
|
|
|
|
|
|
Intent intent = new Intent(context, MemorizingActivity.class);
|
|
|
|
Intent intent = new Intent(context, MemorizingActivity.class);
|
|
|
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
|
|
intent.setData(Uri.parse(MemorizingTrustManager.class.getName() + "/" + myId));
|
|
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
|
|
|
|
intent.putExtra(DECISION_INTENT_ID, myId);
|
|
|
|
intent.putExtra("DECISION_INTENT_ID", myId);
|
|
|
|
intent.putExtra(DECISION_INTENT_CERT, message);
|
|
|
|
intent.putExtra("DECISION_INTENT_CERT", message);
|
|
|
|
intent.putExtra(DECISION_TITLE_ID, titleId);
|
|
|
|
intent.putExtra("DECISION_TITLE_ID", titleId);
|
|
|
|
|
|
|
|
|
|
|
|
// we try to directly start the activity and fall back to
|
|
|
|
|
|
|
|
// making a notification. If no foreground activity is set
|
|
|
|
|
|
|
|
// (foregroundAct==null) or if the app developer set an
|
|
|
|
|
|
|
|
// invalid / expired activity, the catch-all fallback is
|
|
|
|
|
|
|
|
// deployed.
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
foregroundAct.startActivity(intent);
|
|
|
|
context.startActivity(intent);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch(Exception e) {
|
|
|
|
catch(Exception e) {
|
|
|
|
startActivityNotification(intent, myId, message);
|
|
|
|
startActivityNotification(intent, myId, message);
|
|
|
|