package org.wikipedia.zero;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.NotificationCompat;
import org.apache.commons.lang3.StringUtils;
import org.wikipedia.R;
import org.wikipedia.WikipediaApp;
import org.wikipedia.analytics.WikipediaZeroUsageFunnel;
import org.wikipedia.dataclient.WikiSite;
import org.wikipedia.events.WikipediaZeroEnterEvent;
import org.wikipedia.main.MainActivity;
import org.wikipedia.settings.Prefs;
import org.wikipedia.util.log.L;
import okhttp3.Headers;
import retrofit2.Call;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.wikipedia.util.UriUtil.visitInExternalBrowser;
public class WikipediaZeroHandler {
private static final int NOTIFICATION_ID = 100;
private static final int MESSAGE_ZERO_CS = 1;
@NonNull private WikipediaApp app;
private boolean zeroEnabled = false;
private volatile boolean acquiringCarrierMessage = false;
@Nullable private ZeroConfig zeroConfig;
@NonNull private WikipediaZeroUsageFunnel zeroFunnel;
@NonNull private String zeroCarrierString = "";
@NonNull private String zeroCarrierMetaString = "";
// Tracks the X-Carrier header (if any) outside of the Zero state machine.
// This is for reference in the rare cases where a user is potentially eligible
// for zero-rating based on IP, only certain language wikis are whitelisted and
// the user isn't visiting one of them.
@NonNull private String xCarrier = "";
public WikipediaZeroHandler(@NonNull WikipediaApp app) {
this.app = app;
this.zeroFunnel = new WikipediaZeroUsageFunnel(app, "", "");
}
public boolean isZeroEnabled() {
return zeroEnabled;
}
@Nullable
public ZeroConfig getZeroConfig() {
return zeroConfig;
}
@NonNull
public WikipediaZeroUsageFunnel getZeroFunnel() {
return zeroFunnel;
}
public String getXCarrier() {
return xCarrier;
}
public static void showZeroExitInterstitialDialog(@NonNull final Context context,
@NonNull final Uri uri) {
final WikipediaZeroHandler zeroHandler = WikipediaApp.getInstance()
.getWikipediaZeroHandler();
final ZeroConfig zeroConfig = zeroHandler.getZeroConfig();
if (zeroConfig == null) {
return;
}
final String customExitTitle = zeroConfig.getExitTitle();
final String customExitWarning = zeroConfig.getExitWarning();
final String customPartnerInfoText = zeroConfig.getPartnerInfoText();
final Uri customPartnerInfoUrl = zeroConfig.getPartnerInfoUrl();
AlertDialog.Builder alert = new AlertDialog.Builder(context);
alert.setTitle(!StringUtils.isEmpty(customExitTitle) ? customExitTitle
: context.getString(R.string.zero_interstitial_title));
alert.setMessage(!StringUtils.isEmpty(customExitWarning) ? customExitWarning
: context.getString(R.string.zero_interstitial_leave_app));
alert.setPositiveButton(context.getString(R.string.zero_interstitial_continue),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
visitInExternalBrowser(context, uri);
zeroHandler.getZeroFunnel().logExtLinkConf();
}
});
if (customPartnerInfoText != null && customPartnerInfoUrl != null) {
alert.setNeutralButton(customPartnerInfoText, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
visitInExternalBrowser(context, customPartnerInfoUrl);
}
});
}
alert.setNegativeButton(context.getString(R.string.zero_interstitial_cancel),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
zeroHandler.getZeroFunnel().logExtLinkBack();
}
});
AlertDialog ad = alert.create();
ad.show();
zeroHandler.getZeroFunnel().logExtLinkWarn();
}
public void onHeaderCheck(@NonNull Headers headers) {
if (acquiringCarrierMessage) {
return;
}
final String xCarrierFromHeader = getHeader(headers, "X-Carrier");
final String xCarrierMetaFromHeader = StringUtils.defaultString(getHeader(headers,
"X-Carrier-Meta"));
// newHeader may be true but must still be compared against SharedPreferences
final boolean newHeader = xCarrierFromHeader != null
&& eitherChanged(xCarrierFromHeader, xCarrierMetaFromHeader);
boolean transitioningOff = zeroEnabled && xCarrierFromHeader == null;
if (!newHeader && !transitioningOff) {
return;
}
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override public void run() {
try {
if (newHeader) {
identifyZeroCarrier(xCarrierFromHeader, xCarrierMetaFromHeader);
} else {
zeroOff();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
}
public void showZeroTutorialDialog(@NonNull Activity activity) {
new AlertDialog.Builder(activity)
.setTitle(R.string.zero_wikipedia_zero_heading)
.setMessage(R.string.zero_learn_more)
.setPositiveButton(R.string.zero_learn_more_dismiss, null)
.setNegativeButton(R.string.zero_learn_more_learn_more,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
visitInExternalBrowser(app,
(Uri.parse(app.getString(R.string.zero_webpage_url))));
zeroFunnel.logExtLinkMore();
}
}).create().show();
}
private boolean eitherChanged(String xCarrier, String xCarrierMeta) {
return !(xCarrier.equals(zeroCarrierString) && xCarrierMeta.equals(zeroCarrierMetaString));
}
private void identifyZeroCarrier(@NonNull final String xCarrierFromHeader,
@NonNull final String xCarrierMetaFromHeader) {
Handler wikipediaZeroHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
new ZeroConfigClient().request(new WikiSite(app.getWikiSite().mobileHost()),
app.getUserAgent(), new ZeroConfigClient.Callback() {
@Override
public void success(@NonNull Call<ZeroConfig> call, @NonNull ZeroConfig config) {
L.i("New Wikipedia Zero config: " + config);
if (!config.isEligible()) {
acquiringCarrierMessage = false;
return;
}
xCarrier = xCarrierFromHeader; // ex. "123-45"
zeroCarrierString = xCarrierFromHeader;
zeroCarrierMetaString = xCarrierMetaFromHeader; // ex. "wap"; default ""
zeroConfig = config;
zeroEnabled = true;
zeroFunnel = new WikipediaZeroUsageFunnel(app, zeroCarrierString,
defaultString(zeroCarrierMetaString));
app.getBus().post(new WikipediaZeroEnterEvent());
if (zeroConfig.hashCode() != Prefs.zeroConfigHashCode()) {
notifyEnterZeroNetwork(app, zeroConfig);
}
Prefs.zeroConfigHashCode(zeroConfig.hashCode());
acquiringCarrierMessage = false;
}
@Override
public void failure(@NonNull Call<ZeroConfig> call, @NonNull Throwable caught) {
L.w("Wikipedia Zero eligibility check failed", caught);
acquiringCarrierMessage = false;
}
});
acquiringCarrierMessage = true;
return true;
}
});
wikipediaZeroHandler.removeMessages(MESSAGE_ZERO_CS);
Message zeroMessage = Message.obtain();
zeroMessage.what = MESSAGE_ZERO_CS;
zeroMessage.obj = "zero_eligible_check";
wikipediaZeroHandler.sendMessage(zeroMessage);
}
private void zeroOff() {
zeroCarrierString = "";
zeroCarrierMetaString = "";
zeroConfig = null;
zeroEnabled = false;
Prefs.zeroConfigHashCode(0);
notifyExitZeroNetwork(app);
}
@Nullable
private String getHeader(@NonNull Headers headers, @NonNull String key) {
for (String name: headers.names()) {
if (key.equalsIgnoreCase(name)) {
return headers.get(name);
}
}
return null;
}
private void notifyEnterZeroNetwork(@NonNull Context context, @NonNull ZeroConfig config) {
NotificationCompat.Builder builder = createNotification(context);
builder.setColor(config.getBackground())
.setSmallIcon(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
? R.drawable.ic_wikipedia_zero_on : R.mipmap.launcher)
.setLights(config.getBackground(),
context.getResources().getInteger(R.integer.zero_notification_light_on_ms),
context.getResources().getInteger(R.integer.zero_notification_light_off_ms))
.setContentText(context.getString(R.string.zero_learn_more))
.setStyle(new NotificationCompat.BigTextStyle().bigText(context.getString(R.string.zero_learn_more)))
.addAction(0, context.getString(R.string.zero_learn_more_learn_more),
pendingIntentForUrl(context, context.getString(R.string.zero_webpage_url)));
showNotification(context, builder.build());
}
private void notifyExitZeroNetwork(@NonNull Context context) {
NotificationCompat.Builder builder = createNotification(context);
builder.setColor(ContextCompat.getColor(context, R.color.foundation_red))
.setSmallIcon(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
? R.drawable.ic_wikipedia_zero_off : R.mipmap.launcher)
.setContentText(context.getString(R.string.zero_charged_verbiage))
.setAutoCancel(true)
.addAction(0, context.getString(R.string.zero_learn_more_learn_more),
pendingIntentForUrl(context, context.getString(R.string.zero_webpage_url)));
showNotification(context, builder.build());
}
private NotificationCompat.Builder createNotification(@NonNull Context context) {
return (NotificationCompat.Builder) new NotificationCompat.Builder(context)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setContentTitle(context.getString(R.string.zero_wikipedia_zero_heading))
.setContentIntent(PendingIntent
.getActivity(context, 0, new Intent(context, MainActivity.class), 0));
}
private void showNotification(@NonNull Context context, @NonNull Notification notification) {
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(NOTIFICATION_ID, notification);
}
private PendingIntent pendingIntentForUrl(@NonNull Context context, @NonNull String url) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
}