package com.pitchedapps.butler.iconrequest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.XmlResourceParser;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.CallSuper;
import android.support.annotation.CheckResult;
import android.support.annotation.IntDef;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import android.support.annotation.XmlRes;
import android.text.Html;
import com.pitchedapps.butler.R;
import com.pitchedapps.butler.iconrequest.events.AppLoadedEvent;
import com.pitchedapps.butler.iconrequest.events.AppLoadingEvent;
import com.pitchedapps.butler.iconrequest.events.AppSelectionEvent;
import com.pitchedapps.butler.iconrequest.events.EventState;
import com.pitchedapps.butler.iconrequest.events.OnRequestProgress;
import com.pitchedapps.butler.iconrequest.events.RequestEvent;
import com.pitchedapps.butler.iconrequest.events.RequestsCallback;
import com.pitchedapps.butler.iconrequest.logs.IRLog;
import com.pitchedapps.butler.iconrequest.logs.IRLogTree;
import com.pitchedapps.butler.iconrequest.utils.ComponentInfoUtil;
import com.pitchedapps.butler.iconrequest.utils.EventBusUtils;
import com.pitchedapps.butler.iconrequest.utils.FileUtil;
import com.pitchedapps.butler.iconrequest.utils.IRUtils;
import com.pitchedapps.butler.iconrequest.utils.ZipUtil;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import timber.log.Timber;
/**
* Created by Allan Wang on 2016-08-20.
*/
public final class IconRequest {
@IntDef({STATE_NORMAL, STATE_LIMITED, STATE_TIME_LIMITED})
@Retention(RetentionPolicy.SOURCE)
public @interface State {
}
public static final int STATE_NORMAL = 0;
public static final int STATE_LIMITED = 1;
public static final int STATE_TIME_LIMITED = 2;
public static final int INTENT_CODE = 99;
@State
private int state = STATE_NORMAL;
private Builder mBuilder;
private ArrayList<App> mApps;
private ArrayList<App> mSelectedApps;
private static final String KEY_SAVED_TIME_MILLIS = "saved_time_millis";
private static final String MAX_APPS = "apps_to_request";
private static IconRequest mRequest;
private IconRequest() {
mSelectedApps = new ArrayList<>();
}
private IconRequest(@NonNull Builder builder) {
this();
mBuilder = builder;
mRequest = this;
if (mBuilder.mDebugMode) {
Timber.plant(new IRLogTree());
}
}
public static class Builder implements Parcelable {
protected transient Context mContext;
protected File mSaveDir = null;
protected int mFilterId = -1;
protected String mAppName = "Default App";
protected String mEmail = null;
protected String mSubject = "Icon Request";
protected String mHeader = "These apps aren't themed. Thanks in advance";
protected String mFooter = null;
protected int mMaxCount = 0;
protected long mTimeLimit = -1;
protected boolean mIsLoading = false;
protected boolean mHasMaxCount = false;
protected boolean mNoneSelectsAll = false;
protected boolean mIncludeDeviceInfo = true;
protected boolean mComments = true;
protected boolean mGenerateAppFilterXml = true;
protected boolean mGenerateAppMapXml = true;
protected boolean mGenerateThemeResourcesXml = true;
protected boolean mGenerateAppFilterJson = false;
protected boolean mErrorOnInvalidAppFilterDrawable = true;
protected boolean mDebugMode = false;
protected SharedPreferences mPrefs = null;
protected RequestsCallback mCallback = null;
protected EventState mLoadingState = EventState.DISABLED,
mLoadedState = EventState.STICKIED,
mSelectionState = EventState.DISABLED,
mRequestState = EventState.DISABLED;
public Builder() {
}
public Builder(@NonNull Context context) {
mContext = context;
mSaveDir = new File(Environment.getExternalStorageDirectory(), "IconRequest");
}
public Builder filterXmlId(@XmlRes int resId) {
mFilterId = resId;
return this;
}
public Builder filterOff() {
mFilterId = -1;
return this;
}
public Builder saveDir(@NonNull File file) {
mSaveDir = file;
return this;
}
public Builder toEmail(@NonNull String email) {
mEmail = email;
return this;
}
public Builder withAppName(@Nullable String appName, @Nullable Object... args) {
if (args != null && appName != null)
appName = String.format(appName, args);
mAppName = appName;
return this;
}
public Builder withSubject(@Nullable String subject, @Nullable Object... args) {
if (args != null && subject != null)
subject = String.format(subject, args);
mSubject = subject;
return this;
}
public Builder withHeader(@Nullable String header, @Nullable Object... args) {
if (args != null && header != null)
header = String.format(header, args);
mHeader = header;
return this;
}
public Builder withFooter(@Nullable String footer, @Nullable Object... args) {
if (args != null && footer != null)
footer = String.format(footer, args);
mFooter = footer;
return this;
}
public Builder maxSelectionCount(@IntRange(from = 0) int count) {
mMaxCount = count;
mHasMaxCount = mMaxCount > 0;
return this;
}
public Builder withTimeLimit(int minutes, SharedPreferences prefs) {
mTimeLimit = TimeUnit.MINUTES.toMillis(minutes);
mPrefs = prefs != null ? prefs : mContext.getSharedPreferences("ButlerPrefs", Context
.MODE_PRIVATE);
return this;
}
public Builder withComments(boolean b) {
mComments = b;
return this;
}
public Builder noSelectionSelectsAll(boolean b) {
mNoneSelectsAll = b;
return this;
}
public Builder includeDeviceInfo(boolean include) {
mIncludeDeviceInfo = include;
return this;
}
public Builder generateAppFilterXml(boolean generate) {
mGenerateAppFilterXml = generate;
return this;
}
public Builder generateAppMapXml(boolean generate) {
mGenerateAppMapXml = generate;
return this;
}
public Builder generateThemeResourcesXml(boolean generate) {
mGenerateThemeResourcesXml = generate;
return this;
}
public Builder generateAppFilterJson(boolean generate) {
mGenerateAppFilterJson = generate;
return this;
}
public Builder errorOnInvalidFilterDrawable(boolean error) {
mErrorOnInvalidAppFilterDrawable = error;
return this;
}
public Builder debugMode(boolean debug) {
mDebugMode = debug;
return this;
}
public Builder loadingEvents(EventState state) {
mLoadingState = state;
return this;
}
public Builder loadedEvents(EventState state) {
mLoadedState = state;
return this;
}
public Builder selectionEvents(EventState state) {
mSelectionState = state;
return this;
}
public Builder requestEvents(EventState state) {
mRequestState = state;
return this;
}
public Builder setCallback(RequestsCallback callback) {
mCallback = callback;
return this;
}
public IconRequest build() {
return new IconRequest(this);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeSerializable(mSaveDir);
dest.writeInt(mFilterId);
dest.writeString(mAppName);
dest.writeString(mEmail);
dest.writeString(mSubject);
dest.writeString(mHeader);
dest.writeString(mFooter);
dest.writeInt(mMaxCount);
dest.writeLong(mTimeLimit);
dest.writeByte((byte) (mIsLoading ? 1 : 0));
dest.writeByte((byte) (mHasMaxCount ? 1 : 0));
dest.writeByte((byte) (mNoneSelectsAll ? 1 : 0));
dest.writeByte((byte) (mIncludeDeviceInfo ? 1 : 0));
dest.writeByte((byte) (mComments ? 1 : 0));
dest.writeByte((byte) (mGenerateAppFilterXml ? 1 : 0));
dest.writeByte((byte) (mGenerateAppMapXml ? 1 : 0));
dest.writeByte((byte) (mGenerateThemeResourcesXml ? 1 : 0));
dest.writeByte((byte) (mGenerateAppFilterJson ? 1 : 0));
dest.writeByte((byte) (mErrorOnInvalidAppFilterDrawable ? 1 : 0));
dest.writeByte((byte) (mDebugMode ? 1 : 0));
dest.writeInt(mLoadingState == null ? -1 : mLoadingState.ordinal());
dest.writeInt(mLoadedState == null ? -1 : mLoadedState.ordinal());
dest.writeInt(mSelectionState == null ? -1 : mSelectionState.ordinal());
dest.writeInt(mRequestState == null ? -1 : mRequestState.ordinal());
}
protected Builder(Parcel in) {
mSaveDir = (File) in.readSerializable();
mFilterId = in.readInt();
mAppName = in.readString();
mEmail = in.readString();
mSubject = in.readString();
mHeader = in.readString();
mFooter = in.readString();
mMaxCount = in.readInt();
mTimeLimit = in.readLong();
mIsLoading = in.readByte() != 0;
mHasMaxCount = in.readByte() != 0;
mNoneSelectsAll = in.readByte() != 0;
mIncludeDeviceInfo = in.readByte() != 0;
mComments = in.readByte() != 0;
mGenerateAppFilterXml = in.readByte() != 0;
mGenerateAppMapXml = in.readByte() != 0;
mGenerateThemeResourcesXml = in.readByte() != 0;
mGenerateAppFilterJson = in.readByte() != 0;
mErrorOnInvalidAppFilterDrawable = in.readByte() != 0;
mDebugMode = in.readByte() != 0;
int tmpMLoadingState = in.readInt();
mLoadingState = tmpMLoadingState == -1 ? null : EventState.values()[tmpMLoadingState];
int tmpMLoadedState = in.readInt();
mLoadedState = tmpMLoadedState == -1 ? null : EventState.values()[tmpMLoadedState];
int tmpMSelectionState = in.readInt();
mSelectionState = tmpMSelectionState == -1 ? null : EventState.values()
[tmpMSelectionState];
int tmpMRequestState = in.readInt();
mRequestState = tmpMRequestState == -1 ? null : EventState.values()[tmpMRequestState];
}
public static final Creator<Builder> CREATOR = new Creator<Builder>() {
@Override
public Builder createFromParcel(Parcel in) {
return new Builder(in);
}
@Override
public Builder[] newArray(int size) {
return new Builder[size];
}
};
}
public static Builder start(Context context) {
return new Builder(context);
}
public static IconRequest get() {
return mRequest;
}
private StringBuilder mInvalidDrawables;
@CallSuper
@CheckResult
@Nullable
private HashSet<String> loadFilterApps() {
IRUtils.startTimer("LFAXML");
final HashSet<String> defined = new HashSet<>();
if (mBuilder.mFilterId == -1) { //TODO add this
IRUtils.stopTimer("LFAXML");
return defined;
}
XmlResourceParser parser = null;
try {
parser = mBuilder.mContext.getResources().getXml(mBuilder.mFilterId);
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
String mAppCode;
switch (eventType) {
case XmlPullParser.START_TAG:
final String tagName = parser.getName();
if (tagName.equals("item")) {
try {
// Read package and activity name
String component = parser.getAttributeValue(null, "component");
if (!(component.startsWith(":"))) {
mAppCode = component.substring(14, component.length() - 1);
//wrapped in ComponentInfo{[Component]} TODO add checker?
//TODO check for valid drawable
// Add new info to our ArrayList and reset the object.
defined.add(mAppCode);
}
} catch (Exception e) {
IRLog.d("Error adding parsed appfilter item! Due to Exception: "
+ e.getMessage());
}
}
break;
}
eventType = parser.next();
}
} catch (XmlPullParserException | IOException e) {
e.printStackTrace();
} finally {
if (parser != null)
parser.close();
}
IRUtils.stopTimer("LFAXML");
return defined;
}
// @CheckResult
// @Nullable
// private HashSet<String> loadFilterApps2() {
// IRUtils.startTimer("LFAReader");
// final HashSet<String> defined = new HashSet<>();
// if (IRUtils.isEmpty(mBuilder.mFilterName)) {
// IRUtils.stopTimer("LFAReader");
// return defined;
// }
//
// InputStream is;
// try {
// final AssetManager am = mBuilder.mContext.getAssets();
// IRLog.d("Loading your appfilter, opening: %s", mBuilder.mFilterName);
// is = am.open(mBuilder.mFilterName);
// } catch (final Throwable e) {
// e.printStackTrace();
// mBuilder.mIsLoading = false;
// EventBusUtils.post(new AppLoadedEvent(null, new Exception("Failed to open your
// filter: " + e.getLocalizedMessage(), e)), mBuilder.mLoadedState);
// IRUtils.stopTimer("LFAReader");
// return null;
// }
//
// BufferedReader reader = new BufferedReader(new InputStreamReader(is));
// try {
// final String itemEndStr = "/>";
// final String componentStartStr = "component=\"ComponentInfo";
// final String drawableStartStr = "drawable=\"";
// final String endStr = "\"";
// final String commentStart = "<!--";
// final String commentEnd = "-->";
//
// String component = null;
// String drawable = null;
//
// String line;
// boolean inComment = false;
//
// while ((line = reader.readLine()) != null) {
// final String trimmedLine = line.trim();
// if (!inComment && trimmedLine.startsWith(commentStart)) {
// inComment = true;
// }
// if (inComment && trimmedLine.endsWith(commentEnd)) {
// inComment = false;
// continue;
// }
//
// if (inComment) continue;
// int start;
// int end;
//
// start = line.indexOf(componentStartStr);
// if (start != -1) {
// start += componentStartStr.length();
// end = line.indexOf(endStr, start);
// String ci = line.substring(start, end);
// if (ci.startsWith("{"))
// ci = ci.substring(1);
// if (ci.endsWith("}"))
// ci = ci.substring(0, ci.length() - 1);
// component = ci;
// }
//
// start = line.indexOf(drawableStartStr);
// if (start != -1) {
// start += drawableStartStr.length();
// end = line.indexOf(endStr, start);
// drawable = line.substring(start, end);
// }
//
// start = line.indexOf(itemEndStr);
// if (start != -1 && (component != null || drawable != null)) {
// IRLog.d("Found: %s (%s)", component, drawable);
// if (drawable == null || drawable.trim().isEmpty()) {
// IRLog.d("WARNING: Drawable shouldn't be null.");
// if (mBuilder.mErrorOnInvalidAppFilterDrawable) {
// if (mInvalidDrawables == null)
// mInvalidDrawables = new StringBuilder();
// if (mInvalidDrawables.length() > 0) mInvalidDrawables.append("\n");
// mInvalidDrawables.append(String.format("Drawable for %s was null or
// empty.\n", component));
// }
// } else if (mBuilder.mContext != null) {
// final Resources r = mBuilder.mContext.getResources();
// int identifier;
// try {
// identifier = r.getIdentifier(drawable, "drawable", mBuilder
// .mContext.getPackageName());
// } catch (Throwable t) {
// identifier = 0;
// }
// if (identifier == 0) {
// IRLog.d("WARNING: Drawable %s (for %s) doesn't match up with a
// resource.", drawable, component);
// if (mBuilder.mErrorOnInvalidAppFilterDrawable) {
// if (mInvalidDrawables == null)
// mInvalidDrawables = new StringBuilder();
// if (mInvalidDrawables.length() > 0) mInvalidDrawables.append
// ("\n");
// mInvalidDrawables.append(String.format("Drawable %s (for %s)
// doesn't match up with a resource.\n", drawable, component));
// }
// }
// }
// defined.add(component);
// }
// }
//
// if (mInvalidDrawables != null && mInvalidDrawables.length() > 0 &&
// mBuilder.mErrorOnInvalidAppFilterDrawable) {
// mBuilder.mIsLoading = false;
// EventBusUtils.post(new AppLoadedEvent(null, new Exception(mInvalidDrawables
// .toString())), mBuilder.mLoadedState);
// mInvalidDrawables.setLength(0);
// mInvalidDrawables.trimToSize();
// mInvalidDrawables = null;
// }
// IRLog.d("Found %d total app(s) in your appfilter.", defined.size());
// } catch (final Throwable e) {
// e.printStackTrace();
// mBuilder.mIsLoading = false;
// EventBusUtils.post(new AppLoadedEvent(null, new Exception("Failed to read your
// filter: " + e.getMessage(), e)), mBuilder.mLoadedState);
//
// IRUtils.stopTimer("LFAReader");
// return null;
// } finally {
// FileUtil.closeQuietely(reader);
// FileUtil.closeQuietely(is);
// }
// IRUtils.stopTimer("LFAReader");
// return defined;
// }
public int getMaxSelectable() {
return mBuilder.mMaxCount;
}
public void loadApps() {
mBuilder.mIsLoading = true;
EventBusUtils.post(new AppLoadingEvent(-2), mBuilder.mLoadingState);
new Thread(new Runnable() {
@Override
public void run() {
if (mBuilder.mDebugMode) IRUtils.startTimer("IR_debug_auto");
final HashSet<String> filter = loadFilterApps();
if (filter == null) return;
IRLog.d("Loading unthemed installed apps...");
mApps = ComponentInfoUtil.getInstalledApps(mBuilder.mContext, filter, mBuilder
.mLoadingState);
if (mBuilder.mDebugMode) IRUtils.stopTimer("IR_debug_auto");
mBuilder.mIsLoading = false;
EventBusUtils.post(new AppLoadedEvent(mApps, null), mBuilder.mLoadedState);
}
}).start();
}
public void loadHighResIcons() {
if (mApps == null) {
IRLog.d("High res load failed; app list is empty");
return;
}
new Thread(new Runnable() {
@Override
public void run() {
IRLog.d("Getting high res icons for all apps...");
for (App app : mApps) {
app.getHighResIcon(mBuilder.mContext);
}
IRLog.d("High res icon retrieval finished...");
}
}).start();
}
@SuppressWarnings("MalformedFormatString")
private String getBody() {
StringBuilder sb = new StringBuilder();
if (!IRUtils.isEmpty(mBuilder.mHeader)) {
sb.append(mBuilder.mHeader.replace("\n", "<br/>"));
sb.append("<br/><br/>");
}
for (int i = 0; i < mSelectedApps.size(); i++) {
if (i > 0) sb.append("<br/><br/>");
final App app = mSelectedApps.get(i);
sb.append(String.format("Name: <b>%s</b><br/>", app.getName()));
sb.append(String.format("Code: <b>%s</b><br/>", app.getCode()));
sb.append(String.format("Link: https://play.google" +
".com/store/apps/details?id=%s<br/>", app.getPackage()));
}
if (mBuilder.mIncludeDeviceInfo) {
sb.append("<br/><br/><br/>OS Version: ").append(System.getProperty("os.version"))
.append("(").append(Build.VERSION.INCREMENTAL).append(")");
sb.append("<br/>OS API Level: ").append(Build.VERSION.SDK_INT);
sb.append("<br/>Device: ").append(Build.MODEL);
sb.append("<br/>Manufacturer: ").append(Build.MANUFACTURER);
sb.append("<br/>Model (and Product): ").append(Build.DEVICE).append(" (").append
(Build.PRODUCT).append(")");
PackageInfo appInfo;
try {
appInfo = mBuilder.mContext.getPackageManager().getPackageInfo(mBuilder.mContext
.getPackageName(), 0);
sb.append("<br/>App Version Name: ").append(appInfo.versionName);
sb.append("<br/>App Version Code: ").append(appInfo.versionCode);
} catch (PackageManager.NameNotFoundException e) {
sb.append("<br/>There was an error getting application version.");
}
if (mBuilder.mFooter != null) {
sb.append("<br/>");
sb.append(mBuilder.mFooter.replace("\n", "<br/>"));
}
} else {
sb.append("<br/><br/>");
sb.append(mBuilder.mFooter.replace("\n", "<br/>"));
}
return sb.toString();
}
public boolean selectApp(@NonNull App app) {
if (!mSelectedApps.contains(app)) {
mSelectedApps.add(app);
EventBusUtils.post(new AppSelectionEvent(mSelectedApps.size()), mBuilder
.mSelectionState);
return true;
}
return false;
}
public boolean unselectApp(@NonNull App app) {
final boolean result = mSelectedApps.remove(app);
if (result)
EventBusUtils.post(new AppSelectionEvent(mSelectedApps.size()), mBuilder
.mSelectionState);
return result;
}
public boolean toggleAppSelected(@NonNull App app) {
final boolean result;
if (isAppSelected(app)) {
result = unselectApp(app);
} else {
int state = getRequestState(false);
if (state != STATE_NORMAL) {
if (mBuilder.mCallback != null) {
mBuilder.mCallback.onRequestLimited(mBuilder.mContext, state, getRequestsLeft
(), getMillisToFinish());
}
return false;
} else {
result = selectApp(app);
}
}
return result;
}
public boolean isAppSelected(@NonNull App app) {
return mSelectedApps.contains(app);
}
public boolean selectAllApps() {
if (mApps == null) return false;
boolean changed = false;
for (App app : mApps) {
if (!mSelectedApps.contains(app)) {
if (getRequestState(false) == STATE_NORMAL) {
changed = true;
mSelectedApps.add(app);
}
}
}
if (changed)
EventBusUtils.post(new AppSelectionEvent(mSelectedApps.size()), mBuilder
.mSelectionState);
if ((getRequestState(false) != STATE_NORMAL) && (mBuilder.mCallback != null))
mBuilder.mCallback.onRequestLimited(mBuilder.mContext, state, getRequestsLeft(),
getMillisToFinish());
return changed;
}
public boolean unselectAllApps() {
if (mSelectedApps == null || mSelectedApps.size() == 0) return false;
mSelectedApps.clear();
EventBusUtils.post(new AppSelectionEvent(0), mBuilder.mSelectionState);
return true;
}
public boolean isNotEmpty() {
return getApps() != null && getApps().size() > 0;
}
public boolean isLoading() {
return mBuilder.mIsLoading;
}
@Nullable
public ArrayList<App> getApps() {
return mApps;
}
@NonNull
public ArrayList<App> getSelectedApps() {
if (mSelectedApps == null)
mSelectedApps = new ArrayList<>();
return mSelectedApps;
}
@WorkerThread
private void postError(@NonNull final String msg, @Nullable final Exception baseError) {
IRLog.e(msg, baseError);
EventBusUtils.post(new RequestEvent(false, false, new Exception(msg, baseError)),
mBuilder.mRequestState);
}
public void send(final OnRequestProgress onRequestProgress) {
IRLog.d("Preparing your request to send...");
EventBusUtils.post(new RequestEvent(true, false, null), mBuilder.mRequestState);
boolean requestError = false;
if (mApps == null) {
requestError = true;
postError("No apps were loaded from this device.", null);
} else if (IRUtils.isEmpty(mBuilder.mEmail)) {
requestError = true;
postError("The recipient email for the request cannot be empty.", null);
} else if (getSelectedApps().size() <= 0) {
if (mBuilder.mNoneSelectsAll) {
mSelectedApps = mApps;
requestError = false;
} else {
requestError = true;
if (mBuilder.mCallback != null)
mBuilder.mCallback.onRequestEmpty(mBuilder.mContext);
postError("No apps have been selected for sending in the request.", null);
}
} else if (IRUtils.isEmpty(mBuilder.mSubject)) {
mBuilder.mSubject = "Icon Request";
requestError = false;
}
if (requestError) {
if (onRequestProgress != null) onRequestProgress.doOnError();
return;
}
@State int currentState = getRequestState(true);
if (currentState == STATE_NORMAL) {
new Thread(new Runnable() {
@SuppressWarnings({"ResultOfMethodCallIgnored", "deprecation"})
@Override
public void run() {
if (onRequestProgress != null)
onRequestProgress.doWhenStarted();
final ArrayList<File> filesToZip = new ArrayList<>();
FileUtil.wipe(mBuilder.mSaveDir);
mBuilder.mSaveDir.mkdirs();
// Save app icons
IRLog.d("Saving icons...");
ArrayList<String> appNames = new ArrayList<>();
String prevName = "";
int count = 1;
for (App app : mSelectedApps) {
String iconName = app.getName();
if (prevName.equalsIgnoreCase(iconName)) {
iconName += "_" + String.valueOf(count);
count += 1;
} else {
count = 1;
}
final Drawable drawable = app.getHighResIcon(mBuilder.mContext);
if (!(drawable instanceof BitmapDrawable)) continue;
final BitmapDrawable bDrawable = (BitmapDrawable) drawable;
final Bitmap icon = bDrawable.getBitmap();
final File file = new File(mBuilder.mSaveDir,
String.format("%s.png", IRUtils.drawableName(iconName)));
appNames.add(iconName);
filesToZip.add(file);
try {
FileUtil.writeIcon(file, icon);
} catch (final Exception e) {
e.printStackTrace();
postError("Failed to save icon\'" + iconName + "\'due to error: "
+ e.getMessage(), e);
if (onRequestProgress != null) onRequestProgress.doOnError();
return;
}
prevName = iconName;
}
// Create request files
IRLog.d("Creating request files...");
StringBuilder xmlSb = null;
StringBuilder amSb = null;
StringBuilder trSb = null;
StringBuilder jsonSb = null;
if (mBuilder.mGenerateAppFilterXml) {
xmlSb = new StringBuilder("<resources>\n" +
"\t<iconback img1=\"iconback\"/>\n" +
"\t<iconmask img1=\"iconmask\"/>\n" +
"\t<iconupon img1=\"iconupon\"/>\n" +
"\t<scale factor=\"1.0\"/>");
}
if (mBuilder.mGenerateAppMapXml) {
amSb = new StringBuilder("<appmap>");
}
if (mBuilder.mGenerateThemeResourcesXml) {
trSb = new StringBuilder("<Theme version=\"1\">\n" +
"\t<Label value=\"" + mBuilder.mAppName + "\"/>\n" +
"\t<Wallpaper image=\"wallpaper_01\"/>\n" +
"\t<LockScreenWallpaper image=\"wallpaper_02\"/>\n" +
"\t<ThemePreview image=\"preview1\"/>\n" +
"\t<ThemePreviewWork image=\"preview1\"/>\n" +
"\t<ThemePreviewMenu image=\"preview1\"/>\n" +
"\t<DockMenuAppIcon selector=\"drawer\"/>");
}
if (mBuilder.mGenerateAppFilterJson) {
jsonSb = new StringBuilder("{\n" +
"\t\"components\": [");
}
int index = 0;
int n = 1;
appNames.clear();
for (App app : mSelectedApps) {
final String name = app.getName();
String iconName = name;
if (appNames.contains(iconName)) {
iconName += String.valueOf(n);
n += 1;
}
final String drawableName = IRUtils.drawableName(iconName);
if (xmlSb != null) {
xmlSb.append("\n\n");
if (mBuilder.mComments) {
xmlSb.append("\t<!-- ")
.append(name)
.append(" -->\n");
}
xmlSb.append(String.format("\t<item\n" +
"\t\tcomponent=\"ComponentInfo{%s}\"\n" +
"\t\tdrawable=\"%s\"/>",
app.getCode(), drawableName));
}
if (amSb != null) {
amSb.append("\n\n");
if (mBuilder.mComments) {
amSb.append("\t<!-- ")
.append(name)
.append(" -->\n");
}
amSb.append(String.format("\t<item\n" +
"\t\tclass=\"%s\"\n" +
"\t\tname=\"%s\"/>",
app.getCode().split("/")[1], drawableName));
}
if (trSb != null) {
trSb.append("\n\n");
if (mBuilder.mComments) {
trSb.append("\t<!-- ")
.append(name)
.append(" -->\n");
}
trSb.append(String.format("\t<AppIcon\n" +
"\t\tname=\"%s\"\n" +
"\t\timage=\"%s\"/>",
app.getCode(), drawableName));
}
if (jsonSb != null) {
if (index > 0) jsonSb.append(",");
jsonSb.append("\n {\n")
.append(String.format("\t\t\t\"%s\": \"%s\",\n", "name", name))
.append(String.format("\t\t\t\"%s\": \"%s\",\n", "pkg", app
.getPackage()))
.append(String.format("\t\t\t\"%s\": \"%s\",\n",
"componentInfo",
app.getCode()))
.append(String.format("\t\t\t\"%s\": \"%s\"\n", "drawable",
drawableName))
.append("\t\t}");
}
index++;
appNames.add(iconName);
}
final String date = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault())
.format
(new Date());
if (xmlSb != null) {
xmlSb.append("\n\n</resources>");
final File newAppFilter = new File(mBuilder.mSaveDir, String.format
("appfilter_%s.xml", date));
filesToZip.add(newAppFilter);
try {
FileUtil.writeAll(newAppFilter, xmlSb.toString());
} catch (final Exception e) {
e.printStackTrace();
postError("Failed to write your request appfilter.xml file: " + e
.getMessage(), e);
if (onRequestProgress != null) onRequestProgress.doOnError();
return;
}
}
if (amSb != null) {
amSb.append("\n\n</appmap>");
final File newAppFilter = new File(mBuilder.mSaveDir, String.format
("appmap_%s.xml", date));
filesToZip.add(newAppFilter);
try {
FileUtil.writeAll(newAppFilter, amSb.toString());
} catch (final Exception e) {
e.printStackTrace();
postError("Failed to write your request appmap.xml file: " +
e.getMessage(), e);
if (onRequestProgress != null) onRequestProgress.doOnError();
return;
}
}
if (trSb != null) {
trSb.append("\n\n</Theme>");
final File newAppFilter = new File(mBuilder.mSaveDir, String.format
("theme_resources_%s.xml", date));
filesToZip.add(newAppFilter);
try {
FileUtil.writeAll(newAppFilter, trSb.toString());
} catch (final Exception e) {
e.printStackTrace();
postError("Failed to write your request theme_resources.xml file: " +
e.getMessage(), e);
if (onRequestProgress != null) onRequestProgress.doOnError();
return;
}
}
if (jsonSb != null) {
jsonSb.append("\n ]\n}");
final File newAppFilter = new File(mBuilder.mSaveDir, String.format
("appfilter_%s.json", date));
filesToZip.add(newAppFilter);
try {
FileUtil.writeAll(newAppFilter, jsonSb.toString());
} catch (final Exception e) {
e.printStackTrace();
postError("Failed to write your request appfilter.json file: " +
e.getMessage(), e);
if (onRequestProgress != null) onRequestProgress.doOnError();
return;
}
}
if (filesToZip.size() == 0) {
postError("There are no files to put into the ZIP archive.", null);
if (onRequestProgress != null) onRequestProgress.doOnError();
return;
}
// Zip everything into an archive
IRLog.d("Creating ZIP...");
final File zipFile = new File(mBuilder.mSaveDir,
String.format("IconRequest-%s.zip", date));
try {
ZipUtil.zip(zipFile, filesToZip.toArray(new File[filesToZip.size()]));
} catch (final Exception e) {
e.printStackTrace();
postError("Failed to create the request ZIP file: " + e.getMessage(), e);
if (onRequestProgress != null) onRequestProgress.doOnError();
return;
}
// Cleanup files
IRLog.d("Cleaning up files...");
final File[] files = mBuilder.mSaveDir.listFiles();
for (File fi : files) {
if (!fi.isDirectory() && (fi.getName().endsWith(".png") || fi.getName()
.endsWith(".xml"))) {
fi.delete();
}
}
// post(new Runnable() {
// @Override
// public void run() {
// Send email intent
IRLog.d("Launching intent!");
final Uri zipUri = Uri.fromFile(zipFile);
final Intent emailIntent = new Intent(Intent.ACTION_SEND)
.putExtra(Intent.EXTRA_EMAIL, new String[]{mBuilder.mEmail})
.putExtra(Intent.EXTRA_SUBJECT, mBuilder.mSubject)
.putExtra(Intent.EXTRA_TEXT, Html.fromHtml(getBody()))
.putExtra(Intent.EXTRA_STREAM, zipUri)
.setType("application/zip");
int amount = (getRequestsLeft() - getSelectedApps().size());
IRLog.d("Request: Allowing " + amount + " more requests.");
saveRequestsLeft(amount < 0 ? 0 : amount);
if (getRequestsLeft() == 0)
saveRequestMoment();
if (onRequestProgress != null)
onRequestProgress.doWhenReady();
if (mBuilder.mContext instanceof Activity) {
((Activity) mBuilder.mContext).startActivityForResult(Intent.createChooser(
emailIntent, mBuilder.mContext.getString(R.string.send_using)),
INTENT_CODE);
} else {
mBuilder.mContext.startActivity(Intent.createChooser(
emailIntent, mBuilder.mContext.getString(R.string.send_using)));
}
EventBusUtils.post(new RequestEvent(false, true, null), mBuilder.mRequestState);
}
})
.start();
} else {
if (mBuilder.mCallback != null)
mBuilder.mCallback.onRequestLimited(mBuilder.mContext, currentState,
getRequestsLeft(), getMillisToFinish());
}
}
@State
private int getRequestState(boolean toSend) {
if ((mBuilder.mMaxCount <= 0) || (mBuilder.mTimeLimit <= 0)) return STATE_NORMAL;
int sum = toSend ? 0 : 1;
IRLog.d("Selected apps: " + getSelectedApps().size() + " - Requests left: " +
getRequestsLeft());
if ((getSelectedApps().size() + sum) > getRequestsLeft()) {
if (getMillisToFinish() > 0) {
IRLog.d("RequestState: Limited by time");
IRLog.d("RequestState: Millis to finish: " + getMillisToFinish() + " - Request " +
"limit: " + mBuilder.mTimeLimit);
return STATE_TIME_LIMITED;
} else if (getRequestsLeft() == 0) {
saveRequestsLeft(-1);
IRLog.d("RequestState: Restarting requests left.");
return STATE_NORMAL;
}
IRLog.d("RequestState: Limited by requests - Requests left: " + getRequestsLeft());
return STATE_LIMITED;
} else {
if (getMillisToFinish() > 0) {
IRLog.d("RequestState: Limited by time");
IRLog.d("RequestState: Millis to finish: " + getMillisToFinish() + " - Request " +
"limit: " + mBuilder.mTimeLimit);
return STATE_TIME_LIMITED;
}
}
return STATE_NORMAL;
}
private void saveRequestMoment() {
mBuilder.mPrefs.edit().putLong(KEY_SAVED_TIME_MILLIS, IRUtils.getCurrentTimeInMillis())
.apply();
}
@SuppressLint("SimpleDateFormat")
private long getMillisToFinish() {
long savedTime = mBuilder.mPrefs.getLong(KEY_SAVED_TIME_MILLIS, -1);
if (savedTime == -1) return -1;
long elapsedTime = IRUtils.getCurrentTimeInMillis() - savedTime;
SimpleDateFormat sdf = new SimpleDateFormat("MMM dd,yyyy HH:mm:ss");
IRLog.d("Timer: [Last request was on: " + sdf.format(savedTime) + "] - [Right" +
" now is: " + sdf.format(new Date(IRUtils.getCurrentTimeInMillis())) + "] - " +
"[Time Left: ~" + ((mBuilder.mTimeLimit - elapsedTime) / 1000) + " secs.]");
return mBuilder.mTimeLimit - elapsedTime - 500;
}
private int getRequestsLeft() {
int requestsLeft = mBuilder.mPrefs.getInt(MAX_APPS, -1);
if (requestsLeft > -1) {
return requestsLeft;
} else {
saveRequestsLeft(mBuilder.mMaxCount);
return mBuilder.mPrefs.getInt(MAX_APPS, mBuilder.mMaxCount);
}
}
private void saveRequestsLeft(int requestsLeft) {
mBuilder.mPrefs.edit().putInt(MAX_APPS, requestsLeft).apply();
}
public static void saveInstanceState(Bundle outState) {
if (mRequest == null || outState == null) return;
outState.putParcelable("butler_builder", mRequest.mBuilder);
outState.putParcelableArrayList("apps", mRequest.mApps);
outState.putParcelableArrayList("selected_apps", mRequest.mSelectedApps);
}
@SuppressWarnings("unchecked")
@Nullable
public static IconRequest restoreInstanceState(Context context, Bundle inState) {
if (inState == null || !inState.containsKey("butler_builder"))
return null;
mRequest = new IconRequest();
mRequest.mBuilder = inState.getParcelable("butler_builder");
if (mRequest.mBuilder != null) {
mRequest.mBuilder.mContext = context;
}
if (inState.containsKey("apps"))
mRequest.mApps = inState.getParcelableArrayList("apps");
if (inState.containsKey("selected_apps"))
mRequest.mSelectedApps = inState.getParcelableArrayList("selected_apps");
if (mRequest.mApps == null)
mRequest.mApps = new ArrayList<>();
if (mRequest.mSelectedApps == null)
mRequest.mSelectedApps = new ArrayList<>();
else if (mRequest.mSelectedApps.size() > 0 && mRequest.mBuilder != null)
EventBusUtils.post(new AppSelectionEvent(mRequest.mSelectedApps.size()), mRequest
.mBuilder.mRequestState);
return mRequest;
}
public static void cleanup() {
if (mRequest == null) return;
if (mRequest.mBuilder != null) {
mRequest.mBuilder.mContext = null;
mRequest.mBuilder = null;
}
if (mRequest.mApps != null) {
mRequest.mApps.clear();
mRequest.mApps = null;
}
if (mRequest.mSelectedApps != null) {
mRequest.mSelectedApps.clear();
mRequest.mSelectedApps = null;
}
IRUtils.clearTimers();
mRequest = null;
}
}